diff --git a/.agents/skills/gstack-connect-chrome/SKILL.md b/.agents/skills/gstack-connect-chrome/SKILL.md index 972477b65..f6f8a1b41 100644 --- a/.agents/skills/gstack-connect-chrome/SKILL.md +++ b/.agents/skills/gstack-connect-chrome/SKILL.md @@ -56,10 +56,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`$GSTACK_ROOT/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `$GSTACK_ROOT/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `$GSTACK_ROOT/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/CHANGELOG.md b/CHANGELOG.md index a76d7825b..31a564068 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## [0.12.12.0] - 2026-03-27 — Custom Prefix, Live Reload + +Type `gstack-config set skill_prefix g` and every gstack skill immediately switches to `/g-ship`, `/g-qa`, `/g-review`. No re-running setup. No session restart. The dash is automatic — you just pick the word. + +### Added + +- **Custom prefix word.** Setup now offers three choices: short names (`/qa`, `/ship`), namespaced (`/gstack-qa`, `/gstack-ship`), or a custom word you type yourself (`g`, `gs`, `mytools` — anything lowercase, 1-10 chars). The system appends the `-` separator. +- **`{{SKILL_NAME}}` template variable.** SKILL.md templates now use `name: {{SKILL_NAME}}` in their frontmatter. The generator resolves this to the actual prefixed name at build time (e.g. `name: g-ship` when prefix is `g`). Claude Code uses the `name:` field to register the slash command trigger, so this is what makes `/g-ship` actually work — not just a renamed directory. +- **Live prefix reload.** `gstack-config set skill_prefix ` regenerates all installed SKILL.md files immediately. Switching from `gstack` to `g` takes one command, not a full re-run of setup. +- **`--prefix ` on setup.** The `--prefix` flag now takes a word argument (`--prefix g`) instead of being a toggle. `--no-prefix` still works as before. +- **Universal skill cleanup.** Switching between any two prefix configurations (including custom words) now cleans up all previously generated dirs before regenerating. Detection uses the `AUTO-GENERATED from SKILL.md.tmpl` header in each file. + +### Fixed + +- **Backward compat for existing configs.** If `~/.gstack/config.yaml` has `skill_prefix: true` (the old boolean), `gstack-config get skill_prefix` returns `gstack`. If it has `false`, it returns `false`. Existing installs work without migration. +- **Double regen on setup.** When setup saves the prefix preference via `gstack-config set`, it no longer triggers an immediate regen (setup does its own cleanup + regen cycle after). Controlled via `GSTACK_SKIP_REGEN=1`. +- **PATH-invoked `gstack-config`.** `gstack-config set skill_prefix` now uses `readlink -f "$0"` to find the gstack installation directory, so it works when invoked via PATH rather than by absolute path. +- **Shell injection in `gstack-config`.** The `sed` command now uses `|` as the delimiter instead of `/`, preventing prefix words from being misinterpreted as sed metacharacters. + +### For contributors + +- 8 new tests for custom prefix, backward compat, and cleanup behavior. +- `scripts/gen-skill-docs.ts` now accepts `--output-root ` to install SKILL.md files into a target directory without modifying committed source files. Used by `setup` for live installs. + ## [0.12.11.0] - 2026-03-27 — Skill Prefix is Now Your Choice You can now choose how gstack skills appear: short names (`/qa`, `/ship`, `/review`) or namespaced (`/gstack-qa`, `/gstack-ship`). Setup asks on first run, remembers your preference, and switching is one command. diff --git a/SKILL.md b/SKILL.md index aa0d42c58..e1099ad6a 100644 --- a/SKILL.md +++ b/SKILL.md @@ -57,10 +57,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/SKILL.md.tmpl b/SKILL.md.tmpl index 31bd28375..ddbcd75d9 100644 --- a/SKILL.md.tmpl +++ b/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: gstack +name: {{SKILL_NAME}} preamble-tier: 1 version: 1.1.0 description: | diff --git a/VERSION b/VERSION index 5db6e3f47..8c06e3d68 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.12.11.0 +0.12.12.0 diff --git a/autoplan/SKILL.md b/autoplan/SKILL.md index 662ef0409..666178e42 100644 --- a/autoplan/SKILL.md +++ b/autoplan/SKILL.md @@ -66,10 +66,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/autoplan/SKILL.md.tmpl b/autoplan/SKILL.md.tmpl index 16c35adca..e84c26a31 100644 --- a/autoplan/SKILL.md.tmpl +++ b/autoplan/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: autoplan +name: {{SKILL_NAME}} preamble-tier: 3 version: 1.0.0 description: | diff --git a/benchmark/SKILL.md b/benchmark/SKILL.md index c709caadb..7ab020ffe 100644 --- a/benchmark/SKILL.md +++ b/benchmark/SKILL.md @@ -59,10 +59,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/benchmark/SKILL.md.tmpl b/benchmark/SKILL.md.tmpl index 5149ea441..cd036db72 100644 --- a/benchmark/SKILL.md.tmpl +++ b/benchmark/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: benchmark +name: {{SKILL_NAME}} preamble-tier: 1 version: 1.0.0 description: | diff --git a/bin/gstack-config b/bin/gstack-config index 1147adddb..07040b793 100755 --- a/bin/gstack-config +++ b/bin/gstack-config @@ -16,19 +16,61 @@ CONFIG_FILE="$STATE_DIR/config.yaml" case "${1:-}" in get) KEY="${2:?Usage: gstack-config get }" - grep -E "^${KEY}:" "$CONFIG_FILE" 2>/dev/null | tail -1 | awk '{print $2}' | tr -d '[:space:]' || true + _raw=$(grep -E "^${KEY}:" "$CONFIG_FILE" 2>/dev/null | tail -1 | awk '{print $2}' | tr -d '[:space:]' || true) + # Backward compat: skill_prefix stored as boolean → return canonical string + if [ "$KEY" = "skill_prefix" ]; then + case "$_raw" in + "true") echo "gstack" ;; + "false") echo "false" ;; + *) echo "$_raw" ;; + esac + else + echo "$_raw" + fi ;; set) KEY="${2:?Usage: gstack-config set }" - VALUE="${3:?Usage: gstack-config set }" + VALUE="${3:-}" # allow empty string (clears the value) + # Validate skill_prefix: must be lowercase alphanumeric, 1-10 chars (or empty to clear) + if [ "$KEY" = "skill_prefix" ] && [ -n "$VALUE" ]; then + if ! printf '%s' "$VALUE" | grep -qE '^[a-z0-9]{1,10}$'; then + echo "Error: skill_prefix must be lowercase letters/numbers only, 1-10 chars (got: '$VALUE')" >&2 + echo " Valid examples: g, gs, gstack, my, x" >&2 + exit 1 + fi + fi mkdir -p "$STATE_DIR" if grep -qE "^${KEY}:" "$CONFIG_FILE" 2>/dev/null; then - # Portable in-place edit (BSD sed uses -i '', GNU sed uses -i without arg) + # Use | as sed delimiter to avoid conflicts with / in values _tmpfile="$(mktemp "${CONFIG_FILE}.XXXXXX")" - sed "s/^${KEY}:.*/${KEY}: ${VALUE}/" "$CONFIG_FILE" > "$_tmpfile" && mv "$_tmpfile" "$CONFIG_FILE" + sed "s|^${KEY}:.*|${KEY}: ${VALUE}|" "$CONFIG_FILE" > "$_tmpfile" && mv "$_tmpfile" "$CONFIG_FILE" else echo "${KEY}: ${VALUE}" >> "$CONFIG_FILE" fi + # After setting skill_prefix, regenerate Claude skills with new prefix. + # Set GSTACK_SKIP_REGEN=1 to suppress this (e.g., when called from setup, + # which runs its own cleanup + regen cycle after saving the preference). + if [ "$KEY" = "skill_prefix" ] && [ -z "${GSTACK_SKIP_REGEN:-}" ]; then + # Resolve gstack dir from this binary's real path (handles PATH invocation) + _SELF="$(cd "$(dirname "$(readlink -f "$0" 2>/dev/null || echo "$0")")" 2>/dev/null && pwd -P || dirname "$0")" + _GSTACK_DIR="$(cd "$_SELF/.." 2>/dev/null && pwd -P || true)" + _SKILLS_DIR="$(cd "$_GSTACK_DIR/.." 2>/dev/null && pwd -P || true)" + if [ -f "$_GSTACK_DIR/scripts/gen-skill-docs.ts" ] && [ -d "$_SKILLS_DIR" ]; then + _PREFIX_FLAG="" + [ -n "$VALUE" ] && _PREFIX_FLAG="--prefix ${VALUE}-" + # shellcheck disable=SC2086 + if bun --bun "$_GSTACK_DIR/scripts/gen-skill-docs.ts" \ + --output-root "$_SKILLS_DIR" \ + $_PREFIX_FLAG \ + 2>&1 | grep -E '^(GENERATED|INSTALLED|Error)' | sed 's/^/ /'; then + echo " Skills regenerated with prefix: ${VALUE:-(none)}" + else + echo " Warning: skill regeneration failed — run ./setup to reapply prefix." >&2 + fi + else + echo " Warning: could not determine skills directory. Re-run ./setup to apply prefix." >&2 + fi + fi ;; list) cat "$CONFIG_FILE" 2>/dev/null || true diff --git a/browse/SKILL.md b/browse/SKILL.md index 0aff8ea18..2d4998d23 100644 --- a/browse/SKILL.md +++ b/browse/SKILL.md @@ -59,10 +59,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/browse/SKILL.md.tmpl b/browse/SKILL.md.tmpl index a11505ea6..bb7356aba 100644 --- a/browse/SKILL.md.tmpl +++ b/browse/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: browse +name: {{SKILL_NAME}} preamble-tier: 1 version: 1.1.0 description: | diff --git a/canary/SKILL.md b/canary/SKILL.md index 753c37074..2a5ae5e8b 100644 --- a/canary/SKILL.md +++ b/canary/SKILL.md @@ -59,10 +59,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/canary/SKILL.md.tmpl b/canary/SKILL.md.tmpl index 680b58147..e4605817a 100644 --- a/canary/SKILL.md.tmpl +++ b/canary/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: canary +name: {{SKILL_NAME}} preamble-tier: 2 version: 1.0.0 description: | diff --git a/careful/SKILL.md.tmpl b/careful/SKILL.md.tmpl index d8bd46620..be52bfd95 100644 --- a/careful/SKILL.md.tmpl +++ b/careful/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: careful +name: {{SKILL_NAME}} version: 0.1.0 description: | Safety guardrails for destructive commands. Warns before rm -rf, DROP TABLE, diff --git a/codex/SKILL.md b/codex/SKILL.md index 1e974b0ff..59c0bd2c3 100644 --- a/codex/SKILL.md +++ b/codex/SKILL.md @@ -60,10 +60,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/codex/SKILL.md.tmpl b/codex/SKILL.md.tmpl index 684f7d69b..cb7a6bf97 100644 --- a/codex/SKILL.md.tmpl +++ b/codex/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: codex +name: {{SKILL_NAME}} preamble-tier: 3 version: 1.0.0 description: | diff --git a/connect-chrome/SKILL.md b/connect-chrome/SKILL.md index f6f48e506..16908f6a3 100644 --- a/connect-chrome/SKILL.md +++ b/connect-chrome/SKILL.md @@ -57,10 +57,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/connect-chrome/SKILL.md.tmpl b/connect-chrome/SKILL.md.tmpl index fb338fb18..60990932b 100644 --- a/connect-chrome/SKILL.md.tmpl +++ b/connect-chrome/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: connect-chrome +name: {{SKILL_NAME}} version: 0.1.0 description: | Launch real Chrome controlled by gstack with the Side Panel extension auto-loaded. diff --git a/cso/SKILL.md b/cso/SKILL.md index 13fe35c61..616795cf4 100644 --- a/cso/SKILL.md +++ b/cso/SKILL.md @@ -63,10 +63,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/cso/SKILL.md.tmpl b/cso/SKILL.md.tmpl index 676c1bd94..a4198b00a 100644 --- a/cso/SKILL.md.tmpl +++ b/cso/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: cso +name: {{SKILL_NAME}} preamble-tier: 2 version: 2.0.0 description: | diff --git a/design-consultation/SKILL.md b/design-consultation/SKILL.md index 190b84e03..abb212ca3 100644 --- a/design-consultation/SKILL.md +++ b/design-consultation/SKILL.md @@ -64,10 +64,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/design-consultation/SKILL.md.tmpl b/design-consultation/SKILL.md.tmpl index 2d7a5a342..042f085e2 100644 --- a/design-consultation/SKILL.md.tmpl +++ b/design-consultation/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: design-consultation +name: {{SKILL_NAME}} preamble-tier: 3 version: 1.0.0 description: | diff --git a/design-review/SKILL.md b/design-review/SKILL.md index 34c9e3319..f344bd395 100644 --- a/design-review/SKILL.md +++ b/design-review/SKILL.md @@ -64,10 +64,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/design-review/SKILL.md.tmpl b/design-review/SKILL.md.tmpl index bb169142c..a280f814d 100644 --- a/design-review/SKILL.md.tmpl +++ b/design-review/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: design-review +name: {{SKILL_NAME}} preamble-tier: 4 version: 2.0.0 description: | diff --git a/document-release/SKILL.md b/document-release/SKILL.md index a44dbf7dd..adbfb5908 100644 --- a/document-release/SKILL.md +++ b/document-release/SKILL.md @@ -61,10 +61,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/document-release/SKILL.md.tmpl b/document-release/SKILL.md.tmpl index 6b1fb7e34..6a237c226 100644 --- a/document-release/SKILL.md.tmpl +++ b/document-release/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: document-release +name: {{SKILL_NAME}} preamble-tier: 2 version: 1.0.0 description: | diff --git a/freeze/SKILL.md.tmpl b/freeze/SKILL.md.tmpl index 8765cc1f5..b4c18b0e9 100644 --- a/freeze/SKILL.md.tmpl +++ b/freeze/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: freeze +name: {{SKILL_NAME}} version: 0.1.0 description: | Restrict file edits to a specific directory for the session. Blocks Edit and diff --git a/gstack-upgrade/SKILL.md.tmpl b/gstack-upgrade/SKILL.md.tmpl index ac25894b3..0fe9deda0 100644 --- a/gstack-upgrade/SKILL.md.tmpl +++ b/gstack-upgrade/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: gstack-upgrade +name: {{SKILL_NAME}} version: 1.1.0 description: | Upgrade gstack to the latest version. Detects global vs vendored install, diff --git a/guard/SKILL.md.tmpl b/guard/SKILL.md.tmpl index 4dc352448..03bb69b38 100644 --- a/guard/SKILL.md.tmpl +++ b/guard/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: guard +name: {{SKILL_NAME}} version: 0.1.0 description: | Full safety mode: destructive command warnings + directory-scoped edits. diff --git a/investigate/SKILL.md b/investigate/SKILL.md index 3f3d1c841..fa09f9728 100644 --- a/investigate/SKILL.md +++ b/investigate/SKILL.md @@ -75,10 +75,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/investigate/SKILL.md.tmpl b/investigate/SKILL.md.tmpl index d2eee63fe..a32b37427 100644 --- a/investigate/SKILL.md.tmpl +++ b/investigate/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: investigate +name: {{SKILL_NAME}} preamble-tier: 2 version: 1.0.0 description: | diff --git a/land-and-deploy/SKILL.md b/land-and-deploy/SKILL.md index b3e5c34ed..26da0db18 100644 --- a/land-and-deploy/SKILL.md +++ b/land-and-deploy/SKILL.md @@ -58,10 +58,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/land-and-deploy/SKILL.md.tmpl b/land-and-deploy/SKILL.md.tmpl index acec63c2e..992754139 100644 --- a/land-and-deploy/SKILL.md.tmpl +++ b/land-and-deploy/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: land-and-deploy +name: {{SKILL_NAME}} preamble-tier: 4 version: 1.0.0 description: | diff --git a/office-hours/SKILL.md b/office-hours/SKILL.md index 6568f5cba..02afe4114 100644 --- a/office-hours/SKILL.md +++ b/office-hours/SKILL.md @@ -66,10 +66,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/office-hours/SKILL.md.tmpl b/office-hours/SKILL.md.tmpl index 5e7187449..811258a49 100644 --- a/office-hours/SKILL.md.tmpl +++ b/office-hours/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: office-hours +name: {{SKILL_NAME}} preamble-tier: 3 version: 2.0.0 description: | diff --git a/package.json b/package.json index 80f41b41b..6fdb8bd2b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "0.12.11.0", + "version": "0.12.12.0", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/plan-ceo-review/SKILL.md b/plan-ceo-review/SKILL.md index 4e8b13c0d..f7898bb3f 100644 --- a/plan-ceo-review/SKILL.md +++ b/plan-ceo-review/SKILL.md @@ -64,10 +64,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/plan-ceo-review/SKILL.md.tmpl b/plan-ceo-review/SKILL.md.tmpl index 404d1791a..665182e37 100644 --- a/plan-ceo-review/SKILL.md.tmpl +++ b/plan-ceo-review/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: plan-ceo-review +name: {{SKILL_NAME}} preamble-tier: 3 version: 1.0.0 description: | diff --git a/plan-design-review/SKILL.md b/plan-design-review/SKILL.md index 1328222e3..7351bac21 100644 --- a/plan-design-review/SKILL.md +++ b/plan-design-review/SKILL.md @@ -62,10 +62,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/plan-design-review/SKILL.md.tmpl b/plan-design-review/SKILL.md.tmpl index 00bbed280..5fd39cc78 100644 --- a/plan-design-review/SKILL.md.tmpl +++ b/plan-design-review/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: plan-design-review +name: {{SKILL_NAME}} preamble-tier: 3 version: 2.0.0 description: | diff --git a/plan-eng-review/SKILL.md b/plan-eng-review/SKILL.md index caceb7e2c..c8c5b53c4 100644 --- a/plan-eng-review/SKILL.md +++ b/plan-eng-review/SKILL.md @@ -63,10 +63,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/plan-eng-review/SKILL.md.tmpl b/plan-eng-review/SKILL.md.tmpl index b1f05a03d..f172871bc 100644 --- a/plan-eng-review/SKILL.md.tmpl +++ b/plan-eng-review/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: plan-eng-review +name: {{SKILL_NAME}} preamble-tier: 3 version: 1.0.0 description: | diff --git a/qa-only/SKILL.md b/qa-only/SKILL.md index 8fafc61e3..f0ecb0ca0 100644 --- a/qa-only/SKILL.md +++ b/qa-only/SKILL.md @@ -59,10 +59,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/qa-only/SKILL.md.tmpl b/qa-only/SKILL.md.tmpl index 0bb59c0c0..e3db7cd33 100644 --- a/qa-only/SKILL.md.tmpl +++ b/qa-only/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: qa-only +name: {{SKILL_NAME}} preamble-tier: 4 version: 1.0.0 description: | diff --git a/qa/SKILL.md b/qa/SKILL.md index 7173e9310..55193ae8b 100644 --- a/qa/SKILL.md +++ b/qa/SKILL.md @@ -65,10 +65,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/qa/SKILL.md.tmpl b/qa/SKILL.md.tmpl index 0283ffc7c..419cad69c 100644 --- a/qa/SKILL.md.tmpl +++ b/qa/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: qa +name: {{SKILL_NAME}} preamble-tier: 4 version: 2.0.0 description: | diff --git a/retro/SKILL.md b/retro/SKILL.md index 0b004fe66..ff65197eb 100644 --- a/retro/SKILL.md +++ b/retro/SKILL.md @@ -59,10 +59,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/retro/SKILL.md.tmpl b/retro/SKILL.md.tmpl index 5463d07a9..11ae02bf1 100644 --- a/retro/SKILL.md.tmpl +++ b/retro/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: retro +name: {{SKILL_NAME}} preamble-tier: 2 version: 2.0.0 description: | diff --git a/review/SKILL.md b/review/SKILL.md index e5d856c8a..24d9dda94 100644 --- a/review/SKILL.md +++ b/review/SKILL.md @@ -62,10 +62,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/review/SKILL.md.tmpl b/review/SKILL.md.tmpl index bb9a3bc73..4f905a880 100644 --- a/review/SKILL.md.tmpl +++ b/review/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: review +name: {{SKILL_NAME}} preamble-tier: 4 version: 1.0.0 description: | diff --git a/scripts/gen-skill-docs.ts b/scripts/gen-skill-docs.ts index 970e5a3f3..1ca1b1199 100644 --- a/scripts/gen-skill-docs.ts +++ b/scripts/gen-skill-docs.ts @@ -23,6 +23,26 @@ import { generatePlanCompletionAuditShip, generatePlanCompletionAuditReview, gen const ROOT = path.resolve(import.meta.dir, '..'); const DRY_RUN = process.argv.includes('--dry-run'); +// ─── Prefix / Output-Root Args ─────────────────────────────── +// --prefix e.g. --prefix gstack- (default: no prefix) +// --output-root write generated SKILL.md files to //SKILL.md +// instead of the source directory. Used by `setup` to install prefixed skills +// into ~/.claude/skills/ without modifying the committed source files. + +function parseArg(flag: string): string | null { + const idx = process.argv.indexOf(flag); + if (idx !== -1 && process.argv[idx + 1]) return process.argv[idx + 1]; + const eq = process.argv.find(a => a.startsWith(`${flag}=`)); + return eq ? eq.slice(flag.length + 1) : null; +} + +const SKILL_PREFIX: string = parseArg('--prefix') ?? ''; + +const _outputRootRaw = parseArg('--output-root'); +const OUTPUT_ROOT: string | null = _outputRootRaw + ? _outputRootRaw.replace(/^~/, process.env.HOME ?? '~') + : null; + // ─── Host Detection ───────────────────────────────────────── const HOST_ARG = process.argv.find(a => a.startsWith('--host')); @@ -2244,9 +2264,27 @@ function processTemplate(tmplPath: string, host: Host = 'claude'): { outputPath: outputPath = path.join(outputDir, 'SKILL.md'); } - // Extract skill name from frontmatter for TemplateContext + // Derive skill name from directory name + optional prefix. + // If the template uses name: {{SKILL_NAME}}, extractedName would be a placeholder + // string — we fall back to the directory name in that case. const { name: extractedName, description: extractedDescription } = extractNameAndDescription(tmplContent); - const skillName = extractedName || path.basename(path.dirname(tmplPath)); + const dirBaseName = (skillDir === '.' || skillDir === '') ? 'gstack' : path.basename(path.dirname(tmplPath)); + const baseSkillName = (extractedName && !extractedName.includes('{{')) + ? extractedName // legacy: hardcoded name field in template + : dirBaseName; // new: name: {{SKILL_NAME}} — derive from directory + // Apply prefix unless the name already starts with it (e.g., gstack-upgrade + gstack-). + const skillName = SKILL_PREFIX && !baseSkillName.startsWith(SKILL_PREFIX) + ? `${SKILL_PREFIX}${baseSkillName}` + : baseSkillName; + + // When --output-root is set, write to //SKILL.md instead of + // the source directory. Used by `setup` to install prefixed/flat SKILL.md files into + // ~/.claude/skills/ without modifying the committed source files. + if (OUTPUT_ROOT && host === 'claude' && skillDir !== '.') { + const outDir = path.join(OUTPUT_ROOT, skillName); + fs.mkdirSync(outDir, { recursive: true }); + outputPath = path.join(outDir, 'SKILL.md'); + } // Extract benefits-from list from frontmatter (inline YAML: benefits-from: [a, b]) const benefitsMatch = tmplContent.match(/^benefits-from:\s*\[([^\]]*)\]/m); diff --git a/scripts/resolvers/index.ts b/scripts/resolvers/index.ts index d4536312c..ee8d12e75 100644 --- a/scripts/resolvers/index.ts +++ b/scripts/resolvers/index.ts @@ -15,6 +15,7 @@ import { generateReviewDashboard, generatePlanFileReviewReport, generateSpecRevi import { generateSlugEval, generateSlugSetup, generateBaseBranchDetect, generateDeployBootstrap, generateQAMethodology, generateCoAuthorTrailer } from './utility'; export const RESOLVERS: Record string> = { + SKILL_NAME: (ctx) => ctx.skillName, SLUG_EVAL: generateSlugEval, SLUG_SETUP: generateSlugSetup, COMMAND_REFERENCE: generateCommandReference, diff --git a/scripts/resolvers/preamble.ts b/scripts/resolvers/preamble.ts index 4e5092f8a..61b0c62b8 100644 --- a/scripts/resolvers/preamble.ts +++ b/scripts/resolvers/preamble.ts @@ -53,10 +53,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If \`SKILL_PREFIX\` is \`"true"\`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the \`/gstack-\` prefix (e.g., \`/gstack-qa\` instead -of \`/qa\`, \`/gstack-ship\` instead of \`/ship\`). Disk paths are unaffected — always use -\`${ctx.paths.skillRoot}/[skill-name]/SKILL.md\` for reading skill files. +If \`SKILL_PREFIX\` is not \`"false"\` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is \`"g"\`, +suggest \`/g-qa\` instead of \`/qa\`, \`/g-ship\` instead of \`/ship\`; if SKILL_PREFIX is +\`"gstack"\`, use \`/gstack-qa\`, \`/gstack-ship\`, etc.). Disk paths are unaffected — always +use \`${ctx.paths.skillRoot}/[skill-name]/SKILL.md\` for reading skill files. If output shows \`UPGRADE_AVAILABLE \`: read \`${ctx.paths.skillRoot}/gstack-upgrade/SKILL.md\` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If \`JUST_UPGRADED \`: tell user "Running gstack v{to} (just updated!)" and continue.`; } diff --git a/setup b/setup index 995833ae7..8e20733e2 100755 --- a/setup +++ b/setup @@ -23,15 +23,24 @@ esac # ─── Parse flags ────────────────────────────────────────────── HOST="claude" LOCAL_INSTALL=0 -SKILL_PREFIX=1 +SKILL_PREFIX="" # empty = no prefix; any word = that prefix (e.g. "gstack", "g") SKILL_PREFIX_FLAG=0 while [ $# -gt 0 ]; do case "$1" in --host) [ -z "$2" ] && echo "Missing value for --host (expected claude, codex, kiro, or auto)" >&2 && exit 1; HOST="$2"; shift 2 ;; --host=*) HOST="${1#--host=}"; shift ;; --local) LOCAL_INSTALL=1; shift ;; - --prefix) SKILL_PREFIX=1; SKILL_PREFIX_FLAG=1; shift ;; - --no-prefix) SKILL_PREFIX=0; SKILL_PREFIX_FLAG=1; shift ;; + --prefix) + if [ -z "${2:-}" ] || [ "${2#-}" != "${2}" ]; then + echo "Error: --prefix requires a word argument (e.g. --prefix g)" >&2 + exit 1 + fi + if ! printf '%s' "$2" | grep -qE '^[a-z0-9]{1,10}$'; then + echo "Error: prefix must be lowercase letters/numbers only, 1-10 chars (got: '$2')" >&2 + exit 1 + fi + SKILL_PREFIX="$2"; SKILL_PREFIX_FLAG=1; shift 2 ;; + --no-prefix) SKILL_PREFIX=""; SKILL_PREFIX_FLAG=1; shift ;; *) shift ;; esac done @@ -43,40 +52,70 @@ esac # ─── Resolve skill prefix preference ───────────────────────── # Priority: CLI flag > saved config > interactive prompt (or flat default for non-TTY) +# SKILL_PREFIX is a string: "" (no prefix), "gstack" (gstack-), or any custom word (e.g. "g") GSTACK_CONFIG="$SOURCE_GSTACK_DIR/bin/gstack-config" + +# validate_prefix: ensure word is lowercase alphanumeric, 1-10 chars +validate_prefix() { + local word="$1" + if ! printf '%s' "$word" | grep -qE '^[a-z0-9]{1,10}$'; then + echo "Error: prefix must be lowercase letters/numbers only, 1-10 chars (got: '$word')" >&2 + echo " Valid examples: g, gs, gstack, my, x" >&2 + return 1 + fi +} + if [ "$SKILL_PREFIX_FLAG" -eq 0 ]; then _saved_prefix="$("$GSTACK_CONFIG" get skill_prefix 2>/dev/null || true)" - if [ "$_saved_prefix" = "true" ]; then - SKILL_PREFIX=1 - elif [ "$_saved_prefix" = "false" ]; then - SKILL_PREFIX=0 - else - # No saved preference — prompt interactively (or default flat for non-TTY) - if [ -t 0 ]; then - echo "" - echo "Skill naming: how should gstack skills appear?" - echo "" - echo " 1) Short names: /qa, /ship, /review" - echo " Recommended. Clean and fast to type." - echo "" - echo " 2) Namespaced: /gstack-qa, /gstack-ship, /gstack-review" - echo " Use this if you run other skill packs alongside gstack to avoid conflicts." - echo "" - printf "Choice [1/2] (default: 1): " - read -r _prefix_choice /dev/null || _prefix_choice="" - case "$_prefix_choice" in - 2) SKILL_PREFIX=1 ;; - *) SKILL_PREFIX=0 ;; - esac + if [ -n "$_saved_prefix" ] && [ "$_saved_prefix" != "false" ]; then + # Saved preference is a prefix word (e.g. "gstack", "g") — use it directly. + # gstack-config get already migrates stored "true" → "gstack". + SKILL_PREFIX="$_saved_prefix" + elif [ "$_saved_prefix" = "false" ] || [ -z "$_saved_prefix" ]; then + if [ "$_saved_prefix" = "false" ]; then + # Explicit "no prefix" saved — use it + SKILL_PREFIX="" else - SKILL_PREFIX=0 + # No saved preference — prompt interactively (or default flat for non-TTY) + if [ -t 0 ]; then + echo "" + echo "Skill naming: how should gstack skills appear?" + echo "" + echo " 1) Short names: /qa, /ship, /review" + echo " Clean and fast to type." + echo "" + echo " 2) Namespaced: /gstack-qa, /gstack-ship, /gstack-review" + echo " Avoids conflicts with other skill packs." + echo "" + echo " 3) Custom prefix: type your own (e.g. 'g' for /g-ship)" + echo "" + printf "Choice [1/2/3] (default: 1): " + read -r _prefix_choice /dev/null || _prefix_choice="" + case "$_prefix_choice" in + 2) SKILL_PREFIX="gstack" ;; + 3) + while true; do + printf "Prefix word (lowercase letters/numbers, 1-10 chars, no hyphens): " + read -r _custom_prefix /dev/null || _custom_prefix="" + if validate_prefix "$_custom_prefix" 2>/dev/null; then + SKILL_PREFIX="$_custom_prefix" + break + fi + echo " Invalid prefix. Try again (e.g. g, gs, my, x)." >&2 + done + ;; + *) SKILL_PREFIX="" ;; + esac + else + SKILL_PREFIX="" + fi + # Save the choice for future runs (GSTACK_SKIP_REGEN=1 prevents double regen — setup handles it below) + GSTACK_SKIP_REGEN=1 GSTACK_STATE_DIR="${GSTACK_STATE_DIR:-$HOME/.gstack}" "$GSTACK_CONFIG" set skill_prefix "${SKILL_PREFIX:-false}" 2>/dev/null || true fi - # Save the choice for future runs - "$GSTACK_CONFIG" set skill_prefix "$([ "$SKILL_PREFIX" -eq 1 ] && echo true || echo false)" 2>/dev/null || true fi else - # Flag was passed explicitly — persist the choice - "$GSTACK_CONFIG" set skill_prefix "$([ "$SKILL_PREFIX" -eq 1 ] && echo true || echo false)" 2>/dev/null || true + # Flag was passed explicitly — persist the choice (without triggering regen) + GSTACK_STATE_DIR="${GSTACK_STATE_DIR:-$HOME/.gstack}" "$GSTACK_CONFIG" set skill_prefix "${SKILL_PREFIX:-false}" 2>/dev/null || true fi # --local: install to .claude/skills/ in the current working directory @@ -240,101 +279,63 @@ fi # 3. Ensure ~/.gstack global state directory exists mkdir -p "$HOME/.gstack/projects" -# ─── Helper: link Claude skill subdirectories into a skills parent directory ── -# When SKILL_PREFIX=1 (default), symlinks are prefixed with "gstack-" to avoid -# namespace pollution (e.g., gstack-review instead of review). -# Use --no-prefix to restore the old flat names. -link_claude_skill_dirs() { - local gstack_dir="$1" - local skills_dir="$2" - local linked=() - for skill_dir in "$gstack_dir"/*/; do - if [ -f "$skill_dir/SKILL.md" ]; then - skill_name="$(basename "$skill_dir")" - # Skip node_modules - [ "$skill_name" = "node_modules" ] && continue - # Apply gstack- prefix unless --no-prefix or already prefixed - if [ "$SKILL_PREFIX" -eq 1 ]; then - case "$skill_name" in - gstack-*) link_name="$skill_name" ;; - *) link_name="gstack-$skill_name" ;; - esac - else - link_name="$skill_name" - fi - target="$skills_dir/$link_name" - # Create or update symlink; skip if a real file/directory exists - if [ -L "$target" ] || [ ! -e "$target" ]; then - ln -snf "gstack/$skill_name" "$target" - linked+=("$link_name") - fi - fi - done - if [ ${#linked[@]} -gt 0 ]; then - echo " linked skills: ${linked[*]}" - fi -} - -# ─── Helper: remove old unprefixed Claude skill symlinks ────────────────────── -# Migration: when switching from flat names to gstack- prefixed names, -# clean up stale symlinks that point into the gstack directory. -cleanup_old_claude_symlinks() { +# ─── Helper: generate Claude skill SKILL.md files into the skills directory ─── +# Uses gen-skill-docs.ts with --output-root so the installed SKILL.md reflects +# the chosen prefix in its name: frontmatter field. This ensures Claude Code +# registers the skill as /gstack-ship (prefixed) rather than /ship (unprefixed). +generate_claude_skill_docs() { local gstack_dir="$1" local skills_dir="$2" - local removed=() - for skill_dir in "$gstack_dir"/*/; do - if [ -f "$skill_dir/SKILL.md" ]; then - skill_name="$(basename "$skill_dir")" - [ "$skill_name" = "node_modules" ] && continue - # Skip already-prefixed dirs (gstack-upgrade) — no old symlink to clean - case "$skill_name" in gstack-*) continue ;; esac - old_target="$skills_dir/$skill_name" - # Only remove if it's a symlink pointing into gstack/ - if [ -L "$old_target" ]; then - link_dest="$(readlink "$old_target" 2>/dev/null || true)" - case "$link_dest" in - gstack/*|*/gstack/*) - rm -f "$old_target" - removed+=("$skill_name") - ;; - esac - fi - fi - done - if [ ${#removed[@]} -gt 0 ]; then - echo " cleaned up old symlinks: ${removed[*]}" + local prefix_flag="" + # SKILL_PREFIX is a word (e.g. "gstack", "g") or empty for no prefix. + # Only set --prefix when non-empty; an empty prefix must not produce "--prefix -". + [ -n "$SKILL_PREFIX" ] && prefix_flag="--prefix ${SKILL_PREFIX}-" + # shellcheck disable=SC2086 + bun --bun "$gstack_dir/scripts/gen-skill-docs.ts" \ + --output-root "$skills_dir" \ + $prefix_flag \ + 2>&1 | grep -E '^(GENERATED|INSTALLED|Error)' | sed 's/^/ /' + local bun_exit="${PIPESTATUS[0]}" + if [ "$bun_exit" -ne 0 ]; then + echo " ERROR: skill generation failed (bun exit $bun_exit) — is bun in PATH?" >&2 + echo " Run ./setup again to retry, or check that bun is in PATH." >&2 + return 1 fi } -# ─── Helper: remove old prefixed Claude skill symlinks ──────────────────────── -# Reverse migration: when switching from gstack- prefixed names to flat names, -# clean up stale gstack-* symlinks that point into the gstack directory. -cleanup_prefixed_claude_symlinks() { - local gstack_dir="$1" - local skills_dir="$2" +# ─── Helper: remove all gstack-generated Claude skill directories ───────────── +# Scans all dirs under the skills directory and removes any that contain a +# SKILL.md with the AUTO-GENERATED header. This handles prefix switches of any +# kind (flat → prefixed, gstack- → custom, custom → different custom). +# The main gstack/ dir is never removed. +cleanup_generated_skill_dirs() { + local skills_dir="$1" local removed=() - for skill_dir in "$gstack_dir"/*/; do - if [ -f "$skill_dir/SKILL.md" ]; then - skill_name="$(basename "$skill_dir")" - [ "$skill_name" = "node_modules" ] && continue - # Only clean up prefixed symlinks for dirs that AREN'T already prefixed - # (e.g., remove gstack-qa but NOT gstack-upgrade which is the real dir name) - case "$skill_name" in gstack-*) continue ;; esac - prefixed_target="$skills_dir/gstack-$skill_name" - # Only remove if it's a symlink pointing into gstack/ - if [ -L "$prefixed_target" ]; then - link_dest="$(readlink "$prefixed_target" 2>/dev/null || true)" - case "$link_dest" in - gstack/*|*/gstack/*) - rm -f "$prefixed_target" - removed+=("gstack-$skill_name") - ;; - esac - fi + for target_dir in "$skills_dir"/*/; do + [ -d "$target_dir" ] || continue + local skill_name + skill_name="$(basename "$target_dir")" + # Never remove the main gstack installation dir + [ "$skill_name" = "gstack" ] && continue + local skill_md="$target_dir/SKILL.md" + # Remove symlink pointing into gstack/ (legacy from pre-generation approach) + if [ -L "$target_dir" ]; then + local link_dest + link_dest="$(readlink "$target_dir" 2>/dev/null || true)" + case "$link_dest" in + gstack/*|*/gstack/*) + rm -f "$target_dir" + removed+=("$skill_name") + ;; + esac + # Remove generated directory (identified by AUTO-GENERATED header in SKILL.md) + elif [ -f "$skill_md" ] && grep -q 'AUTO-GENERATED from SKILL.md.tmpl' "$skill_md" 2>/dev/null; then + rm -rf "$target_dir" + removed+=("$skill_name") fi done if [ ${#removed[@]} -gt 0 ]; then - echo " cleaned up prefixed symlinks: ${removed[*]}" + echo " cleaned up generated skill dirs: ${removed[*]}" fi } @@ -465,13 +466,10 @@ fi if [ "$INSTALL_CLAUDE" -eq 1 ]; then if [ "$SKILLS_BASENAME" = "skills" ]; then - # Clean up stale symlinks from the opposite prefix mode - if [ "$SKILL_PREFIX" -eq 1 ]; then - cleanup_old_claude_symlinks "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR" - else - cleanup_prefixed_claude_symlinks "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR" - fi - link_claude_skill_dirs "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR" + # Clean up all previously generated skill dirs before regenerating with the new prefix. + # This handles any prefix switch: flat→prefixed, gstack→custom, custom→different, etc. + cleanup_generated_skill_dirs "$INSTALL_SKILLS_DIR" + generate_claude_skill_docs "$SOURCE_GSTACK_DIR" "$INSTALL_SKILLS_DIR" if [ "$LOCAL_INSTALL" -eq 1 ]; then echo "gstack ready (project-local)." echo " skills: $INSTALL_SKILLS_DIR" @@ -482,7 +480,7 @@ if [ "$INSTALL_CLAUDE" -eq 1 ]; then else echo "gstack ready (claude)." echo " browse: $BROWSE_BIN" - echo " (skipped skill symlinks — not inside .claude/skills/)" + echo " (skipped skill generation — not inside .claude/skills/)" fi fi diff --git a/setup-browser-cookies/SKILL.md b/setup-browser-cookies/SKILL.md index c20de97f3..72b158333 100644 --- a/setup-browser-cookies/SKILL.md +++ b/setup-browser-cookies/SKILL.md @@ -56,10 +56,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/setup-browser-cookies/SKILL.md.tmpl b/setup-browser-cookies/SKILL.md.tmpl index 88b1f5533..acf07fb1b 100644 --- a/setup-browser-cookies/SKILL.md.tmpl +++ b/setup-browser-cookies/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: setup-browser-cookies +name: {{SKILL_NAME}} preamble-tier: 1 version: 1.0.0 description: | diff --git a/setup-deploy/SKILL.md b/setup-deploy/SKILL.md index fed3f07e9..623e1863d 100644 --- a/setup-deploy/SKILL.md +++ b/setup-deploy/SKILL.md @@ -62,10 +62,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/setup-deploy/SKILL.md.tmpl b/setup-deploy/SKILL.md.tmpl index 8326da977..805f1fb89 100644 --- a/setup-deploy/SKILL.md.tmpl +++ b/setup-deploy/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: setup-deploy +name: {{SKILL_NAME}} preamble-tier: 2 version: 1.0.0 description: | diff --git a/ship/SKILL.md b/ship/SKILL.md index c634aa5ce..238cc6180 100644 --- a/ship/SKILL.md +++ b/ship/SKILL.md @@ -60,10 +60,12 @@ types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefl "I think /skillname might help here — want me to run it?" and wait for confirmation. The user opted out of proactive behavior. -If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting -or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead -of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use -`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. +If `SKILL_PREFIX` is not `"false"` and not empty, the user has namespaced skill names. +The value printed above is the user's chosen prefix word. When suggesting or invoking +other gstack skills, prepend that word plus a dash (e.g., if SKILL_PREFIX is `"g"`, +suggest `/g-qa` instead of `/qa`, `/g-ship` instead of `/ship`; if SKILL_PREFIX is +`"gstack"`, use `/gstack-qa`, `/gstack-ship`, etc.). Disk paths are unaffected — always +use `~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. diff --git a/ship/SKILL.md.tmpl b/ship/SKILL.md.tmpl index 62842fc52..34dc3ebbf 100644 --- a/ship/SKILL.md.tmpl +++ b/ship/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: ship +name: {{SKILL_NAME}} preamble-tier: 4 version: 1.0.0 description: | diff --git a/test/gen-skill-docs.test.ts b/test/gen-skill-docs.test.ts index e0159dd85..3564434b7 100644 --- a/test/gen-skill-docs.test.ts +++ b/test/gen-skill-docs.test.ts @@ -1567,20 +1567,20 @@ describe('Codex generation (--host codex)', () => { describe('setup script validation', () => { const setupContent = fs.readFileSync(path.join(ROOT, 'setup'), 'utf-8'); - test('setup has separate link functions for Claude and Codex', () => { - expect(setupContent).toContain('link_claude_skill_dirs'); + test('setup has separate install functions for Claude and Codex', () => { + expect(setupContent).toContain('generate_claude_skill_docs'); expect(setupContent).toContain('link_codex_skill_dirs'); // Old unified function must not exist expect(setupContent).not.toMatch(/^link_skill_dirs\(\)/m); }); - test('Claude install uses link_claude_skill_dirs', () => { - // The Claude install section (section 4) should use the Claude function + test('Claude install uses generate_claude_skill_docs', () => { + // The Claude install section (section 4) should use the generate function const claudeSection = setupContent.slice( setupContent.indexOf('# 4. Install for Claude'), setupContent.indexOf('# 5. Install for Codex') ); - expect(claudeSection).toContain('link_claude_skill_dirs'); + expect(claudeSection).toContain('generate_claude_skill_docs'); expect(claudeSection).not.toContain('link_codex_skill_dirs'); }); @@ -1592,7 +1592,7 @@ describe('setup script validation', () => { ); expect(codexSection).toContain('create_codex_runtime_root'); expect(codexSection).toContain('link_codex_skill_dirs'); - expect(codexSection).not.toContain('link_claude_skill_dirs'); + expect(codexSection).not.toContain('generate_claude_skill_docs'); expect(codexSection).not.toContain('ln -snf "$GSTACK_DIR" "$CODEX_GSTACK"'); }); @@ -1626,12 +1626,13 @@ describe('setup script validation', () => { expect(fnBody).toContain('gstack*'); }); - test('link_claude_skill_dirs creates relative symlinks', () => { - // Claude links should be relative: ln -snf "gstack/skill_name" - const fnStart = setupContent.indexOf('link_claude_skill_dirs()'); - const fnEnd = setupContent.indexOf('}', setupContent.indexOf('linked[@]}', fnStart)); + test('generate_claude_skill_docs uses gen-skill-docs.ts with output-root', () => { + // Claude skill generation should call gen-skill-docs.ts with --output-root + const fnStart = setupContent.indexOf('generate_claude_skill_docs()'); + const fnEnd = setupContent.indexOf('\n}', fnStart); const fnBody = setupContent.slice(fnStart, fnEnd); - expect(fnBody).toContain('ln -snf "gstack/$skill_name"'); + expect(fnBody).toContain('gen-skill-docs.ts'); + expect(fnBody).toContain('--output-root'); }); test('setup supports --host auto|claude|codex|kiro', () => { @@ -1705,46 +1706,53 @@ describe('setup script validation', () => { // --- Symlink prefix tests (PR #503) --- - test('link_claude_skill_dirs applies gstack- prefix by default', () => { - const fnStart = setupContent.indexOf('link_claude_skill_dirs()'); - const fnEnd = setupContent.indexOf('}', setupContent.indexOf('linked[@]}', fnStart)); + test('generate_claude_skill_docs builds --prefix from SKILL_PREFIX word when set', () => { + const fnStart = setupContent.indexOf('generate_claude_skill_docs()'); + const fnEnd = setupContent.indexOf('\n}', fnStart); const fnBody = setupContent.slice(fnStart, fnEnd); expect(fnBody).toContain('SKILL_PREFIX'); - expect(fnBody).toContain('link_name="gstack-$skill_name"'); + // Uses the prefix word with a trailing dash: "--prefix ${SKILL_PREFIX}-" + expect(fnBody).toContain('--prefix ${SKILL_PREFIX}-'); + // Guard: only sets prefix_flag when SKILL_PREFIX is non-empty (no "--prefix -") + expect(fnBody).toContain('-n "$SKILL_PREFIX"'); }); - test('link_claude_skill_dirs preserves already-prefixed dirs', () => { - const fnStart = setupContent.indexOf('link_claude_skill_dirs()'); - const fnEnd = setupContent.indexOf('}', setupContent.indexOf('linked[@]}', fnStart)); - const fnBody = setupContent.slice(fnStart, fnEnd); - // gstack-* dirs should keep their name (e.g., gstack-upgrade stays gstack-upgrade) - expect(fnBody).toContain('gstack-*) link_name="$skill_name"'); + test('gen-skill-docs preserves already-prefixed dirs (gstack-upgrade stays gstack-upgrade)', () => { + // This is now handled inside gen-skill-docs.ts, not setup. + // Verify the logic exists in gen-skill-docs.ts. + const genDocsPath = path.join(ROOT, 'scripts', 'gen-skill-docs.ts'); + const genDocsContent = fs.readFileSync(genDocsPath, 'utf-8'); + expect(genDocsContent).toContain('startsWith(SKILL_PREFIX)'); }); test('setup supports --no-prefix flag', () => { expect(setupContent).toContain('--no-prefix'); - expect(setupContent).toContain('SKILL_PREFIX=0'); + // --no-prefix sets SKILL_PREFIX to empty string (no prefix word) + expect(setupContent).toContain('SKILL_PREFIX=""'); + expect(setupContent).toContain('SKILL_PREFIX_FLAG=1'); }); - test('cleanup_old_claude_symlinks removes only gstack-pointing symlinks', () => { - expect(setupContent).toContain('cleanup_old_claude_symlinks'); - const fnStart = setupContent.indexOf('cleanup_old_claude_symlinks()'); + test('cleanup_generated_skill_dirs removes gstack-generated dirs by AUTO-GENERATED header', () => { + expect(setupContent).toContain('cleanup_generated_skill_dirs'); + const fnStart = setupContent.indexOf('cleanup_generated_skill_dirs()'); const fnEnd = setupContent.indexOf('}', setupContent.indexOf('removed[@]}', fnStart)); const fnBody = setupContent.slice(fnStart, fnEnd); - // Should check readlink before removing + // Should check readlink for symlinks pointing into gstack/ expect(fnBody).toContain('readlink'); expect(fnBody).toContain('gstack/*'); - // Should skip already-prefixed dirs - expect(fnBody).toContain('gstack-*) continue'); + // Should check AUTO-GENERATED header to identify generated dirs + expect(fnBody).toContain('AUTO-GENERATED from SKILL.md.tmpl'); + // Should never remove the main gstack dir + expect(fnBody).toContain('"gstack"'); }); - test('cleanup runs before link when prefix is enabled', () => { - // In the Claude install section, cleanup should happen before linking + test('cleanup runs before generate when installing Claude skills', () => { + // In the Claude install section, cleanup should happen before generating const claudeInstallSection = setupContent.slice( setupContent.indexOf('INSTALL_CLAUDE'), - setupContent.lastIndexOf('link_claude_skill_dirs') + setupContent.lastIndexOf('generate_claude_skill_docs') ); - expect(claudeInstallSection).toContain('cleanup_old_claude_symlinks'); + expect(claudeInstallSection).toContain('cleanup_generated_skill_dirs'); }); // --- Persistent config + interactive prompt tests --- @@ -1756,7 +1764,10 @@ describe('setup script validation', () => { test('setup supports --prefix flag', () => { expect(setupContent).toContain('--prefix)'); - expect(setupContent).toContain('SKILL_PREFIX=1; SKILL_PREFIX_FLAG=1'); + // --prefix now takes a word argument (not a toggle), sets SKILL_PREFIX_FLAG=1 + expect(setupContent).toContain('SKILL_PREFIX_FLAG=1'); + // Validates the prefix word: lowercase alphanumeric, 1-10 chars + expect(setupContent).toContain('[a-z0-9]{1,10}'); }); test('--prefix and --no-prefix persist to config', () => { @@ -1766,7 +1777,9 @@ describe('setup script validation', () => { test('interactive prompt shows when no config', () => { expect(setupContent).toContain('Short names'); expect(setupContent).toContain('Namespaced'); - expect(setupContent).toContain('Choice [1/2]'); + // Now has 3 options including custom prefix + expect(setupContent).toContain('Choice [1/2/3]'); + expect(setupContent).toContain('Custom prefix'); }); test('non-TTY defaults to flat names', () => { @@ -1774,21 +1787,29 @@ describe('setup script validation', () => { expect(setupContent).toContain('-t 0'); }); - test('cleanup_prefixed_claude_symlinks exists and uses readlink', () => { - expect(setupContent).toContain('cleanup_prefixed_claude_symlinks'); - const fnStart = setupContent.indexOf('cleanup_prefixed_claude_symlinks()'); + test('universal cleanup handles both symlinks and generated directories', () => { + // cleanup_generated_skill_dirs replaces both the old prefix-specific cleanup functions + const fnStart = setupContent.indexOf('cleanup_generated_skill_dirs()'); const fnEnd = setupContent.indexOf('}', setupContent.indexOf('removed[@]}', fnStart)); const fnBody = setupContent.slice(fnStart, fnEnd); + // Handles old symlinks pointing into gstack/ expect(fnBody).toContain('readlink'); - expect(fnBody).toContain('gstack-$skill_name'); + // Handles generated dirs by checking the AUTO-GENERATED header + expect(fnBody).toContain('AUTO-GENERATED from SKILL.md.tmpl'); + // Never removes the main gstack installation dir + expect(fnBody).toContain('"gstack"'); }); - test('reverse cleanup runs before link when prefix is disabled', () => { + test('unified cleanup runs before generate for all prefix modes', () => { + // Both prefix and no-prefix modes use the same cleanup function const claudeInstallSection = setupContent.slice( setupContent.indexOf('INSTALL_CLAUDE'), - setupContent.lastIndexOf('link_claude_skill_dirs') + setupContent.lastIndexOf('generate_claude_skill_docs') ); - expect(claudeInstallSection).toContain('cleanup_prefixed_claude_symlinks'); + expect(claudeInstallSection).toContain('cleanup_generated_skill_dirs'); + // Old prefix-specific functions should no longer be in the Claude install section + expect(claudeInstallSection).not.toContain('cleanup_old_claude_symlinks'); + expect(claudeInstallSection).not.toContain('cleanup_prefixed_claude_symlinks'); }); test('welcome message references SKILL_PREFIX', () => { @@ -1957,3 +1978,77 @@ describe('codex commands must not use inline $(git rev-parse --show-toplevel) fo expect(violations).toEqual([]); }); }); + +describe('--prefix and --output-root integration', () => { + // These tests actually invoke gen-skill-docs.ts and read the output files. + // They verify the end-to-end behavior that the unit tests above can't catch: + // that the generated SKILL.md files have the correct name: field. + + test('--prefix gstack- writes name: gstack- into output-root', () => { + const outDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-prefix-test-')); + try { + const result = Bun.spawnSync( + ['bun', 'run', 'scripts/gen-skill-docs.ts', '--prefix', 'gstack-', '--output-root', outDir], + { cwd: ROOT, stdout: 'pipe', stderr: 'pipe' } + ); + expect(result.exitCode).toBe(0); + + // A normal skill: ship → gstack-ship + const shipSkill = path.join(outDir, 'gstack-ship', 'SKILL.md'); + expect(fs.existsSync(shipSkill)).toBe(true); + const shipContent = fs.readFileSync(shipSkill, 'utf-8'); + expect(shipContent).toMatch(/^name: gstack-ship$/m); + + // A normal skill: qa → gstack-qa + const qaSkill = path.join(outDir, 'gstack-qa', 'SKILL.md'); + expect(fs.existsSync(qaSkill)).toBe(true); + const qaContent = fs.readFileSync(qaSkill, 'utf-8'); + expect(qaContent).toMatch(/^name: gstack-qa$/m); + } finally { + fs.rmSync(outDir, { recursive: true, force: true }); + } + }); + + test('already-prefixed skill (gstack-upgrade) is not double-prefixed', () => { + const outDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-double-prefix-test-')); + try { + const result = Bun.spawnSync( + ['bun', 'run', 'scripts/gen-skill-docs.ts', '--prefix', 'gstack-', '--output-root', outDir], + { cwd: ROOT, stdout: 'pipe', stderr: 'pipe' } + ); + expect(result.exitCode).toBe(0); + + // gstack-upgrade must stay gstack-upgrade, not become gstack-gstack-upgrade + const upgradeSkill = path.join(outDir, 'gstack-upgrade', 'SKILL.md'); + expect(fs.existsSync(upgradeSkill)).toBe(true); + const upgradeContent = fs.readFileSync(upgradeSkill, 'utf-8'); + expect(upgradeContent).toMatch(/^name: gstack-upgrade$/m); + + // The double-prefixed path must not exist + const doublePrefix = path.join(outDir, 'gstack-gstack-upgrade'); + expect(fs.existsSync(doublePrefix)).toBe(false); + } finally { + fs.rmSync(outDir, { recursive: true, force: true }); + } + }); + + test('no --prefix writes name: (unprefixed) into output-root', () => { + const outDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-noprefix-test-')); + try { + const result = Bun.spawnSync( + ['bun', 'run', 'scripts/gen-skill-docs.ts', '--output-root', outDir], + { cwd: ROOT, stdout: 'pipe', stderr: 'pipe' } + ); + expect(result.exitCode).toBe(0); + + const shipSkill = path.join(outDir, 'ship', 'SKILL.md'); + expect(fs.existsSync(shipSkill)).toBe(true); + const shipContent = fs.readFileSync(shipSkill, 'utf-8'); + expect(shipContent).toMatch(/^name: ship$/m); + // Must not contain the prefixed name + expect(shipContent).not.toMatch(/^name: gstack-ship$/m); + } finally { + fs.rmSync(outDir, { recursive: true, force: true }); + } + }); +}); diff --git a/unfreeze/SKILL.md.tmpl b/unfreeze/SKILL.md.tmpl index 129685797..d1f15ac81 100644 --- a/unfreeze/SKILL.md.tmpl +++ b/unfreeze/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: unfreeze +name: {{SKILL_NAME}} version: 0.1.0 description: | Clear the freeze boundary set by /freeze, allowing edits to all directories