feat(profile): partition skills/plugins/MCPs/CLIs by usage profile
Ship lib/profile.sh + 9 profiles in lib/profiles/. A profile is a
plain-text file listing items + types (gstack | personal | external |
plugin@<marketplace> | mcp | cli). `profile set <name>` enables the
listed items and disables the rest:
- gstack/personal/external skills: symlink toggle skills/ ↔
skills-disabled/ (gstack__<name> prefix to avoid collisions; no
prefix for personal/external).
- plugins typed `plugin@<marketplace>`: actually toggled via
`claude plugin enable|disable <name>@<marketplace>`. Allowlist:
MANAGED_PLUGINS = ui-ux-pro-max, plugin-dev, pr-review-toolkit.
Denylist: PROTECTED_PLUGINS = caveman, security-guidance,
superpowers (always-on, never disabled even if absent from a
profile).
- mcp magic: delegated to lib/toggle-external.sh which already
handles the MAGIC_API_KEY env lookup. Other MCPs stay advisory.
- cli (rtk, gsd, ctx7, graphify): status-only, never auto-installed.
Profiles shipped:
web public website work — frontend + content + light dev
seo SEO + GEO + W3C audit (search/AI indexability + a11y)
web-full production website end-to-end (web ∪ seo ∪ qa-only/canary)
backend backend / API / system dev — no design, no SEO
design visual QA, design systems, mockups, polish
dev daily code work — features, fixes, refactor, ship
qa site testing, perf, canary, validation
audit comprehensive audit — security + SEO + perf + health
minimal strip all gstack skills (quiet session)
Commands:
profile list / show <name> / current / apply <name> / set <name> /
reset / diff <a> <b>
`current` heuristic returns "full" when nothing is disabled, otherwise
picks the profile with the highest available-ratio (counts both
"enabled" and "installed" — the latter for CLIs). Tiebreaker: larger
profile total wins, so web-full beats web at a 100% tie.
`reset` re-enables every gstack skill but does NOT touch plugins —
the user re-enables a managed plugin manually or via `apply <profile>`.
This is documented in the trailing info line.
Integration:
- skills/profile/SKILL.md — `/profile` slash command, lists profiles,
documents the per-type mechanism, points at lib/profile.sh.
- agents/plugin-advisor.md — DETECT phase calls `profile current`,
OUTPUT adds a PROFILE line, and TOGGLING EXTERNAL TOOLS gains a
"Skill profiles" section with a signal → profile recommendation
table.
- lib/toggle-external.sh — header pointer to profile.sh for fine-
grained activation (toggle-external still owns whole-gstack and
magic-MCP toggles).
- Makefile — `make profile cmd="set <name>"`, profile-list,
profile-current, profile-reset.
Tested end-to-end: `set web` enables ui-ux-pro-max + magic; `set seo`
disables ui-ux-pro-max; `set minimal` disables ui-ux-pro-max but
spares always-on plugins; `reset` restores all 64 skills; shellcheck
clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d8ad38b131
commit
239d91db67
14
Makefile
14
Makefile
@ -1,4 +1,4 @@
|
|||||||
.PHONY: help install plugin link doctor update new-skill
|
.PHONY: help install plugin link doctor update new-skill profile profile-list profile-current profile-reset
|
||||||
|
|
||||||
help: ## Show available commands
|
help: ## Show available commands
|
||||||
@grep -E '^[a-zA-Z_-]+:.*##' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*## "}; {printf " make %-14s %s\n", $$1, $$2}'
|
@grep -E '^[a-zA-Z_-]+:.*##' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*## "}; {printf " make %-14s %s\n", $$1, $$2}'
|
||||||
@ -22,6 +22,18 @@ onboard: link ## Onboard an existing project (run from the project directory)
|
|||||||
@echo "Open Claude Code in your project directory and run: /onboard"
|
@echo "Open Claude Code in your project directory and run: /onboard"
|
||||||
@echo "Or with hints: /onboard Python FastAPI monorepo"
|
@echo "Or with hints: /onboard Python FastAPI monorepo"
|
||||||
|
|
||||||
|
profile: ## Run profile.sh (usage: make profile cmd="set design")
|
||||||
|
@bash lib/profile.sh $(cmd)
|
||||||
|
|
||||||
|
profile-list: ## List skill profiles (design, dev, qa, audit, minimal)
|
||||||
|
@bash lib/profile.sh list
|
||||||
|
|
||||||
|
profile-current: ## Detect which skill profile is currently active
|
||||||
|
@bash lib/profile.sh current
|
||||||
|
|
||||||
|
profile-reset: ## Re-enable all gstack skills (undo any profile set)
|
||||||
|
@bash lib/profile.sh reset
|
||||||
|
|
||||||
new-skill: ## Create a new skill scaffold (usage: make new-skill name=myskill)
|
new-skill: ## Create a new skill scaffold (usage: make new-skill name=myskill)
|
||||||
@test -n "$(name)" || (echo "Usage: make new-skill name=myskill" && exit 1)
|
@test -n "$(name)" || (echo "Usage: make new-skill name=myskill" && exit 1)
|
||||||
@mkdir -p agents skills/$(name)
|
@mkdir -p agents skills/$(name)
|
||||||
|
|||||||
@ -23,6 +23,11 @@ claude plugin list 2>/dev/null || echo "plugin-list-unavailable"
|
|||||||
# `claude plugin enable|disable` does not apply to them.
|
# `claude plugin enable|disable` does not apply to them.
|
||||||
bash "$HOME/.claude/lib/toggle-external.sh" list 2>/dev/null || echo "toggle-external-unavailable"
|
bash "$HOME/.claude/lib/toggle-external.sh" list 2>/dev/null || echo "toggle-external-unavailable"
|
||||||
|
|
||||||
|
# Active skill profile — design / dev / qa / audit / minimal / custom.
|
||||||
|
# Profiles partition gstack + personal skills by purpose. See
|
||||||
|
# lib/profile.sh and lib/profiles/*.profile.
|
||||||
|
bash "$HOME/.claude/lib/profile.sh" current 2>/dev/null || echo "profile-unavailable"
|
||||||
|
|
||||||
# Context7 CLI
|
# Context7 CLI
|
||||||
command -v ctx7 &>/dev/null && ctx7 --version 2>/dev/null | head -1 || echo "ctx7-not-installed"
|
command -v ctx7 &>/dev/null && ctx7 --version 2>/dev/null | head -1 || echo "ctx7-not-installed"
|
||||||
|
|
||||||
@ -114,6 +119,7 @@ Output: `COMPLEXITY: <score>% — <label>` with one-line justification.
|
|||||||
```
|
```
|
||||||
PLUGIN CHECK
|
PLUGIN CHECK
|
||||||
ACTIVE: [plugin — status, one line each]
|
ACTIVE: [plugin — status, one line each]
|
||||||
|
PROFILE: [active skill profile — name + match%, or "custom"]
|
||||||
SIGNALS: [detected signals]
|
SIGNALS: [detected signals]
|
||||||
COMPLEXITY: <score>% — <simple|moderate|complex|enterprise>
|
COMPLEXITY: <score>% — <simple|moderate|complex|enterprise>
|
||||||
PLAN: <Max|Pro|Free> (budget: ~<N>t passive tokens)
|
PLAN: <Max|Pro|Free> (budget: ~<N>t passive tokens)
|
||||||
@ -308,6 +314,40 @@ bash $HOME/.claude/lib/toggle-external.sh enable gstack
|
|||||||
bash $HOME/.claude/lib/toggle-external.sh disable darwin-skill
|
bash $HOME/.claude/lib/toggle-external.sh disable darwin-skill
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Skill profiles (fine-grained partitioning, with plugin + MCP toggle)
|
||||||
|
|
||||||
|
For task-shaped activation (web only, seo only, backend only, design only,
|
||||||
|
etc.) prefer `lib/profile.sh` over toggling all of gstack at once. Profiles
|
||||||
|
activate a curated subset of skills + plugins + MCPs and disable the rest of
|
||||||
|
gstack + managed plugins — sessions stay focused and passive token cost drops.
|
||||||
|
|
||||||
|
`profile set <name>` actually toggles plugins (`claude plugin enable|disable`)
|
||||||
|
and MCPs (delegates to `lib/toggle-external.sh` for `magic`) — not just
|
||||||
|
advisory. Always-on plugins (`caveman`, `security-guidance`, `superpowers`)
|
||||||
|
are protected. Managed plugins that `set` may toggle:
|
||||||
|
`ui-ux-pro-max@ui-ux-pro-max-skill`, `plugin-dev@claude-code-plugins`,
|
||||||
|
`pr-review-toolkit@claude-code-plugins`. Other plugins are never auto-toggled.
|
||||||
|
|
||||||
|
When the project signal matches one of the canonical profiles, recommend the
|
||||||
|
matching `profile set` command:
|
||||||
|
|
||||||
|
| Signal | Recommended profile | Command |
|
||||||
|
|---|---|---|
|
||||||
|
| `frontend` public website (no backend, no SEO focus) | `web` | `bash $HOME/.claude/lib/profile.sh set web` |
|
||||||
|
| audit-only: SEO + GEO + W3C + WCAG | `seo` | `bash $HOME/.claude/lib/profile.sh set seo` |
|
||||||
|
| public website end-to-end (build + audit) | `web-full` | `bash $HOME/.claude/lib/profile.sh set web-full` |
|
||||||
|
| backend / API / system / library (no UI, no SEO) | `backend` | `bash $HOME/.claude/lib/profile.sh set backend` |
|
||||||
|
| `design-system` (heavy UI work, no dev) | `design` | `bash $HOME/.claude/lib/profile.sh set design` |
|
||||||
|
| `simple` / hotfix / typical dev session | `dev` | `bash $HOME/.claude/lib/profile.sh set dev` |
|
||||||
|
| `browser-qa` (e2e tests, no design work) | `qa` | `bash $HOME/.claude/lib/profile.sh set qa` |
|
||||||
|
| comprehensive audit (security + SEO + perf) | `audit` | `bash $HOME/.claude/lib/profile.sh set audit` |
|
||||||
|
| narrow session, minimal noise | `minimal` | `bash $HOME/.claude/lib/profile.sh set minimal` |
|
||||||
|
|
||||||
|
To restore the full skill set: `bash $HOME/.claude/lib/profile.sh reset`.
|
||||||
|
Plugin state is NOT touched by reset — re-enable a managed plugin manually
|
||||||
|
or by applying a profile that lists it (e.g. `apply web` to restore
|
||||||
|
`ui-ux-pro-max`).
|
||||||
|
|
||||||
## BLOCK if
|
## BLOCK if
|
||||||
|
|
||||||
- Superpowers not active → install: `claude plugin marketplace add obra/superpowers-marketplace && claude plugin install --scope user superpowers@superpowers-marketplace`
|
- Superpowers not active → install: `claude plugin marketplace add obra/superpowers-marketplace && claude plugin install --scope user superpowers@superpowers-marketplace`
|
||||||
|
|||||||
525
lib/profile.sh
Executable file
525
lib/profile.sh
Executable file
@ -0,0 +1,525 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ============================================================
|
||||||
|
# lib/profile.sh — Partition Claude skills + plugins + MCPs by purpose
|
||||||
|
#
|
||||||
|
# Profiles group skills (gstack + external + personal), plugins, MCPs,
|
||||||
|
# and CLIs for a specific kind of work: web, seo, web-full, backend,
|
||||||
|
# design, dev, qa, audit, minimal. Apply a profile to enable just the
|
||||||
|
# relevant tools and disable the rest, instead of carrying every gstack
|
||||||
|
# skill + every plugin in every session.
|
||||||
|
#
|
||||||
|
# Mechanism:
|
||||||
|
# - Skills (gstack/external/personal): symlink toggle skills/ ↔ skills-disabled/
|
||||||
|
# - Plugins: `claude plugin enable|disable <name>@<marketplace>`
|
||||||
|
# - MCPs: delegated to lib/toggle-external.sh for known servers (magic),
|
||||||
|
# advisory otherwise
|
||||||
|
# - CLIs: advisory only (rtk, gsd, ctx7, graphify — installed externally)
|
||||||
|
#
|
||||||
|
# Always-on plugins (never toggled by `set`): caveman, security-guidance,
|
||||||
|
# superpowers + rtk hook + .claude internal. The script refuses to disable
|
||||||
|
# anything in PROTECTED_PLUGINS.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# profile.sh list list available profiles
|
||||||
|
# profile.sh show <name> show contents of a profile
|
||||||
|
# profile.sh current detect which profile is active
|
||||||
|
# profile.sh apply <name> enable items in profile (additive)
|
||||||
|
# profile.sh set <name> enable only profile (disables rest)
|
||||||
|
# profile.sh reset re-enable all gstack skills + managed plugins
|
||||||
|
# profile.sh diff <a> <b> compare two profiles
|
||||||
|
#
|
||||||
|
# Profile file format (lib/profiles/<name>.profile):
|
||||||
|
# # DESC: <one-line description>
|
||||||
|
# <skill-name> # type defaults to "gstack"
|
||||||
|
# <skill-name> personal # personal skill (skills/<x>/SKILL.md is real)
|
||||||
|
# <skill-name> external # symlinked into skills-external/
|
||||||
|
# <plugin-name> plugin@<marketplace> # Claude plugin — auto-toggle
|
||||||
|
# <mcp-name> mcp # MCP — advisory or via toggle-external
|
||||||
|
# <cli-name> cli # standalone CLI — advisory only
|
||||||
|
#
|
||||||
|
# ============================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
SKILLS_DIR="$REPO/skills"
|
||||||
|
DISABLED_DIR="$REPO/skills-disabled"
|
||||||
|
PROFILES_DIR="$REPO/lib/profiles"
|
||||||
|
TOGGLE_EXTERNAL="$REPO/lib/toggle-external.sh"
|
||||||
|
|
||||||
|
# Plugins that are toggle-managed by `set`. Anything NOT in this list is
|
||||||
|
# never auto-disabled — protects always-on plugins (caveman, security-guidance,
|
||||||
|
# superpowers) and unrelated user plugins. Add a plugin here only when its
|
||||||
|
# enabled state is meaningfully driven by task type.
|
||||||
|
MANAGED_PLUGINS=(
|
||||||
|
"ui-ux-pro-max@ui-ux-pro-max-skill"
|
||||||
|
"plugin-dev@claude-code-plugins"
|
||||||
|
"pr-review-toolkit@claude-code-plugins"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Plugins that MUST stay enabled — `set` will refuse to disable these even if
|
||||||
|
# they're not in the profile. (Defensive: belt-and-suspenders alongside
|
||||||
|
# MANAGED_PLUGINS allowlist.)
|
||||||
|
PROTECTED_PLUGINS=(
|
||||||
|
"caveman@caveman"
|
||||||
|
"security-guidance@claude-code-plugins"
|
||||||
|
"superpowers@superpowers-marketplace"
|
||||||
|
)
|
||||||
|
|
||||||
|
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; BLUE='\033[0;34m'; NC='\033[0m'
|
||||||
|
ok() { echo -e "${GREEN}✓${NC} $1"; }
|
||||||
|
warn() { echo -e "${YELLOW}⚠${NC} $1"; }
|
||||||
|
err() { echo -e "${RED}✗${NC} $1" >&2; }
|
||||||
|
info() { echo -e "${BLUE}ℹ${NC} $1"; }
|
||||||
|
|
||||||
|
# ── Profile parsing ────────────────────────────────────────
|
||||||
|
|
||||||
|
# Read a profile file. Output one line per entry: "<skill>\t<type>"
|
||||||
|
# Comments (#…) and blank lines are stripped. Default type is "gstack".
|
||||||
|
read_profile() {
|
||||||
|
local prof="$1"
|
||||||
|
local file="$PROFILES_DIR/$prof.profile"
|
||||||
|
[ -f "$file" ] || { err "Profile not found: $prof (looked in $PROFILES_DIR)"; return 1; }
|
||||||
|
local skill type rest
|
||||||
|
while IFS= read -r line || [ -n "$line" ]; do
|
||||||
|
line="${line%%#*}"
|
||||||
|
# trim leading whitespace + tabs
|
||||||
|
while [[ "$line" =~ ^[[:space:]] ]]; do line="${line#?}"; done
|
||||||
|
# trim trailing whitespace + tabs
|
||||||
|
while [[ "$line" =~ [[:space:]]$ ]]; do line="${line%?}"; done
|
||||||
|
[ -z "$line" ] && continue
|
||||||
|
# split on first whitespace run
|
||||||
|
skill="${line%%[[:space:]]*}"
|
||||||
|
rest="${line#"$skill"}"
|
||||||
|
while [[ "$rest" =~ ^[[:space:]] ]]; do rest="${rest#?}"; done
|
||||||
|
type="${rest:-gstack}"
|
||||||
|
# Validate type. Accepted forms:
|
||||||
|
# gstack | external | personal — skill (symlink toggle)
|
||||||
|
# plugin@<marketplace> — Claude plugin (auto-toggle)
|
||||||
|
# plugin — legacy/advisory (no marketplace known)
|
||||||
|
# mcp — MCP server (advisory or via toggle-external)
|
||||||
|
# cli — standalone CLI (advisory only)
|
||||||
|
case "$type" in
|
||||||
|
gstack|external|personal|plugin|mcp|cli) : ;;
|
||||||
|
plugin@*) : ;;
|
||||||
|
*) warn "unknown type '$type' for entry '$skill' in $prof — defaulting to gstack"; type=gstack ;;
|
||||||
|
esac
|
||||||
|
printf '%s\t%s\n' "$skill" "$type"
|
||||||
|
done < "$file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# All skills bundled in skills-external/gstack/
|
||||||
|
gstack_skills() {
|
||||||
|
local src="$REPO/skills-external/gstack"
|
||||||
|
[ -d "$src" ] || return 0
|
||||||
|
for d in "$src"/*/; do
|
||||||
|
[ -f "${d}SKILL.md" ] || continue
|
||||||
|
basename "$d"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Profile description (line starting with "# DESC: …")
|
||||||
|
profile_desc() {
|
||||||
|
local file="$1"
|
||||||
|
grep -m1 '^# DESC:' "$file" 2>/dev/null | sed 's/^# DESC:[[:space:]]*//' || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Status detection ──────────────────────────────────────
|
||||||
|
|
||||||
|
skill_status() {
|
||||||
|
local skill="$1" type="$2"
|
||||||
|
case "$type" in
|
||||||
|
gstack|external|personal)
|
||||||
|
if [ -e "$SKILLS_DIR/$skill" ]; then
|
||||||
|
echo "enabled"
|
||||||
|
elif [ -e "$DISABLED_DIR/gstack__$skill" ] || [ -e "$DISABLED_DIR/$skill" ]; then
|
||||||
|
echo "disabled"
|
||||||
|
else
|
||||||
|
echo "missing"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
plugin|plugin@*)
|
||||||
|
# `claude plugin list` is the source of truth — settings.json may be
|
||||||
|
# ahead of or behind reality if the user toggled outside this tool.
|
||||||
|
if command -v claude >/dev/null 2>&1; then
|
||||||
|
# Match the plugin block by name then check Status line
|
||||||
|
if claude plugin list 2>/dev/null \
|
||||||
|
| awk -v p="$skill" '
|
||||||
|
/^[[:space:]]*❯ '"$skill"'@/ { found=1; next }
|
||||||
|
found && /Status:/ { print; exit }
|
||||||
|
' \
|
||||||
|
| grep -q "✔ enabled"; then
|
||||||
|
echo "enabled"
|
||||||
|
else
|
||||||
|
echo "disabled"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "unknown"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
mcp)
|
||||||
|
if command -v claude >/dev/null 2>&1 && \
|
||||||
|
claude mcp list 2>/dev/null | grep -q "^${skill}"; then
|
||||||
|
echo "enabled"
|
||||||
|
else
|
||||||
|
echo "disabled"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
cli)
|
||||||
|
command -v "$skill" >/dev/null 2>&1 && echo "installed" || echo "not-installed"
|
||||||
|
;;
|
||||||
|
*) echo "unknown" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Enable / disable ──────────────────────────────────────
|
||||||
|
|
||||||
|
enable_skill() {
|
||||||
|
local skill="$1" type="$2"
|
||||||
|
case "$type" in
|
||||||
|
gstack)
|
||||||
|
if [ -e "$DISABLED_DIR/gstack__$skill" ]; then
|
||||||
|
rm -rf "${SKILLS_DIR:?}/${skill:?}"
|
||||||
|
mv "$DISABLED_DIR/gstack__$skill" "$SKILLS_DIR/$skill"
|
||||||
|
ok "enabled: $skill"
|
||||||
|
elif [ -e "$DISABLED_DIR/$skill" ]; then
|
||||||
|
rm -rf "${SKILLS_DIR:?}/${skill:?}"
|
||||||
|
mv "$DISABLED_DIR/$skill" "$SKILLS_DIR/$skill"
|
||||||
|
ok "enabled: $skill"
|
||||||
|
elif [ -e "$SKILLS_DIR/$skill" ]; then
|
||||||
|
: # already enabled — silent
|
||||||
|
else
|
||||||
|
warn "missing: $skill — try: bash link.sh"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
external|personal)
|
||||||
|
if [ -e "$DISABLED_DIR/$skill" ]; then
|
||||||
|
rm -rf "${SKILLS_DIR:?}/${skill:?}"
|
||||||
|
mv "$DISABLED_DIR/$skill" "$SKILLS_DIR/$skill"
|
||||||
|
ok "enabled: $skill ($type)"
|
||||||
|
elif [ -e "$SKILLS_DIR/$skill" ]; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
warn "missing: $skill ($type)"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
plugin@*)
|
||||||
|
# type holds the marketplace: plugin@<marketplace>
|
||||||
|
local marketplace="${type#plugin@}"
|
||||||
|
if [ "$(skill_status "$skill" "$type")" = "enabled" ]; then
|
||||||
|
: # already on
|
||||||
|
elif command -v claude >/dev/null 2>&1; then
|
||||||
|
if claude plugin enable "${skill}@${marketplace}" 2>&1 | grep -qiE "enabled|already"; then
|
||||||
|
ok "enabled plugin: ${skill}@${marketplace}"
|
||||||
|
else
|
||||||
|
warn "could not enable plugin: ${skill}@${marketplace}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
info "claude CLI not in PATH — manual: claude plugin enable ${skill}@${marketplace}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
plugin)
|
||||||
|
# No marketplace specified — purely advisory.
|
||||||
|
if [ "$(skill_status "$skill" plugin)" = "enabled" ]; then
|
||||||
|
: # already on
|
||||||
|
else
|
||||||
|
info "plugin '$skill' not enabled — run: claude plugin enable $skill@<marketplace>"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
mcp)
|
||||||
|
if [ "$(skill_status "$skill" mcp)" = "enabled" ]; then
|
||||||
|
: # already on
|
||||||
|
elif [ "$skill" = "magic" ] && [ -x "$TOGGLE_EXTERNAL" ]; then
|
||||||
|
# Known MCP — delegate to lib/toggle-external.sh which handles env vars.
|
||||||
|
if bash "$TOGGLE_EXTERNAL" enable magic 2>&1 | grep -qE "enabled|already"; then
|
||||||
|
ok "enabled MCP: magic"
|
||||||
|
else
|
||||||
|
info "MCP 'magic' could not be enabled (check .env for MAGIC_API_KEY)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
info "MCP '$skill' not registered — run: claude mcp add $skill -- <command>"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
cli)
|
||||||
|
# CLIs install externally; we never auto-install. Just report status.
|
||||||
|
if command -v "$skill" >/dev/null 2>&1; then
|
||||||
|
: # installed — silent
|
||||||
|
else
|
||||||
|
info "CLI '$skill' not installed — install separately (npm/cargo/pipx)"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_skill() {
|
||||||
|
local skill="$1" type="$2"
|
||||||
|
case "$type" in
|
||||||
|
gstack)
|
||||||
|
if [ -e "$SKILLS_DIR/$skill" ]; then
|
||||||
|
mkdir -p "$DISABLED_DIR"
|
||||||
|
rm -rf "$DISABLED_DIR/gstack__$skill"
|
||||||
|
mv "$SKILLS_DIR/$skill" "$DISABLED_DIR/gstack__$skill"
|
||||||
|
ok "disabled: $skill"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
external|personal)
|
||||||
|
if [ -e "$SKILLS_DIR/$skill" ]; then
|
||||||
|
mkdir -p "$DISABLED_DIR"
|
||||||
|
rm -rf "${DISABLED_DIR:?}/${skill:?}"
|
||||||
|
mv "$SKILLS_DIR/$skill" "$DISABLED_DIR/$skill"
|
||||||
|
ok "disabled: $skill ($type)"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
plugin@*)
|
||||||
|
local marketplace="${type#plugin@}"
|
||||||
|
local key="${skill}@${marketplace}"
|
||||||
|
# Defensive check against PROTECTED_PLUGINS (always-on).
|
||||||
|
local p
|
||||||
|
for p in "${PROTECTED_PLUGINS[@]}"; do
|
||||||
|
if [ "$key" = "$p" ]; then
|
||||||
|
warn "refusing to disable protected plugin: $key"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "$(skill_status "$skill" "$type")" = "disabled" ]; then
|
||||||
|
: # already off
|
||||||
|
elif command -v claude >/dev/null 2>&1; then
|
||||||
|
if claude plugin disable "$key" 2>&1 | grep -qiE "disabled|already"; then
|
||||||
|
ok "disabled plugin: $key"
|
||||||
|
else
|
||||||
|
warn "could not disable plugin: $key"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
info "claude CLI not in PATH — manual: claude plugin disable $key"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
plugin)
|
||||||
|
info "plugin '$skill' — manual: claude plugin disable $skill@<marketplace>"
|
||||||
|
;;
|
||||||
|
mcp)
|
||||||
|
if [ "$skill" = "magic" ] && [ -x "$TOGGLE_EXTERNAL" ]; then
|
||||||
|
if bash "$TOGGLE_EXTERNAL" disable magic 2>&1 | grep -qE "disabled|already"; then
|
||||||
|
ok "disabled MCP: magic"
|
||||||
|
else
|
||||||
|
info "MCP 'magic' — manual disable failed"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
info "MCP '$skill' — manual: claude mcp remove $skill"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
cli)
|
||||||
|
: # never auto-uninstall CLIs
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Commands ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
cmd_list() {
|
||||||
|
printf "%-12s %s\n" "PROFILE" "DESCRIPTION"
|
||||||
|
printf "%-12s %s\n" "-------" "-----------"
|
||||||
|
local f name desc
|
||||||
|
for f in "$PROFILES_DIR"/*.profile; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
name="$(basename "$f" .profile)"
|
||||||
|
desc="$(profile_desc "$f")"
|
||||||
|
printf "%-12s %s\n" "$name" "${desc:--}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_show() {
|
||||||
|
local prof="$1"
|
||||||
|
local file="$PROFILES_DIR/$prof.profile"
|
||||||
|
[ -f "$file" ] || { err "Profile not found: $prof"; return 1; }
|
||||||
|
echo "Profile: $prof"
|
||||||
|
local desc
|
||||||
|
desc="$(profile_desc "$file")"
|
||||||
|
[ -n "$desc" ] && echo "Description: $desc"
|
||||||
|
echo ""
|
||||||
|
printf "%-25s %-30s %s\n" "ITEM" "TYPE" "STATUS"
|
||||||
|
printf "%-25s %-30s %s\n" "----" "----" "------"
|
||||||
|
local skill type status
|
||||||
|
while IFS=$'\t' read -r skill type; do
|
||||||
|
status="$(skill_status "$skill" "$type")"
|
||||||
|
printf "%-25s %-30s %s\n" "$skill" "$type" "$status"
|
||||||
|
done < <(read_profile "$prof")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_apply() {
|
||||||
|
local prof="$1"
|
||||||
|
info "Applying profile: $prof (additive — leaves other skills alone)"
|
||||||
|
local skill type
|
||||||
|
while IFS=$'\t' read -r skill type; do
|
||||||
|
enable_skill "$skill" "$type"
|
||||||
|
done < <(read_profile "$prof")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_set() {
|
||||||
|
local prof="$1"
|
||||||
|
info "Setting profile: $prof (exclusive — disables non-listed gstack skills + managed plugins)"
|
||||||
|
|
||||||
|
# Index of items in profile (skill names + plugin keys "name@marketplace").
|
||||||
|
local keep_file
|
||||||
|
keep_file="$(mktemp)"
|
||||||
|
# Skill names (col 1) — used to keep gstack skills.
|
||||||
|
read_profile "$prof" | cut -f1 | sort -u > "$keep_file"
|
||||||
|
# Plugin keys "name@marketplace" — used to keep managed plugins.
|
||||||
|
local plugin_keep_file
|
||||||
|
plugin_keep_file="$(mktemp)"
|
||||||
|
read_profile "$prof" | awk -F'\t' '$2 ~ /^plugin@/ { sub(/^plugin@/, "", $2); print $1"@"$2 }' | sort -u > "$plugin_keep_file"
|
||||||
|
|
||||||
|
# Disable gstack-origin skills not in profile.
|
||||||
|
local name
|
||||||
|
while read -r name; do
|
||||||
|
[ -n "$name" ] || continue
|
||||||
|
if ! grep -qx "$name" "$keep_file"; then
|
||||||
|
disable_skill "$name" gstack
|
||||||
|
fi
|
||||||
|
done < <(gstack_skills | sort -u)
|
||||||
|
|
||||||
|
# Disable managed plugins not in profile (PROTECTED_PLUGINS are excluded
|
||||||
|
# by disable_skill itself — belt and suspenders).
|
||||||
|
local p key plugin_name marketplace
|
||||||
|
for p in "${MANAGED_PLUGINS[@]}"; do
|
||||||
|
if ! grep -qx "$p" "$plugin_keep_file"; then
|
||||||
|
plugin_name="${p%@*}"
|
||||||
|
marketplace="${p#*@}"
|
||||||
|
disable_skill "$plugin_name" "plugin@${marketplace}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
rm -f "$keep_file" "$plugin_keep_file"
|
||||||
|
# Enable everything listed in the profile.
|
||||||
|
cmd_apply "$prof"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_reset() {
|
||||||
|
info "Re-enabling all gstack skills (move skills-disabled/gstack__* back)"
|
||||||
|
local entry name
|
||||||
|
if [ -d "$DISABLED_DIR" ]; then
|
||||||
|
for entry in "$DISABLED_DIR"/gstack__*; do
|
||||||
|
[ -e "$entry" ] || continue
|
||||||
|
name="$(basename "$entry" | sed 's/^gstack__//')"
|
||||||
|
rm -rf "${SKILLS_DIR:?}/${name:?}"
|
||||||
|
mv "$entry" "$SKILLS_DIR/$name"
|
||||||
|
ok "re-enabled: $name"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
info "Plugin state NOT touched. To re-enable a managed plugin disabled by 'set',"
|
||||||
|
info "run: claude plugin enable <name>@<marketplace> (or: profile apply <profile>)"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_current() {
|
||||||
|
# A profile is "active" only if (a) most of its skills are enabled AND
|
||||||
|
# (b) at least one non-listed gstack skill is currently disabled (i.e. a
|
||||||
|
# `set` has actually been applied). Without (b), every profile reports
|
||||||
|
# 100% trivially because the full gstack is on.
|
||||||
|
local disabled_count=0
|
||||||
|
if [ -d "$DISABLED_DIR" ]; then
|
||||||
|
disabled_count=$(find "$DISABLED_DIR" -maxdepth 1 -name 'gstack__*' 2>/dev/null | wc -l | tr -d ' ')
|
||||||
|
fi
|
||||||
|
if [ "$disabled_count" -eq 0 ]; then
|
||||||
|
echo "full (all gstack skills enabled — no profile set)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
# Pick the profile with the highest "available" ratio. An item counts as
|
||||||
|
# available when its status is "enabled" (skills, plugins, MCPs) or
|
||||||
|
# "installed" (CLIs). On ties, the profile with the larger total wins
|
||||||
|
# — superset profiles describe state more completely than subsets.
|
||||||
|
local f name total available score skill type status
|
||||||
|
local best="" best_score=0 best_total=0
|
||||||
|
for f in "$PROFILES_DIR"/*.profile; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
name="$(basename "$f" .profile)"
|
||||||
|
total=0; available=0
|
||||||
|
while IFS=$'\t' read -r skill type; do
|
||||||
|
total=$((total + 1))
|
||||||
|
status="$(skill_status "$skill" "$type")"
|
||||||
|
case "$status" in
|
||||||
|
enabled|installed) available=$((available + 1)) ;;
|
||||||
|
esac
|
||||||
|
done < <(read_profile "$name")
|
||||||
|
[ "$total" -eq 0 ] && continue
|
||||||
|
score=$((available * 100 / total))
|
||||||
|
if [ "$score" -gt "$best_score" ] || \
|
||||||
|
{ [ "$score" -eq "$best_score" ] && [ "$total" -gt "$best_total" ]; }; then
|
||||||
|
best_score="$score"
|
||||||
|
best_total="$total"
|
||||||
|
best="$name"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ -n "$best" ] && [ "$best_score" -ge 80 ]; then
|
||||||
|
echo "$best (${best_score}% match, $disabled_count gstack skills disabled)"
|
||||||
|
else
|
||||||
|
echo "custom (best guess: ${best:-none} ${best_score}%, $disabled_count gstack skills disabled)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_diff() {
|
||||||
|
local a="$1" b="$2"
|
||||||
|
local fa="$PROFILES_DIR/$a.profile" fb="$PROFILES_DIR/$b.profile"
|
||||||
|
[ -f "$fa" ] || { err "Profile not found: $a"; return 1; }
|
||||||
|
[ -f "$fb" ] || { err "Profile not found: $b"; return 1; }
|
||||||
|
local list_a list_b
|
||||||
|
list_a="$(mktemp)"; list_b="$(mktemp)"
|
||||||
|
read_profile "$a" | cut -f1 | sort -u > "$list_a"
|
||||||
|
read_profile "$b" | cut -f1 | sort -u > "$list_b"
|
||||||
|
echo "Only in $a:"; comm -23 "$list_a" "$list_b" | sed 's/^/ - /'
|
||||||
|
echo "Only in $b:"; comm -13 "$list_a" "$list_b" | sed 's/^/ + /'
|
||||||
|
echo "Common:" ; comm -12 "$list_a" "$list_b" | sed 's/^/ = /'
|
||||||
|
rm -f "$list_a" "$list_b"
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
profile.sh — partition Claude skills by purpose
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
profile list list all available profiles
|
||||||
|
profile show <name> show profile contents + per-skill status
|
||||||
|
profile current detect which profile is currently active
|
||||||
|
profile apply <name> enable skills in profile (additive)
|
||||||
|
profile set <name> enable only listed skills (disables rest of gstack)
|
||||||
|
profile reset re-enable all gstack skills
|
||||||
|
profile diff <a> <b> compare two profiles
|
||||||
|
|
||||||
|
PROFILES (in $PROFILES_DIR):
|
||||||
|
EOF
|
||||||
|
local f name desc
|
||||||
|
for f in "$PROFILES_DIR"/*.profile; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
name="$(basename "$f" .profile)"
|
||||||
|
desc="$(profile_desc "$f")"
|
||||||
|
printf " %-10s %s\n" "$name" "${desc:--}"
|
||||||
|
done
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
EXAMPLES:
|
||||||
|
bash lib/profile.sh list
|
||||||
|
bash lib/profile.sh show design
|
||||||
|
bash lib/profile.sh set design # only design skills active
|
||||||
|
bash lib/profile.sh apply qa # add QA skills on top
|
||||||
|
bash lib/profile.sh reset # restore everything
|
||||||
|
|
||||||
|
NOTE:
|
||||||
|
Plugin and MCP entries print advisory commands — they are NOT toggled
|
||||||
|
automatically. Run "claude plugin enable|disable" or "claude mcp add|remove"
|
||||||
|
yourself for those.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local cmd="${1:-}"
|
||||||
|
case "$cmd" in
|
||||||
|
list) cmd_list ;;
|
||||||
|
show) [ $# -ge 2 ] || { usage; exit 1; }; cmd_show "$2" ;;
|
||||||
|
current) cmd_current ;;
|
||||||
|
apply) [ $# -ge 2 ] || { usage; exit 1; }; cmd_apply "$2" ;;
|
||||||
|
set) [ $# -ge 2 ] || { usage; exit 1; }; cmd_set "$2" ;;
|
||||||
|
reset) cmd_reset ;;
|
||||||
|
diff) [ $# -ge 3 ] || { usage; exit 1; }; cmd_diff "$2" "$3" ;;
|
||||||
|
""|-h|--help|help) usage ;;
|
||||||
|
*) err "Unknown command: $cmd"; usage; exit 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
28
lib/profiles/audit.profile
Normal file
28
lib/profiles/audit.profile
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# DESC: Comprehensive audit — security + SEO + GEO + W3C + perf + health
|
||||||
|
# Activate when: doing a top-to-bottom audit pass on an existing project.
|
||||||
|
# Wider than `seo` (adds security + dependency review).
|
||||||
|
|
||||||
|
# Security
|
||||||
|
cso
|
||||||
|
harden personal
|
||||||
|
analyze personal
|
||||||
|
|
||||||
|
# SEO / GEO / web standards
|
||||||
|
seo personal
|
||||||
|
geo personal
|
||||||
|
validate personal
|
||||||
|
|
||||||
|
# Code + perf health
|
||||||
|
health
|
||||||
|
benchmark
|
||||||
|
review
|
||||||
|
|
||||||
|
# Browser tooling for live audits
|
||||||
|
browse
|
||||||
|
open-gstack-browser
|
||||||
|
|
||||||
|
# Plugin: PR review toolkit (audit context for diffs)
|
||||||
|
pr-review-toolkit plugin@claude-code-plugins
|
||||||
|
|
||||||
|
# CLI: graphify for code structure
|
||||||
|
graphify cli
|
||||||
43
lib/profiles/backend.profile
Normal file
43
lib/profiles/backend.profile
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# DESC: Backend / API / system dev — no design, no SEO, focused on logic
|
||||||
|
# Activate when: building backend services, APIs, CLIs, libraries, system
|
||||||
|
# code, data pipelines. UI/visual work is out of scope. SEO/GEO out of scope.
|
||||||
|
|
||||||
|
# Code work — primary
|
||||||
|
feat personal
|
||||||
|
ship-feature personal
|
||||||
|
hotfix personal
|
||||||
|
bugfix personal
|
||||||
|
investigate
|
||||||
|
refactor personal
|
||||||
|
code-clean personal
|
||||||
|
commit-change personal
|
||||||
|
analyze personal
|
||||||
|
|
||||||
|
# Ship + review + land
|
||||||
|
ship
|
||||||
|
review
|
||||||
|
checkpoint
|
||||||
|
land-and-deploy
|
||||||
|
|
||||||
|
# Second opinion for hard problems
|
||||||
|
codex
|
||||||
|
|
||||||
|
# Security + health (always relevant for backend)
|
||||||
|
cso
|
||||||
|
health
|
||||||
|
|
||||||
|
# Session hygiene
|
||||||
|
careful
|
||||||
|
freeze
|
||||||
|
unfreeze
|
||||||
|
guard
|
||||||
|
learn
|
||||||
|
retro
|
||||||
|
|
||||||
|
# Plugin: PR review toolkit (pre-merge audit)
|
||||||
|
pr-review-toolkit plugin@claude-code-plugins
|
||||||
|
|
||||||
|
# CLIs (advisory)
|
||||||
|
ctx7 cli
|
||||||
|
gsd cli
|
||||||
|
graphify cli
|
||||||
30
lib/profiles/design.profile
Normal file
30
lib/profiles/design.profile
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# DESC: Design work — visual QA, design systems, mockups, polish
|
||||||
|
# Activate when: building/reviewing UI, picking aesthetics, design tokens.
|
||||||
|
# Companion CLIs (advisory): graphify (visual structure).
|
||||||
|
|
||||||
|
# Core design skills (gstack)
|
||||||
|
design-shotgun
|
||||||
|
design-review
|
||||||
|
design-consultation
|
||||||
|
design-html
|
||||||
|
plan-design-review
|
||||||
|
|
||||||
|
# Browser tooling — design-review and design-shotgun rely on it
|
||||||
|
browse
|
||||||
|
open-gstack-browser
|
||||||
|
setup-browser-cookies
|
||||||
|
|
||||||
|
# Plan-mode review companion (taste decisions before code)
|
||||||
|
plan-ceo-review
|
||||||
|
|
||||||
|
# External: Emil Kowalski's polish philosophy
|
||||||
|
emil-design-eng external
|
||||||
|
|
||||||
|
# Plugin (auto-toggle)
|
||||||
|
ui-ux-pro-max plugin@ui-ux-pro-max-skill
|
||||||
|
|
||||||
|
# MCP — auto-toggle via lib/toggle-external.sh (needs MAGIC_API_KEY in .env)
|
||||||
|
magic mcp
|
||||||
|
|
||||||
|
# CLIs (advisory only — installed/not-installed)
|
||||||
|
graphify cli
|
||||||
24
lib/profiles/dev.profile
Normal file
24
lib/profiles/dev.profile
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# DESC: Daily code work — small features, fixes, refactor, ship (any stack)
|
||||||
|
# Activate when: implementing features, debugging, shipping PRs without a
|
||||||
|
# specific frontend or backend bias. Lighter than `backend` (no security audit
|
||||||
|
# baggage) and lighter than `web` (no design pipeline).
|
||||||
|
|
||||||
|
# Implementation
|
||||||
|
feat personal
|
||||||
|
ship-feature personal
|
||||||
|
ship
|
||||||
|
|
||||||
|
# Bug fixing
|
||||||
|
hotfix personal
|
||||||
|
bugfix personal
|
||||||
|
investigate
|
||||||
|
|
||||||
|
# Code health
|
||||||
|
review
|
||||||
|
refactor personal
|
||||||
|
code-clean personal
|
||||||
|
commit-change personal
|
||||||
|
|
||||||
|
# Session hygiene
|
||||||
|
checkpoint
|
||||||
|
land-and-deploy
|
||||||
7
lib/profiles/minimal.profile
Normal file
7
lib/profiles/minimal.profile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# DESC: Strip all gstack skills (only essential personal skills + plugins remain)
|
||||||
|
# Activate when: you want a quiet session with no gstack noise.
|
||||||
|
#
|
||||||
|
# Empty by design — `profile set minimal` disables every gstack skill,
|
||||||
|
# leaves personal skills (analyze, doc, init-project, onboard, plugin-check,
|
||||||
|
# refactor, seo, validate, harden, etc.) untouched, and makes no plugin/MCP
|
||||||
|
# changes. To revert: `profile reset`.
|
||||||
11
lib/profiles/qa.profile
Normal file
11
lib/profiles/qa.profile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# DESC: QA + testing — site checks, perf, canary, validation
|
||||||
|
# Activate when: running QA, checking deployed pages, perf regressions.
|
||||||
|
|
||||||
|
qa
|
||||||
|
qa-only
|
||||||
|
browse
|
||||||
|
benchmark
|
||||||
|
canary
|
||||||
|
validate personal
|
||||||
|
open-gstack-browser
|
||||||
|
setup-browser-cookies
|
||||||
25
lib/profiles/seo.profile
Normal file
25
lib/profiles/seo.profile
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# DESC: SEO + GEO + W3C audit — search/AI indexability + standards
|
||||||
|
# Activate when: auditing a site for Google/Bing indexability, AI search
|
||||||
|
# (ChatGPT/Perplexity/Claude/Gemini), W3C HTML/CSS validity, WCAG a11y.
|
||||||
|
# No design skills, no dev skills — pure audit pipeline.
|
||||||
|
|
||||||
|
# SEO + GEO audits (personal)
|
||||||
|
seo personal
|
||||||
|
geo personal
|
||||||
|
|
||||||
|
# W3C HTML/CSS validity + WCAG a11y
|
||||||
|
validate personal
|
||||||
|
|
||||||
|
# Web hardening (HSTS, CSP, redirects — affects ranking signals)
|
||||||
|
harden personal
|
||||||
|
|
||||||
|
# Code analysis (read structure for audit)
|
||||||
|
analyze personal
|
||||||
|
|
||||||
|
# Browser tooling — needed for live-page audits
|
||||||
|
browse
|
||||||
|
open-gstack-browser
|
||||||
|
|
||||||
|
# Health check + benchmark — Core Web Vitals, perf signals
|
||||||
|
health # gstack
|
||||||
|
benchmark # gstack
|
||||||
53
lib/profiles/web-full.profile
Normal file
53
lib/profiles/web-full.profile
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# DESC: Production website — design + dev + SEO + GEO + W3C + perf
|
||||||
|
# Activate when: shipping a public website end-to-end. Combines `web`
|
||||||
|
# and `seo` profiles into a single set so the same session can build,
|
||||||
|
# polish, audit, and verify.
|
||||||
|
|
||||||
|
# === Design ===========================================================
|
||||||
|
design-shotgun
|
||||||
|
design-review
|
||||||
|
design-consultation
|
||||||
|
design-html
|
||||||
|
plan-design-review
|
||||||
|
|
||||||
|
# === Browser + dogfooding =============================================
|
||||||
|
browse
|
||||||
|
open-gstack-browser
|
||||||
|
setup-browser-cookies
|
||||||
|
|
||||||
|
# === Plan-mode reviews ================================================
|
||||||
|
plan-ceo-review
|
||||||
|
plan-eng-review
|
||||||
|
|
||||||
|
# === Code work ========================================================
|
||||||
|
feat personal
|
||||||
|
ship-feature personal
|
||||||
|
hotfix personal
|
||||||
|
bugfix personal
|
||||||
|
ship
|
||||||
|
review
|
||||||
|
checkpoint
|
||||||
|
commit-change personal
|
||||||
|
refactor personal
|
||||||
|
|
||||||
|
# === SEO / GEO / standards ===========================================
|
||||||
|
seo personal
|
||||||
|
geo personal
|
||||||
|
validate personal
|
||||||
|
harden personal
|
||||||
|
analyze personal
|
||||||
|
|
||||||
|
# === Perf + canary ===================================================
|
||||||
|
health
|
||||||
|
benchmark
|
||||||
|
canary
|
||||||
|
qa-only
|
||||||
|
|
||||||
|
# === External + plugin + MCP =========================================
|
||||||
|
emil-design-eng external
|
||||||
|
ui-ux-pro-max plugin@ui-ux-pro-max-skill
|
||||||
|
magic mcp
|
||||||
|
|
||||||
|
# === CLIs (advisory) =================================================
|
||||||
|
ctx7 cli
|
||||||
|
graphify cli
|
||||||
46
lib/profiles/web.profile
Normal file
46
lib/profiles/web.profile
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# DESC: Public website work — frontend + content + light dev
|
||||||
|
# Activate when: building/iterating a public-facing website (landing,
|
||||||
|
# portfolio, marketing, blog) where SEO is NOT the immediate focus.
|
||||||
|
# For SEO/GEO audit on top, use web-full or apply seo afterwards.
|
||||||
|
|
||||||
|
# Design skills (gstack) — full design pipeline
|
||||||
|
design-shotgun
|
||||||
|
design-review
|
||||||
|
design-consultation
|
||||||
|
design-html
|
||||||
|
plan-design-review
|
||||||
|
|
||||||
|
# Browser tooling for design + dogfooding
|
||||||
|
browse
|
||||||
|
open-gstack-browser
|
||||||
|
setup-browser-cookies
|
||||||
|
|
||||||
|
# Plan-mode reviews relevant to web work
|
||||||
|
plan-ceo-review
|
||||||
|
plan-eng-review
|
||||||
|
|
||||||
|
# Code work skills — needed to actually build the site
|
||||||
|
feat personal
|
||||||
|
ship-feature personal
|
||||||
|
hotfix personal
|
||||||
|
ship # gstack
|
||||||
|
review # gstack
|
||||||
|
checkpoint # gstack
|
||||||
|
commit-change personal
|
||||||
|
refactor personal
|
||||||
|
|
||||||
|
# Validation companion (basic W3C/a11y check during build)
|
||||||
|
validate personal
|
||||||
|
|
||||||
|
# External: Emil Kowalski's polish philosophy
|
||||||
|
emil-design-eng external
|
||||||
|
|
||||||
|
# Plugin: UI/UX intelligence (auto-toggle)
|
||||||
|
ui-ux-pro-max plugin@ui-ux-pro-max-skill
|
||||||
|
|
||||||
|
# MCP: 21st-dev Magic component generator
|
||||||
|
magic mcp
|
||||||
|
|
||||||
|
# CLI: ctx7 (doc lookup for fast-evolving libs like Next.js)
|
||||||
|
ctx7 cli
|
||||||
|
graphify cli
|
||||||
@ -22,6 +22,12 @@
|
|||||||
# darwin-skill — single symlink → ~/.agents/skills/darwin-skill
|
# darwin-skill — single symlink → ~/.agents/skills/darwin-skill
|
||||||
# find-skills — single symlink → ~/.agents/skills/find-skills
|
# find-skills — single symlink → ~/.agents/skills/find-skills
|
||||||
# magic — 21st-dev Magic MCP server (API key in .env)
|
# magic — 21st-dev Magic MCP server (API key in .env)
|
||||||
|
#
|
||||||
|
# For fine-grained activation (only design skills, only qa skills, only
|
||||||
|
# audit skills, etc.) instead of all-or-nothing gstack toggling, use:
|
||||||
|
# bash lib/profile.sh list
|
||||||
|
# bash lib/profile.sh set <design|dev|qa|audit|minimal>
|
||||||
|
# bash lib/profile.sh reset
|
||||||
# ============================================================
|
# ============================================================
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|||||||
119
skills/profile/SKILL.md
Normal file
119
skills/profile/SKILL.md
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
---
|
||||||
|
name: profile
|
||||||
|
description: |
|
||||||
|
Partition Claude skills by purpose: design, dev, qa, audit, minimal.
|
||||||
|
Toggles symlinks between skills/ and skills-disabled/ to keep only
|
||||||
|
the skills relevant to the current kind of work.
|
||||||
|
Trigger: "profile", "skill profile", "design profile", "qa profile",
|
||||||
|
"switch to design", "set profile", "active profile", "quel profil",
|
||||||
|
"profil design", "active les skills design", "désactive gstack",
|
||||||
|
"réduire le bruit gstack".
|
||||||
|
argument-hint: list | show <name> | current | apply <name> | set <name> | reset | diff <a> <b>
|
||||||
|
disable-model-invocation: false
|
||||||
|
allowed-tools:
|
||||||
|
- Bash
|
||||||
|
- Read
|
||||||
|
---
|
||||||
|
|
||||||
|
# profile
|
||||||
|
|
||||||
|
Activate a curated subset of skills for a specific kind of work — instead of
|
||||||
|
carrying every gstack + personal skill in every session.
|
||||||
|
|
||||||
|
## When to invoke
|
||||||
|
|
||||||
|
- User asks to switch profile (`set design`, `profile dev`, `quel profil actif`).
|
||||||
|
- User wants to see what's in a profile (`profile show qa`).
|
||||||
|
- User wants to compare profiles (`profile diff design qa`).
|
||||||
|
- User asks to "reduce gstack noise" or "only design skills".
|
||||||
|
|
||||||
|
## Profiles available
|
||||||
|
|
||||||
|
| Profile | Use case |
|
||||||
|
|------------|----------|
|
||||||
|
| `web` | Public website work — frontend + content + light dev |
|
||||||
|
| `seo` | SEO + GEO + W3C audit — search/AI indexability + standards |
|
||||||
|
| `web-full` | Production website end-to-end — `web` + `seo` combined |
|
||||||
|
| `backend` | Backend / API / system dev — no design, no SEO |
|
||||||
|
| `design` | Visual QA, design systems, mockups, polish |
|
||||||
|
| `dev` | Daily code work — features, fixes, refactor, ship (any stack) |
|
||||||
|
| `qa` | Site testing, perf, canary, validation |
|
||||||
|
| `audit` | Comprehensive audit — security + SEO + GEO + W3C + perf + health |
|
||||||
|
| `minimal` | Strip all gstack skills (quiet session) |
|
||||||
|
|
||||||
|
## Mechanism
|
||||||
|
|
||||||
|
Each profile is a plain-text file under `lib/profiles/<name>.profile` that
|
||||||
|
lists items + types:
|
||||||
|
|
||||||
|
| Type | Toggle mechanism |
|
||||||
|
|-------------------------|------------------|
|
||||||
|
| `gstack` | symlink move skills/ ↔ skills-disabled/gstack__\<name\> |
|
||||||
|
| `personal` | symlink move skills/ ↔ skills-disabled/\<name\> (no prefix) |
|
||||||
|
| `external` | symlink move skills/ ↔ skills-disabled/\<name\> |
|
||||||
|
| `plugin@<marketplace>` | `claude plugin enable\|disable <name>@<marketplace>` (auto) |
|
||||||
|
| `mcp` (known: magic) | delegate to `lib/toggle-external.sh` (uses `.env`) |
|
||||||
|
| `mcp` (other) | advisory — prints manual `claude mcp add …` command |
|
||||||
|
| `cli` | advisory only — reports installed/not-installed |
|
||||||
|
|
||||||
|
**Always-on plugins** (`caveman`, `security-guidance`, `superpowers`) are
|
||||||
|
protected — `set` will refuse to disable them even if the profile omits them.
|
||||||
|
**Managed plugins** that `set` may disable when not in profile:
|
||||||
|
`ui-ux-pro-max@ui-ux-pro-max-skill`, `plugin-dev@claude-code-plugins`,
|
||||||
|
`pr-review-toolkit@claude-code-plugins`. Other plugins are never auto-toggled.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List available profiles
|
||||||
|
bash "$HOME/.claude/lib/profile.sh" list
|
||||||
|
|
||||||
|
# Show profile contents + per-skill status
|
||||||
|
bash "$HOME/.claude/lib/profile.sh" show <name>
|
||||||
|
|
||||||
|
# Detect which profile is currently active
|
||||||
|
bash "$HOME/.claude/lib/profile.sh" current
|
||||||
|
|
||||||
|
# Enable skills in profile (additive — keeps others enabled)
|
||||||
|
bash "$HOME/.claude/lib/profile.sh" apply <name>
|
||||||
|
|
||||||
|
# Enable only skills in profile (disables non-listed gstack skills)
|
||||||
|
bash "$HOME/.claude/lib/profile.sh" set <name>
|
||||||
|
|
||||||
|
# Re-enable every gstack skill (undo any set/apply)
|
||||||
|
bash "$HOME/.claude/lib/profile.sh" reset
|
||||||
|
|
||||||
|
# Compare two profiles
|
||||||
|
bash "$HOME/.claude/lib/profile.sh" diff <a> <b>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Execution
|
||||||
|
|
||||||
|
Run `lib/profile.sh` with the user's arguments. If user passed nothing, default
|
||||||
|
to `list`. If user named a profile without a verb (e.g. "profile design"),
|
||||||
|
treat it as `set <name>` — but confirm first because `set` disables other gstack
|
||||||
|
skills.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash "$HOME/.claude/lib/profile.sh" $ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output policy
|
||||||
|
|
||||||
|
- After `set` / `apply` / `reset`: show the count of skills moved + tell the
|
||||||
|
user to start a new Claude session to pick up the changes (Claude scans
|
||||||
|
`skills/` at session start).
|
||||||
|
- After `current`: report the active profile + match percentage.
|
||||||
|
- After `show`: render the table directly — no extra commentary unless the user
|
||||||
|
asks.
|
||||||
|
|
||||||
|
## Tradeoffs to mention if asked
|
||||||
|
|
||||||
|
- gstack skills still depend on `~/.claude/skills/gstack/bin/` for telemetry,
|
||||||
|
update-check, learnings — script doesn't touch that infra. Disabled skills
|
||||||
|
are just hidden from Claude Code's scanner; the gstack repo stays installed.
|
||||||
|
- Profile changes do NOT toggle Claude Code plugins (ui-ux-pro-max, etc.) or
|
||||||
|
MCP servers — those are advisory only. The user runs `claude plugin
|
||||||
|
enable|disable` and `claude mcp add|remove` manually.
|
||||||
|
- `set` is destructive in the sense that it disables non-listed gstack skills.
|
||||||
|
Use `apply` if the user wants additive behavior.
|
||||||
Loading…
Reference in New Issue
Block a user