feat(profile): add gstack on|off verb to lib/profile.sh

Centralize gstack toggling in the `profile` command without losing the
active-profile label.

  - `gstack on`  re-enables ALL parked gstack skills (moves
    skills-disabled/gstack__* back) but does NOT touch .active-profile,
    so the user layers full gstack on top of their current profile and
    the statusline label is preserved. Unlike `reset`, which clears the
    label to "none".
  - `gstack off` disables gstack skills not listed in the active profile;
    errors cleanly when no profile is active (needs one to know what to
    keep).

Refactor (behavior-preserving): extract three shared helpers
`enable_all_gstack`, `disable_gstack_not_in`, `parked_gstack_count` and
rewire `cmd_reset` + `cmd_set` to reuse them instead of duplicating the
symlink-toggle loops. Wire `gstack` into main() dispatch, usage(), and the
header usage block.

Docs: SKILL.md argument-hint, examples, and output-policy updated. The
generic `make profile cmd="gstack on"` target already covers Make usage.

Verified: shellcheck CLEAN, `bash -n` OK, 6-case test (help, bad-action,
off-with-no-profile, on, off-trim, on-cycle) with final assertion that the
live symlink state was restored exactly to its pre-test value.

Memory: capitalize BDR-018 (decision), LRN-024 (DRY helper-extraction
pattern), BLK-007 (6 gstack source skills ios-*/spec unlinked post
submodule bump — open follow-up), EVAL-002 (self-eval, false "full.profile
bug" flag corrected pre-edit). Backfill index drift: BDR-017, BLK-005/006.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bastien Chanot 2026-06-02 18:31:48 +02:00
parent 0d9f3d41eb
commit da4e6b9590
8 changed files with 174 additions and 35 deletions

View File

@ -24,6 +24,9 @@ rules:
| BLK-002 | 2026-04-23 | `rmdir` denied in sandbox on empty directory | resolved |
| BLK-003 | 2026-05-12 | `scripts/screenshot.mjs` hardcoded macOS path blocks PNG cards on Linux | upstream |
| BLK-004 | 2026-05-20 | `/ship-feature` wrapper at `~/.claude/commands/` points to deleted agent files post-refactor | resolved |
| BLK-005 | 2026-05-21 | gstack submodule rename (checkpoint→context-save) breaks profile entries | resolved |
| BLK-006 | 2026-05-21 | `profile.sh current` false-negative via `~/.claude` symlink (`cd` not `cd -P`) | resolved |
| BLK-007 | 2026-06-02 | 6 gstack source skills (ios-*, spec) unlinked post-bump — invisible to profiles + `gstack on` | open |
---
@ -82,3 +85,11 @@ rules:
- **Real cause**: `lib/profile.sh:43` set `REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"`. Default bash `cd` preserves symlinks (logical pathname mode, `set -P` off). When the script is invoked via the `~/.claude/lib/profile.sh` symlink (link.sh wires `~/.claude/lib -> <repo>/lib`), `$BASH_SOURCE[0]` is the symlinked path, `dirname` returns `~/.claude/lib`, `cd ..` lands at `~/.claude`, and `pwd` returns the logical path `/home/bchanot-ubuntu/.claude`. `$SKILLS_DIR="$REPO/skills"` still works because `~/.claude/skills` happens to be a symlink to the repo's `skills/`. But `$DISABLED_DIR="$REPO/skills-disabled"` resolves to `~/.claude/skills-disabled` — a real sibling directory created at some earlier point containing only 2 stale npx-skill symlinks (`darwin-skill`, `find-skills`). `cmd_current` scans this near-empty dir, finds 0 `gstack__*` entries, returns the "none" sentinel.
- **Solution**: `REPO="$(cd -P "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"` (commit `a4558ee`). `-P` forces physical-path resolution so `$REPO` is always the real repo path regardless of how the script is invoked. Verify: `bash "$HOME/.claude/lib/profile.sh" current` now returns `full (100% match, 14 gstack skills disabled)`.
- **Status**: resolved. Follow-up: `~/.claude/skills-disabled/` (real dir with only `darwin-skill`/`find-skills` symlinks) is orphaned — these npx skills are already symlinked into `<repo>/skills/` by link.sh, so the disabled-side copies serve no purpose. Could be deleted to remove confusion, but harmless as-is.
## BLK-007 — 6 gstack source skills (ios-*, spec) unlinked — invisible to profile system + `gstack on`
- **Date**: 2026-06-02
- **Friction**: `skills-external/gstack/` has 53 source skills; 6 (`ios-clean`, `ios-design-review`, `ios-fix`, `ios-qa`, `ios-sync`, `spec`) exist ONLY as source — NOT symlinked into `skills/` (enabled) nor `skills-disabled/gstack__*` (parked). So invisible to Claude AND untouched by `reset`/`gstack on` (both operate on parked `gstack__*` only). Surfaced while adding `gstack on|off`: `comm` of gstack source vs `full.profile`.
- **Real cause**: gstack submodule bump added new skills; gstack's own `./setup` (source of truth for per-skill symlinks per link.sh) not re-run → symlinks never created. Same lifecycle gap class as [[toggle-external-source-only-state]] (LRN-007). NOT a `full.profile` bug — full curated by design (BDR-017 caveat: "full excludes rarely-used gstack skills"). Initial "full omits ios = bug" flag was WRONG, self-corrected (see EVAL-002).
- **Solution**: re-run gstack setup to link new skills, then reconcile profiles (decide if iOS skills belong in web/dev profiles — likely NOT). Per [[gstack-rename-profile-audit]] (LRN-022): diff `skills-external/gstack/` vs `lib/profiles/*.profile` after every submodule bump. NOT auto-fixed — gstack installer domain + iOS-in-web judgment call.
- **Status**: open. Low impact (skills unused today, never linked). Next: run gstack `./setup`, audit profile membership.

View File

@ -38,6 +38,8 @@ rules:
| BDR-014 | 2026-05-11 | Personal SKILL.md descriptions: "Use when [triggers]…" pattern + 1024-char spec limit | accepted |
| BDR-015 | 2026-05-12 | Exclude broken gstack symlinks from /darwin-skill scope (external ownership) | accepted |
| BDR-016 | 2026-05-15 | doc-syncer: README AUTO+unconditional, DEPLOY.md prod-only + 14-section VPS template | accepted |
| BDR-017 | 2026-05-18 | `full` profile = web-full + plan + dev superset for /init-project MVP | accepted |
| BDR-018 | 2026-06-02 | `profile gstack on/off` verb — toggle gstack keeping active-profile label | accepted |
---
@ -321,3 +323,17 @@ rules:
- `full` excludes a few rarely-used gstack skills (devex-review, pair-agent, gstack-upgrade, skills-perso). `set full` will disable those; user can `apply <profile>` after to add back.
- Sentinel rename "full" → "none" is breaking for any tooling that grepped `cmd_current` output for literal "full". No known consumers in this repo.
- **Reference**: commit message references `lib/profiles/full.profile` (new), `lib/profile.sh:421` sentinel, `skills/profile/SKILL.md` table row. Linked to [[profile-sentinel-collision]] (LRN-020).
---
## BDR-018 — `profile gstack on|off` verb keeps active-profile label
- **Date**: 2026-06-02
- **Status**: accepted
- **Decision**: New `cmd_gstack()` in `lib/profile.sh`. `gstack on` = re-enable all parked gstack (move `skills-disabled/gstack__*` back), DON'T touch `.active-profile`. `gstack off` = disable gstack skills not in active profile (errors if active=none). Wired into `main()` dispatch + `usage()` + header block + `skills/profile/SKILL.md` (argument-hint + examples + output-policy).
- **Why**: User wanted central command for "enable all gstack" + "disable gstack not needed by profile". Both ops existed (`reset`, `set`) but `reset` clobbers `.active-profile` to "none" — loses profile context in statusline. New verb does same skill-toggle WITHOUT clearing label, so user layers full gstack on top of current profile (e.g. `dev`) and statusline still reads `dev`.
- **Alternatives rejected**:
- 3 new profiles (current+gstack, current+gsd, current+gsd+gstack) — rejected: `gsd` = standalone CLI (not profile-toggleable, always-on, 0 passive token), so 2 of 3 meaningless. `full` already = current+gstack+gsd advisory. `apply` already additive.
- Just document `reset`/`set` — rejected: user wanted clearer centralized verb + label preservation.
- **Impl note**: extracted 3 shared helpers (`enable_all_gstack`, `disable_gstack_not_in`, `parked_gstack_count`); `cmd_reset`+`cmd_set` refactored to reuse (behavior preserved exact, verified by test). See [[dry-helper-extract-sibling-command]] (LRN-024).
- **Reference**: `lib/profile.sh` cmd_gstack + helpers, `skills/profile/SKILL.md`. Linked to [[full-profile-superset-init-project]] (BDR-017), [[gstack-source-only-skills-unlinked]] (BLK-007).

View File

@ -22,6 +22,7 @@ rules:
| ID | Date | Output | Action |
|----|------|--------|--------|
| EVAL-001 | 2026-04-23 | `.claude/` restructure plan (ship-feature STEP 2) | keep |
| EVAL-002 | 2026-06-02 | `profile gstack on/off` verb implementation | keep |
---
@ -32,3 +33,13 @@ rules:
- **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.
---
## EVAL-002 — `profile gstack on|off` verb implementation
- **Date**: 2026-06-02
- **Output**: `cmd_gstack()` + 3 extracted helpers in `lib/profile.sh`; `cmd_reset`/`cmd_set` refactored to reuse; `skills/profile/SKILL.md` doc updated.
- **Method**: shellcheck 0.10.0 (CLEAN) + `bash -n`; 6-case live test (help; bad-action exit 1; `off` with active=none → exit 1 zero-mutation; `on` restores 14 + label `full` preserved NOT cleared; `off` trim; `on` cycle) with saved manifest + final assertion final-state == original (PASS, live env untouched).
- **Anomalies**: (1) Initial flag "full.profile omits ios/spec = bug" WRONG — full curated by design, confirmed by BDR-017 caveat. Self-corrected BEFORE any edit, no bad change shipped. Lesson: verify profile INTENT vs source completeness before calling omission a bug. (2) Surfaced real source-only gap → BLK-007 (open).
- **Action**: keep — verb works, tested, documented; false bug-flag caught pre-edit.

View File

@ -129,3 +129,11 @@ rules:
- `/hotfix` follow-up — `bash "$HOME/.claude/lib/profile.sh" current` falsely reported `none (all gstack skills enabled — no profile set)` even with profile applied + 14 gstack__* entries in repo's `skills-disabled/`. Root cause: `lib/profile.sh:43` used `cd "$(dirname $BASH_SOURCE)/.."` — default bash `cd` preserves symlinks, so `$REPO` resolved to `/home/bchanot-ubuntu/.claude` (symlink dir) instead of real repo path. `$DISABLED_DIR` then pointed at near-empty `~/.claude/skills-disabled/` (2 stale npx symlinks only). Fixed by adding `-P` to `cd` (commit `a4558ee`). `cmd_current` now correctly reports `full (100% match, 14 gstack skills disabled)`.
- BLK-006 capitalized — `cmd_current` false-negative when invoked via `~/.claude/lib/profile.sh` symlink; status: resolved.
- LRN-023 capitalized — `$REPO="$(cd -P "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"` mandatory pattern for any script meant to be invoked via a symlink into the install location.
## 2026-06-02
- Added `profile gstack on|off` verb to `lib/profile.sh`. `on` = re-enable all parked gstack keeping `.active-profile` label intact (vs `reset` which clears to "none"); `off` = disable gstack not in active profile (errors if none). User wanted centralized toggle without losing profile context.
- Extracted 3 helpers (`enable_all_gstack`/`disable_gstack_not_in`/`parked_gstack_count`); refactored `cmd_reset`+`cmd_set` to reuse — behavior preserved, 6-case test + exact state-restore assertion PASS, shellcheck CLEAN. Doc: SKILL.md argument-hint + examples + output-policy. Makefile generic `make profile cmd="gstack on"` already covers it.
- Corrected own false flag: full.profile omitting ios-*/spec is curation by design (BDR-017 caveat), NOT a bug — caught before any edit. Surfaced real gap: 6 gstack source skills unlinked post-submodule-bump → BLK-007 (open, gstack ./setup domain, not auto-fixed).
- Backfilled index drift: decisions (BDR-017), blockers (BLK-005/006).
- BDR-018 + LRN-024 + BLK-007 + EVAL-002 capitalized.

View File

@ -40,6 +40,7 @@ rules:
| LRN-017 | 2026-05-12 | Thin-dispatcher SKILL.md round-1 win = fallback + frontmatter triggers (+15 to +30) | any `/darwin-skill` round-1 on a dispatcher SKILL.md |
| LRN-018 | 2026-05-12 | Darwin eval subagents drift on total math — recompute in main thread | any subagent-driven SKILL.md rescore |
| LRN-019 | 2026-05-15 | Deployable-project doc split: README dev-quickstart + DEPLOY 14-section prod-VPS topology | any onboard/doc-syncer/scaffold producing docs for a deployable project |
| LRN-024 | 2026-06-02 | New sibling command sharing logic → extract helper + refactor existing caller, never copy-paste; assert pre/post state equality | adding a subcommand/branch reusing logic inline in a peer command |
---
@ -359,3 +360,14 @@ rules:
- Lint via: `grep -n 'cd "$(dirname "${BASH_SOURCE' <script>` — every match should also contain `cd -P` (or be followed by an explicit `realpath` call).
- **Cost when missed**: state lands in two parallel directories. Reads from one, writes from the other. False-negative status reports. Worst case: silent data loss when one dir is cleaned by a tool that thinks the other is canonical.
- **Reference**: BLK-006, commit `a4558ee`. Linked to [[gstack-rename-profile-audit]] (LRN-022) — both bugs surfaced from the same `/profile set full` invocation, but root causes are independent.
---
## LRN-024 — New sibling command sharing logic → extract helper + refactor caller, never copy-paste
- **Date**: 2026-06-02
- **Pattern**: New `gstack on|off` needed same skill-toggle loops already inline in `cmd_reset` (enable-all-parked) + `cmd_set` (disable-not-in-profile). Copy-paste = divergence risk (gstack__ prefix logic, mktemp keep-file). Instead extracted `enable_all_gstack()` + `disable_gstack_not_in()` + `parked_gstack_count()`; refactored `cmd_reset`/`cmd_set` to call them, then added `cmd_gstack` as 3rd caller. Behavior preserved exact (code MOVED not changed).
- **Why matters**: CLAUDE.md "more elegant solution exists?" — slight scope expansion (touch existing fns) beats duplication. Risk contained by test: snapshot original symlink state → run on/off cycle → re-park exact original → assert final == original. PASS, live env untouched.
- **Key trick**: when mutating shared resource (symlinks, files, db), verify refactor by asserting `final_state == original_state` after a round-trip, not just "command exited 0".
- **Applies to**: any new subcommand/branch reusing logic inline in a peer command — extract first, refactor existing caller, then add new caller. shellcheck after.
- **Reference**: BDR-018, `lib/profile.sh` enable_all_gstack/disable_gstack_not_in/parked_gstack_count. Linked to [[gstack-on-off-verb]] (BDR-018).

View File

@ -1,5 +1,18 @@
# TODO
## profile.sh — verbe `gstack on|off`
- [x] Extraire helper `enable_all_gstack()` (boucle de cmd_reset) — anti-duplication
- [x] Extraire helper `disable_gstack_not_in(prof)` (boucle gstack de cmd_set) — anti-duplication
- [x] Extraire helper `parked_gstack_count()` (réutilise pattern cmd_current)
- [x] Refactor cmd_reset + cmd_set pour utiliser les helpers (comportement préservé)
- [x] `cmd_gstack()` : `on` = enable tout gstack (garde label active-profile), `off` = disable gstack hors profil actif
- [x] Wire main() dispatch `gstack)` + usage() + bloc header
- [x] Doc : SKILL.md argument-hint + exemples + output-policy (Makefile générique suffit)
- [x] shellcheck propre + tests (help/bad-action/none-error/on/off cycle) — état live restauré exact
- [x] Investigué "fix" full.profile : PAS un bug — curation par design (BDR-017 caveat). Aucun fix code.
- [ ] FOLLOW-UP (BLK-007) : 6 skills gstack source (ios-*, spec) unlinkés post-bump → re-run gstack ./setup + reconcilier profils (iOS dans profils web ? probablement non)
- [x] Capitalize : BDR-018, LRN-024, BLK-007, EVAL-002, journal 2026-06-02 + backfill index (BDR-017, BLK-005/006)
## README.md overhaul
- [x] Plan
- [x] Corriger section install ctx7 (retirer MCP, clarifier CLI + API key)

View File

@ -26,6 +26,7 @@
# 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 gstack on|off toggle gstack, keeping active-profile label
# profile.sh diff <a> <b> compare two profiles
#
# Profile file format (lib/profiles/<name>.profile):
@ -321,6 +322,43 @@ disable_skill() {
esac
}
# ── Shared gstack operations ──────────────────────────────
# Re-enable every gstack skill parked in skills-disabled/ (move gstack__*
# back into skills/). Shared by cmd_reset and `gstack on`. Side effects
# only; prints one confirmation per restored skill.
enable_all_gstack() {
local entry name
[ -d "$DISABLED_DIR" ] || return 0
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
}
# Disable gstack-origin skills not listed in the given profile. Shared by
# cmd_set and `gstack off`. Caller is responsible for validating the profile.
disable_gstack_not_in() {
local prof="$1"
local keep_file name
keep_file="$(mktemp)"
read_profile "$prof" | cut -f1 | sort -u > "$keep_file"
while read -r name; do
[ -n "$name" ] || continue
grep -qx "$name" "$keep_file" || disable_skill "$name" gstack
done < <(gstack_skills | sort -u)
rm -f "$keep_file"
}
# Count gstack skills currently parked in skills-disabled/.
parked_gstack_count() {
[ -d "$DISABLED_DIR" ] || { echo 0; return 0; }
find "$DISABLED_DIR" -maxdepth 1 -name 'gstack__*' 2>/dev/null | wc -l | tr -d ' '
}
# ── Commands ──────────────────────────────────────────────
cmd_list() {
@ -367,28 +405,14 @@ 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_gstack_not_in "$prof"
# Disable managed plugins not in profile (PROTECTED_PLUGINS are excluded
# by disable_skill itself — belt and suspenders).
local p key plugin_name marketplace
local plugin_keep_file p plugin_name marketplace
plugin_keep_file="$(mktemp)"
read_profile "$prof" | awk -F'\t' '$2 ~ /^plugin@/ { sub(/^plugin@/, "", $2); print $1"@"$2 }' | sort -u > "$plugin_keep_file"
for p in "${MANAGED_PLUGINS[@]}"; do
if ! grep -qx "$p" "$plugin_keep_file"; then
plugin_name="${p%@*}"
@ -396,29 +420,67 @@ cmd_set() {
disable_skill "$plugin_name" "plugin@${marketplace}"
fi
done
rm -f "$plugin_keep_file"
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
enable_all_gstack
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>)"
write_active "none"
}
# gstack on|off — focused gstack-only toggle that keeps the active-profile
# label intact (unlike reset, which clears it to "none"). Lets the user
# layer all gstack on top of their current profile, or trim it back down
# to just what the active profile needs.
cmd_gstack() {
local action="${1:-}"
case "$action" in
on)
# Re-enable ALL gstack skills, but DON'T touch active-profile — the
# user is adding gstack on top of their current profile, not clearing it.
local parked
parked="$(parked_gstack_count)"
if [ "$parked" -eq 0 ]; then
info "all gstack skills already enabled"
else
enable_all_gstack
ok "all gstack enabled ($parked skills restored)"
fi
;;
off)
# Disable gstack skills not needed by the active profile. Needs a real
# active profile to know what to keep.
local active
active="$(head -n1 "$ACTIVE_CACHE" 2>/dev/null || echo none)"
[ -z "$active" ] && active="none"
if [ "$active" = "none" ] || [ ! -f "$PROFILES_DIR/$active.profile" ]; then
err "no active profile — 'gstack off' needs one to know what to keep"
info "run: bash lib/profile.sh set <name> then: gstack off"
return 1
fi
info "Disabling gstack skills not in active profile: $active"
disable_gstack_not_in "$active"
ok "gstack trimmed to profile: $active"
;;
""|-h|--help|help)
cat <<'EOF'
profile gstack on|off — toggle gstack without losing the active-profile label
on re-enable ALL gstack skills (keeps active-profile label)
off disable gstack skills not in the active profile
EOF
;;
*)
err "Unknown gstack action: '$action' (use: on | off)"; return 1 ;;
esac
}
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
@ -491,6 +553,7 @@ USAGE:
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 gstack on|off toggle gstack only, keep active-profile label
profile diff <a> <b> compare two profiles
PROFILES (in $PROFILES_DIR):
@ -527,6 +590,7 @@ main() {
apply) [ $# -ge 2 ] || { usage; exit 1; }; cmd_apply "$2" ;;
set) [ $# -ge 2 ] || { usage; exit 1; }; cmd_set "$2" ;;
reset) cmd_reset ;;
gstack) cmd_gstack "${2:-}" ;;
diff) [ $# -ge 3 ] || { usage; exit 1; }; cmd_diff "$2" "$3" ;;
""|-h|--help|help) usage ;;
*) err "Unknown command: $cmd"; usage; exit 1 ;;

View File

@ -8,7 +8,7 @@ description: |
"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>
argument-hint: list | show <name> | current | apply <name> | set <name> | reset | gstack on|off | diff <a> <b>
disable-model-invocation: false
allowed-tools:
- Bash
@ -81,9 +81,13 @@ 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)
# Re-enable every gstack skill (undo any set/apply) — resets active label to "none"
bash "$HOME/.claude/lib/profile.sh" reset
# Toggle gstack only, keeping the active-profile label intact
bash "$HOME/.claude/lib/profile.sh" gstack on # re-enable ALL gstack on top of current profile
bash "$HOME/.claude/lib/profile.sh" gstack off # disable gstack skills not in the active profile
# Compare two profiles
bash "$HOME/.claude/lib/profile.sh" diff <a> <b>
```
@ -101,9 +105,9 @@ 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 `set` / `apply` / `reset` / `gstack on|off`: 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.