diff --git a/CHANGELOG.md b/CHANGELOG.md index d938c9f5d..cee02336e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## [0.13.7.0] - 2026-03-29 — Community Wave + +Six community fixes with 16 new tests. Telemetry off now means off everywhere. Skills are findable by name. And changing your prefix setting actually works now. + +### Fixed + +- **Telemetry off means off everywhere.** When you set telemetry to off, gstack no longer writes local JSONL analytics files. Previously "off" only stopped remote reporting. Now nothing is written anywhere. Clean trust contract. +- **`find -delete` replaced with POSIX `-exec rm`.** Safety Net and other non-GNU environments no longer choke on session cleanup. +- **No more preemptive context warnings.** `/plan-eng-review` no longer warns you about running low on context. The system handles compaction automatically. +- **Sidebar security test updated** for Write tool fallback string change. +- **`gstack-relink` no longer double-prefixes `gstack-upgrade`.** Setting `skill_prefix=true` was creating `gstack-gstack-upgrade` instead of keeping the existing name. Now matches `setup` script behavior. + +### Added + +- **Skill discoverability.** Every skill description now contains "(gstack)" so you can find gstack skills by searching in Claude Code's command palette. +- **Feature signal detection in `/ship`.** Version bump now checks for new routes, migrations, test+source pairs, and `feat/` branches. Catches MINOR-worthy changes that line count alone misses. +- **Sidebar Write tool.** Both the sidebar agent and headed-mode server now include Write in allowedTools. Write doesn't expand the attack surface beyond what Bash already provides. +- **Sidebar stderr capture.** The sidebar agent now buffers stderr and includes it in error and timeout messages instead of silently discarding it. +- **`bin/gstack-relink`** re-creates skill symlinks when you change `skill_prefix` via `gstack-config set`. No more manual `./setup` re-run needed. +- **`bin/gstack-open-url`** cross-platform URL opener (macOS: `open`, Linux: `xdg-open`, Windows: `start`). + ## [0.13.6.0] - 2026-03-29 — GStack Learns Every session now makes the next one smarter. gstack remembers patterns, pitfalls, and preferences across sessions and uses them to improve every review, plan, debug, and ship. The more you use it, the better it gets on your codebase. diff --git a/SKILL.md b/SKILL.md index ec0ed5aa5..d4840cf1b 100644 --- a/SKILL.md +++ b/SKILL.md @@ -6,7 +6,7 @@ description: | Fast headless browser for QA testing and site dogfooding. Navigate pages, interact with elements, verify state, diff before/after, take annotated screenshots, test responsive layouts, forms, uploads, dialogs, and capture bug evidence. Use when asked to open or - test a site, verify a deployment, dogfood a user flow, or file a bug with screenshots. + test a site, verify a deployment, dogfood a user flow, or file a bug with screenshots. (gstack) allowed-tools: - Bash - Read @@ -24,7 +24,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -46,7 +46,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"gstack","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"gstack","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -216,20 +218,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/SKILL.md.tmpl b/SKILL.md.tmpl index 39b6873e2..fcc0900b0 100644 --- a/SKILL.md.tmpl +++ b/SKILL.md.tmpl @@ -6,7 +6,7 @@ description: | Fast headless browser for QA testing and site dogfooding. Navigate pages, interact with elements, verify state, diff before/after, take annotated screenshots, test responsive layouts, forms, uploads, dialogs, and capture bug evidence. Use when asked to open or - test a site, verify a deployment, dogfood a user flow, or file a bug with screenshots. + test a site, verify a deployment, dogfood a user flow, or file a bug with screenshots. (gstack) allowed-tools: - Bash - Read diff --git a/VERSION b/VERSION index 3006dba97..a4029e21a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.13.6.0 +0.13.7.0 diff --git a/autoplan/SKILL.md b/autoplan/SKILL.md index 338a1af89..f827fcba8 100644 --- a/autoplan/SKILL.md +++ b/autoplan/SKILL.md @@ -10,7 +10,7 @@ description: | Use when asked to "auto review", "autoplan", "run all reviews", "review this plan automatically", or "make the decisions for me". Proactively suggest when the user has a plan file and wants to run the full review - gauntlet without answering 15-30 intermediate questions. + gauntlet without answering 15-30 intermediate questions. (gstack) benefits-from: [office-hours] allowed-tools: - Bash @@ -33,7 +33,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -55,7 +55,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"autoplan","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"autoplan","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -308,20 +310,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/autoplan/SKILL.md.tmpl b/autoplan/SKILL.md.tmpl index 5577b64bc..38ab2816e 100644 --- a/autoplan/SKILL.md.tmpl +++ b/autoplan/SKILL.md.tmpl @@ -10,7 +10,7 @@ description: | Use when asked to "auto review", "autoplan", "run all reviews", "review this plan automatically", or "make the decisions for me". Proactively suggest when the user has a plan file and wants to run the full review - gauntlet without answering 15-30 intermediate questions. + gauntlet without answering 15-30 intermediate questions. (gstack) benefits-from: [office-hours] allowed-tools: - Bash diff --git a/benchmark/SKILL.md b/benchmark/SKILL.md index 10d716737..417092269 100644 --- a/benchmark/SKILL.md +++ b/benchmark/SKILL.md @@ -7,7 +7,7 @@ description: | baselines for page load times, Core Web Vitals, and resource sizes. Compares before/after on every PR. Tracks performance trends over time. Use when: "performance", "benchmark", "page speed", "lighthouse", "web vitals", - "bundle size", "load time". + "bundle size", "load time". (gstack) allowed-tools: - Bash - Read @@ -26,7 +26,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -48,7 +48,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"benchmark","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"benchmark","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -218,20 +220,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/benchmark/SKILL.md.tmpl b/benchmark/SKILL.md.tmpl index 5149ea441..dca820142 100644 --- a/benchmark/SKILL.md.tmpl +++ b/benchmark/SKILL.md.tmpl @@ -7,7 +7,7 @@ description: | baselines for page load times, Core Web Vitals, and resource sizes. Compares before/after on every PR. Tracks performance trends over time. Use when: "performance", "benchmark", "page speed", "lighthouse", "web vitals", - "bundle size", "load time". + "bundle size", "load time". (gstack) allowed-tools: - Bash - Read diff --git a/bin/gstack-config b/bin/gstack-config index 821a342a7..08549a29d 100755 --- a/bin/gstack-config +++ b/bin/gstack-config @@ -41,6 +41,11 @@ case "${1:-}" in else echo "${KEY}: ${VALUE}" >> "$CONFIG_FILE" fi + # Auto-relink skills when prefix setting changes (skip during setup to avoid recursive call) + if [ "$KEY" = "skill_prefix" ] && [ -z "${GSTACK_SETUP_RUNNING:-}" ]; then + GSTACK_RELINK="$(dirname "$0")/gstack-relink" + [ -x "$GSTACK_RELINK" ] && "$GSTACK_RELINK" || true + fi ;; list) cat "$CONFIG_FILE" 2>/dev/null || true diff --git a/bin/gstack-open-url b/bin/gstack-open-url new file mode 100755 index 000000000..725231376 --- /dev/null +++ b/bin/gstack-open-url @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# gstack-open-url — cross-platform URL opener +# +# Usage: gstack-open-url +set -euo pipefail + +URL="${1:?Usage: gstack-open-url }" + +case "$(uname -s)" in + Darwin) open "$URL" ;; + Linux) xdg-open "$URL" 2>/dev/null || echo "$URL" ;; + MINGW*|MSYS*|CYGWIN*) start "$URL" ;; + *) echo "$URL" ;; +esac diff --git a/bin/gstack-relink b/bin/gstack-relink new file mode 100755 index 000000000..49d0ccacf --- /dev/null +++ b/bin/gstack-relink @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# gstack-relink — re-create skill symlinks based on skill_prefix config +# +# Usage: +# gstack-relink +# +# Env overrides (for testing): +# GSTACK_STATE_DIR — override ~/.gstack state directory +# GSTACK_INSTALL_DIR — override gstack install directory +# GSTACK_SKILLS_DIR — override target skills directory +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +GSTACK_CONFIG="${SCRIPT_DIR}/gstack-config" + +# Detect install dir +INSTALL_DIR="${GSTACK_INSTALL_DIR:-}" +if [ -z "$INSTALL_DIR" ]; then + if [ -d "$HOME/.claude/skills/gstack" ]; then + INSTALL_DIR="$HOME/.claude/skills/gstack" + elif [ -d "${SCRIPT_DIR}/.." ] && [ -f "${SCRIPT_DIR}/../setup" ]; then + INSTALL_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + fi +fi + +if [ -z "$INSTALL_DIR" ] || [ ! -d "$INSTALL_DIR" ]; then + echo "Error: gstack install directory not found." >&2 + echo "Run: cd ~/.claude/skills/gstack && ./setup" >&2 + exit 1 +fi + +# Detect target skills dir +SKILLS_DIR="${GSTACK_SKILLS_DIR:-$(dirname "$INSTALL_DIR")}" +[ -d "$SKILLS_DIR" ] || mkdir -p "$SKILLS_DIR" + +# Read prefix setting +PREFIX=$("$GSTACK_CONFIG" get skill_prefix 2>/dev/null || echo "false") + +# Discover skills (directories with SKILL.md, excluding meta dirs) +SKILL_COUNT=0 +for skill_dir in "$INSTALL_DIR"/*/; do + [ -d "$skill_dir" ] || continue + skill=$(basename "$skill_dir") + # Skip non-skill directories + case "$skill" in bin|browse|design|docs|extension|lib|node_modules|scripts|test|.git|.github) continue ;; esac + [ -f "$skill_dir/SKILL.md" ] || continue + + if [ "$PREFIX" = "true" ]; then + # Don't double-prefix directories already named gstack-* + case "$skill" in + gstack-*) link_name="$skill" ;; + *) link_name="gstack-$skill" ;; + esac + ln -sfn "$INSTALL_DIR/$skill" "$SKILLS_DIR/$link_name" + # Remove old flat symlink if it exists (and isn't the same as the new link) + [ "$link_name" != "$skill" ] && [ -L "$SKILLS_DIR/$skill" ] && rm -f "$SKILLS_DIR/$skill" + else + # Create flat symlink, remove gstack-* if exists + ln -sfn "$INSTALL_DIR/$skill" "$SKILLS_DIR/$skill" + # Don't remove gstack-* dirs that are their real name (e.g., gstack-upgrade) + case "$skill" in + gstack-*) ;; # Already the real name, no old prefixed link to clean + *) [ -L "$SKILLS_DIR/gstack-$skill" ] && rm -f "$SKILLS_DIR/gstack-$skill" ;; + esac + fi + SKILL_COUNT=$((SKILL_COUNT + 1)) +done + +if [ "$PREFIX" = "true" ]; then + echo "Relinked $SKILL_COUNT skills as gstack-*" +else + echo "Relinked $SKILL_COUNT skills as flat names" +fi diff --git a/browse/SKILL.md b/browse/SKILL.md index 440871c86..9448edabe 100644 --- a/browse/SKILL.md +++ b/browse/SKILL.md @@ -8,7 +8,7 @@ description: | responsive layouts, test forms and uploads, handle dialogs, and assert element states. ~100ms per command. Use when you need to test a feature, verify a deployment, dogfood a user flow, or file a bug with evidence. Use when asked to "open in browser", "test the - site", "take a screenshot", or "dogfood this". + site", "take a screenshot", or "dogfood this". (gstack) allowed-tools: - Bash - Read @@ -26,7 +26,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -48,7 +48,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"browse","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"browse","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -218,20 +220,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/browse/SKILL.md.tmpl b/browse/SKILL.md.tmpl index a11505ea6..df70a685a 100644 --- a/browse/SKILL.md.tmpl +++ b/browse/SKILL.md.tmpl @@ -8,7 +8,7 @@ description: | responsive layouts, test forms and uploads, handle dialogs, and assert element states. ~100ms per command. Use when you need to test a feature, verify a deployment, dogfood a user flow, or file a bug with evidence. Use when asked to "open in browser", "test the - site", "take a screenshot", or "dogfood this". + site", "take a screenshot", or "dogfood this". (gstack) allowed-tools: - Bash - Read diff --git a/browse/src/server.ts b/browse/src/server.ts index f3f8d68dd..99ce4e9c0 100644 --- a/browse/src/server.ts +++ b/browse/src/server.ts @@ -430,7 +430,7 @@ function spawnClaude(userMessage: string, extensionUrl?: string | null): void { const prompt = `${systemPrompt}\n\n\n${escapedMessage}\n`; const args = ['-p', prompt, '--model', 'opus', '--output-format', 'stream-json', '--verbose', - '--allowedTools', 'Bash,Read,Glob,Grep']; + '--allowedTools', 'Bash,Read,Glob,Grep,Write']; if (sidebarSession?.claudeSessionId) { args.push('--resume', sidebarSession.claudeSessionId); } diff --git a/browse/src/sidebar-agent.ts b/browse/src/sidebar-agent.ts index db5602211..644d45b05 100644 --- a/browse/src/sidebar-agent.ts +++ b/browse/src/sidebar-agent.ts @@ -161,8 +161,10 @@ async function askClaude(queueEntry: any): Promise { return new Promise((resolve) => { // Use args from queue entry (server sets --model, --allowedTools, prompt framing). // Fall back to defaults only if queue entry has no args (backward compat). + // Write doesn't expand attack surface beyond what Bash already provides. + // The security boundary is the localhost-only message path, not the tool allowlist. let claudeArgs = args || ['-p', prompt, '--output-format', 'stream-json', '--verbose', - '--allowedTools', 'Bash,Read,Glob,Grep']; + '--allowedTools', 'Bash,Read,Glob,Grep,Write']; // Validate cwd exists — queue may reference a stale worktree let effectiveCwd = cwd || process.cwd(); @@ -188,20 +190,30 @@ async function askClaude(queueEntry: any): Promise { } }); - proc.stderr.on('data', () => {}); // Claude logs to stderr, ignore + let stderrBuffer = ''; + proc.stderr.on('data', (data: Buffer) => { + stderrBuffer += data.toString(); + }); proc.on('close', (code) => { if (buffer.trim()) { try { handleStreamEvent(JSON.parse(buffer)); } catch {} } - sendEvent({ type: 'agent_done' }).then(() => { + const doneEvent: Record = { type: 'agent_done' }; + if (code !== 0 && stderrBuffer.trim()) { + doneEvent.stderr = stderrBuffer.trim().slice(-500); + } + sendEvent(doneEvent).then(() => { isProcessing = false; resolve(); }); }); proc.on('error', (err) => { - sendEvent({ type: 'agent_error', error: err.message }).then(() => { + const errorMsg = stderrBuffer.trim() + ? `${err.message}\nstderr: ${stderrBuffer.trim().slice(-500)}` + : err.message; + sendEvent({ type: 'agent_error', error: errorMsg }).then(() => { isProcessing = false; resolve(); }); @@ -211,7 +223,10 @@ async function askClaude(queueEntry: any): Promise { const timeoutMs = parseInt(process.env.SIDEBAR_AGENT_TIMEOUT || '300000', 10); setTimeout(() => { try { proc.kill(); } catch {} - sendEvent({ type: 'agent_error', error: `Timed out after ${timeoutMs / 1000}s` }).then(() => { + const timeoutMsg = stderrBuffer.trim() + ? `Timed out after ${timeoutMs / 1000}s\nstderr: ${stderrBuffer.trim().slice(-500)}` + : `Timed out after ${timeoutMs / 1000}s`; + sendEvent({ type: 'agent_error', error: timeoutMsg }).then(() => { isProcessing = false; resolve(); }); diff --git a/browse/test/sidebar-security.test.ts b/browse/test/sidebar-security.test.ts index b953f5b77..33c64b497 100644 --- a/browse/test/sidebar-security.test.ts +++ b/browse/test/sidebar-security.test.ts @@ -115,6 +115,6 @@ describe('Sidebar prompt injection defense', () => { test('sidebar-agent falls back to defaults if queue has no args', () => { // Backward compatibility: if old queue entries lack args, use defaults - expect(AGENT_SRC).toContain("'--allowedTools', 'Bash,Read,Glob,Grep'"); + expect(AGENT_SRC).toContain("'--allowedTools', 'Bash,Read,Glob,Grep,Write'"); }); }); diff --git a/canary/SKILL.md b/canary/SKILL.md index c91bf15d5..0ea349efc 100644 --- a/canary/SKILL.md +++ b/canary/SKILL.md @@ -7,7 +7,7 @@ description: | performance regressions, and page failures using the browse daemon. Takes periodic screenshots, compares against pre-deploy baselines, and alerts on anomalies. Use when: "monitor deploy", "canary", "post-deploy check", - "watch production", "verify deploy". + "watch production", "verify deploy". (gstack) allowed-tools: - Bash - Read @@ -26,7 +26,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -48,7 +48,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"canary","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"canary","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -283,20 +285,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/canary/SKILL.md.tmpl b/canary/SKILL.md.tmpl index 680b58147..412183040 100644 --- a/canary/SKILL.md.tmpl +++ b/canary/SKILL.md.tmpl @@ -7,7 +7,7 @@ description: | performance regressions, and page failures using the browse daemon. Takes periodic screenshots, compares against pre-deploy baselines, and alerts on anomalies. Use when: "monitor deploy", "canary", "post-deploy check", - "watch production", "verify deploy". + "watch production", "verify deploy". (gstack) allowed-tools: - Bash - Read diff --git a/careful/SKILL.md b/careful/SKILL.md index 7513b2937..5f9aea3f2 100644 --- a/careful/SKILL.md +++ b/careful/SKILL.md @@ -6,7 +6,7 @@ description: | force-push, git reset --hard, kubectl delete, and similar destructive operations. User can override each warning. Use when touching prod, debugging live systems, or working in a shared environment. Use when asked to "be careful", "safety mode", - "prod mode", or "careful mode". + "prod mode", or "careful mode". (gstack) allowed-tools: - Bash - Read diff --git a/careful/SKILL.md.tmpl b/careful/SKILL.md.tmpl index 33c38ef81..dd8f0ded1 100644 --- a/careful/SKILL.md.tmpl +++ b/careful/SKILL.md.tmpl @@ -6,7 +6,7 @@ description: | force-push, git reset --hard, kubectl delete, and similar destructive operations. User can override each warning. Use when touching prod, debugging live systems, or working in a shared environment. Use when asked to "be careful", "safety mode", - "prod mode", or "careful mode". + "prod mode", or "careful mode". (gstack) allowed-tools: - Bash - Read diff --git a/codex/SKILL.md b/codex/SKILL.md index a9f409cf4..a3c826212 100644 --- a/codex/SKILL.md +++ b/codex/SKILL.md @@ -7,7 +7,7 @@ description: | codex review with pass/fail gate. Challenge: adversarial mode that tries to break your code. Consult: ask codex anything with session continuity for follow-ups. The "200 IQ autistic developer" second opinion. Use when asked to "codex review", - "codex challenge", "ask codex", "second opinion", or "consult codex". + "codex challenge", "ask codex", "second opinion", or "consult codex". (gstack) allowed-tools: - Bash - Read @@ -27,7 +27,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -49,7 +49,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"codex","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"codex","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -302,20 +304,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/codex/SKILL.md.tmpl b/codex/SKILL.md.tmpl index c44480a9f..86500003c 100644 --- a/codex/SKILL.md.tmpl +++ b/codex/SKILL.md.tmpl @@ -7,7 +7,7 @@ description: | codex review with pass/fail gate. Challenge: adversarial mode that tries to break your code. Consult: ask codex anything with session continuity for follow-ups. The "200 IQ autistic developer" second opinion. Use when asked to "codex review", - "codex challenge", "ask codex", "second opinion", or "consult codex". + "codex challenge", "ask codex", "second opinion", or "consult codex". (gstack) allowed-tools: - Bash - Read diff --git a/connect-chrome/SKILL.md b/connect-chrome/SKILL.md index 58df84f99..f9529d315 100644 --- a/connect-chrome/SKILL.md +++ b/connect-chrome/SKILL.md @@ -24,7 +24,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -46,7 +46,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"connect-chrome","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"connect-chrome","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -299,20 +301,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/cso/SKILL.md b/cso/SKILL.md index 14b712f49..783a5ee01 100644 --- a/cso/SKILL.md +++ b/cso/SKILL.md @@ -8,7 +8,7 @@ description: | scanning, plus OWASP Top 10, STRIDE threat modeling, and active verification. Two modes: daily (zero-noise, 8/10 confidence gate) and comprehensive (monthly deep scan, 2/10 bar). Trend tracking across audit runs. - Use when: "security audit", "threat model", "pentest review", "OWASP", "CSO review". + Use when: "security audit", "threat model", "pentest review", "OWASP", "CSO review". (gstack) allowed-tools: - Bash - Read @@ -30,7 +30,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -52,7 +52,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"cso","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"cso","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -287,20 +289,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/cso/SKILL.md.tmpl b/cso/SKILL.md.tmpl index 5bd86a9ce..120319f65 100644 --- a/cso/SKILL.md.tmpl +++ b/cso/SKILL.md.tmpl @@ -8,7 +8,7 @@ description: | scanning, plus OWASP Top 10, STRIDE threat modeling, and active verification. Two modes: daily (zero-noise, 8/10 confidence gate) and comprehensive (monthly deep scan, 2/10 bar). Trend tracking across audit runs. - Use when: "security audit", "threat model", "pentest review", "OWASP", "CSO review". + Use when: "security audit", "threat model", "pentest review", "OWASP", "CSO review". (gstack) allowed-tools: - Bash - Read diff --git a/design-consultation/SKILL.md b/design-consultation/SKILL.md index dc5c9ec7e..41793f2e8 100644 --- a/design-consultation/SKILL.md +++ b/design-consultation/SKILL.md @@ -9,7 +9,7 @@ description: | of truth. For existing sites, use /plan-design-review to infer the system instead. Use when asked to "design system", "brand guidelines", or "create DESIGN.md". Proactively suggest when starting a new project's UI with no existing - design system or DESIGN.md. + design system or DESIGN.md. (gstack) allowed-tools: - Bash - Read @@ -31,7 +31,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -53,7 +53,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"design-consultation","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"design-consultation","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -306,20 +308,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/design-consultation/SKILL.md.tmpl b/design-consultation/SKILL.md.tmpl index 2ce7c1d3b..5f46317c8 100644 --- a/design-consultation/SKILL.md.tmpl +++ b/design-consultation/SKILL.md.tmpl @@ -9,7 +9,7 @@ description: | of truth. For existing sites, use /plan-design-review to infer the system instead. Use when asked to "design system", "brand guidelines", or "create DESIGN.md". Proactively suggest when starting a new project's UI with no existing - design system or DESIGN.md. + design system or DESIGN.md. (gstack) allowed-tools: - Bash - Read diff --git a/design-review/SKILL.md b/design-review/SKILL.md index fa1f104f8..3a69484da 100644 --- a/design-review/SKILL.md +++ b/design-review/SKILL.md @@ -9,7 +9,7 @@ description: | screenshots. For plan-mode design review (before implementation), use /plan-design-review. Use when asked to "audit the design", "visual QA", "check if it looks good", or "design polish". Proactively suggest when the user mentions visual inconsistencies or - wants to polish the look of a live site. + wants to polish the look of a live site. (gstack) allowed-tools: - Bash - Read @@ -31,7 +31,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -53,7 +53,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"design-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"design-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -306,20 +308,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/design-review/SKILL.md.tmpl b/design-review/SKILL.md.tmpl index 904a732c4..de57c217b 100644 --- a/design-review/SKILL.md.tmpl +++ b/design-review/SKILL.md.tmpl @@ -9,7 +9,7 @@ description: | screenshots. For plan-mode design review (before implementation), use /plan-design-review. Use when asked to "audit the design", "visual QA", "check if it looks good", or "design polish". Proactively suggest when the user mentions visual inconsistencies or - wants to polish the look of a live site. + wants to polish the look of a live site. (gstack) allowed-tools: - Bash - Read diff --git a/design-shotgun/SKILL.md b/design-shotgun/SKILL.md index e1e4d02d0..ac30aa5fa 100644 --- a/design-shotgun/SKILL.md +++ b/design-shotgun/SKILL.md @@ -8,7 +8,7 @@ description: | run anytime. Use when: "explore designs", "show me options", "design variants", "visual brainstorm", or "I don't like how this looks". Proactively suggest when the user describes a UI feature but hasn't seen - what it could look like. + what it could look like. (gstack) allowed-tools: - Bash - Read @@ -28,7 +28,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -50,7 +50,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"design-shotgun","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"design-shotgun","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -285,20 +287,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/design-shotgun/SKILL.md.tmpl b/design-shotgun/SKILL.md.tmpl index 436c8bc65..6581e3c62 100644 --- a/design-shotgun/SKILL.md.tmpl +++ b/design-shotgun/SKILL.md.tmpl @@ -8,7 +8,7 @@ description: | run anytime. Use when: "explore designs", "show me options", "design variants", "visual brainstorm", or "I don't like how this looks". Proactively suggest when the user describes a UI feature but hasn't seen - what it could look like. + what it could look like. (gstack) allowed-tools: - Bash - Read diff --git a/document-release/SKILL.md b/document-release/SKILL.md index f75962d7e..e7f80c9e7 100644 --- a/document-release/SKILL.md +++ b/document-release/SKILL.md @@ -7,7 +7,7 @@ description: | diff, updates README/ARCHITECTURE/CONTRIBUTING/CLAUDE.md to match what shipped, polishes CHANGELOG voice, cleans up TODOS, and optionally bumps VERSION. Use when asked to "update the docs", "sync documentation", or "post-ship docs". - Proactively suggest after a PR is merged or code is shipped. + Proactively suggest after a PR is merged or code is shipped. (gstack) allowed-tools: - Bash - Read @@ -28,7 +28,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -50,7 +50,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"document-release","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"document-release","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -285,20 +287,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/document-release/SKILL.md.tmpl b/document-release/SKILL.md.tmpl index 6b1fb7e34..b1b6f684a 100644 --- a/document-release/SKILL.md.tmpl +++ b/document-release/SKILL.md.tmpl @@ -7,7 +7,7 @@ description: | diff, updates README/ARCHITECTURE/CONTRIBUTING/CLAUDE.md to match what shipped, polishes CHANGELOG voice, cleans up TODOS, and optionally bumps VERSION. Use when asked to "update the docs", "sync documentation", or "post-ship docs". - Proactively suggest after a PR is merged or code is shipped. + Proactively suggest after a PR is merged or code is shipped. (gstack) allowed-tools: - Bash - Read diff --git a/freeze/SKILL.md b/freeze/SKILL.md index 00aaef613..abab021c7 100644 --- a/freeze/SKILL.md +++ b/freeze/SKILL.md @@ -6,7 +6,7 @@ description: | Write outside the allowed path. Use when debugging to prevent accidentally "fixing" unrelated code, or when you want to scope changes to one module. Use when asked to "freeze", "restrict edits", "only edit this folder", - or "lock down edits". + or "lock down edits". (gstack) allowed-tools: - Bash - Read diff --git a/freeze/SKILL.md.tmpl b/freeze/SKILL.md.tmpl index b2b1de53c..42329c41c 100644 --- a/freeze/SKILL.md.tmpl +++ b/freeze/SKILL.md.tmpl @@ -6,7 +6,7 @@ description: | Write outside the allowed path. Use when debugging to prevent accidentally "fixing" unrelated code, or when you want to scope changes to one module. Use when asked to "freeze", "restrict edits", "only edit this folder", - or "lock down edits". + or "lock down edits". (gstack) allowed-tools: - Bash - Read diff --git a/guard/SKILL.md b/guard/SKILL.md index f846d38a1..289b4f939 100644 --- a/guard/SKILL.md +++ b/guard/SKILL.md @@ -6,7 +6,7 @@ description: | Combines /careful (warns before rm -rf, DROP TABLE, force-push, etc.) with /freeze (blocks edits outside a specified directory). Use for maximum safety when touching prod or debugging live systems. Use when asked to "guard mode", - "full safety", "lock it down", or "maximum safety". + "full safety", "lock it down", or "maximum safety". (gstack) allowed-tools: - Bash - Read diff --git a/guard/SKILL.md.tmpl b/guard/SKILL.md.tmpl index a96108fb2..fe385c98c 100644 --- a/guard/SKILL.md.tmpl +++ b/guard/SKILL.md.tmpl @@ -6,7 +6,7 @@ description: | Combines /careful (warns before rm -rf, DROP TABLE, force-push, etc.) with /freeze (blocks edits outside a specified directory). Use for maximum safety when touching prod or debugging live systems. Use when asked to "guard mode", - "full safety", "lock it down", or "maximum safety". + "full safety", "lock it down", or "maximum safety". (gstack) allowed-tools: - Bash - Read diff --git a/investigate/SKILL.md b/investigate/SKILL.md index 365a9ca98..565cc6404 100644 --- a/investigate/SKILL.md +++ b/investigate/SKILL.md @@ -8,7 +8,7 @@ description: | Use when asked to "debug this", "fix this bug", "why is this broken", "investigate this error", or "root cause analysis". Proactively suggest when the user reports errors, unexpected behavior, or - is troubleshooting why something stopped working. + is troubleshooting why something stopped working. (gstack) allowed-tools: - Bash - Read @@ -42,7 +42,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -64,7 +64,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"investigate","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"investigate","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -299,20 +301,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/investigate/SKILL.md.tmpl b/investigate/SKILL.md.tmpl index 8ba17fb8e..4da2a708f 100644 --- a/investigate/SKILL.md.tmpl +++ b/investigate/SKILL.md.tmpl @@ -8,7 +8,7 @@ description: | Use when asked to "debug this", "fix this bug", "why is this broken", "investigate this error", or "root cause analysis". Proactively suggest when the user reports errors, unexpected behavior, or - is troubleshooting why something stopped working. + is troubleshooting why something stopped working. (gstack) allowed-tools: - Bash - Read diff --git a/land-and-deploy/SKILL.md b/land-and-deploy/SKILL.md index e36426f0f..d568c509d 100644 --- a/land-and-deploy/SKILL.md +++ b/land-and-deploy/SKILL.md @@ -6,7 +6,7 @@ description: | Land and deploy workflow. Merges the PR, waits for CI and deploy, verifies production health via canary checks. Takes over after /ship creates the PR. Use when: "merge", "land", "deploy", "merge and verify", - "land it", "ship it to production". + "land it", "ship it to production". (gstack) allowed-tools: - Bash - Read @@ -25,7 +25,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -47,7 +47,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"land-and-deploy","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"land-and-deploy","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -300,20 +302,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/land-and-deploy/SKILL.md.tmpl b/land-and-deploy/SKILL.md.tmpl index a7ac546dc..9c01fc02b 100644 --- a/land-and-deploy/SKILL.md.tmpl +++ b/land-and-deploy/SKILL.md.tmpl @@ -6,7 +6,7 @@ description: | Land and deploy workflow. Merges the PR, waits for CI and deploy, verifies production health via canary checks. Takes over after /ship creates the PR. Use when: "merge", "land", "deploy", "merge and verify", - "land it", "ship it to production". + "land it", "ship it to production". (gstack) allowed-tools: - Bash - Read diff --git a/learn/SKILL.md b/learn/SKILL.md index 254c7dcfb..67fa311e7 100644 --- a/learn/SKILL.md +++ b/learn/SKILL.md @@ -28,7 +28,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -50,7 +50,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"learn","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"learn","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -285,20 +287,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/office-hours/SKILL.md b/office-hours/SKILL.md index d624dc377..c5986b7ce 100644 --- a/office-hours/SKILL.md +++ b/office-hours/SKILL.md @@ -11,7 +11,7 @@ description: | this", "office hours", or "is this worth building". Proactively suggest when the user describes a new product idea or is exploring whether something is worth building — before any code is written. - Use before /plan-ceo-review or /plan-eng-review. + Use before /plan-ceo-review or /plan-eng-review. (gstack) allowed-tools: - Bash - Read @@ -33,7 +33,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -55,7 +55,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"office-hours","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"office-hours","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -308,20 +310,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/office-hours/SKILL.md.tmpl b/office-hours/SKILL.md.tmpl index 358f82900..1e340cf97 100644 --- a/office-hours/SKILL.md.tmpl +++ b/office-hours/SKILL.md.tmpl @@ -11,7 +11,7 @@ description: | this", "office hours", or "is this worth building". Proactively suggest when the user describes a new product idea or is exploring whether something is worth building — before any code is written. - Use before /plan-ceo-review or /plan-eng-review. + Use before /plan-ceo-review or /plan-eng-review. (gstack) allowed-tools: - Bash - Read diff --git a/package.json b/package.json index 037d0358a..750b20e85 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "0.13.6.0", + "version": "0.13.7.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 0090752c3..40d03ef6e 100644 --- a/plan-ceo-review/SKILL.md +++ b/plan-ceo-review/SKILL.md @@ -10,7 +10,7 @@ description: | Use when asked to "think bigger", "expand scope", "strategy review", "rethink this", or "is this ambitious enough". Proactively suggest when the user is questioning scope or ambition of a plan, - or when the plan feels like it could be thinking bigger. + or when the plan feels like it could be thinking bigger. (gstack) benefits-from: [office-hours] allowed-tools: - Read @@ -31,7 +31,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -53,7 +53,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"plan-ceo-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"plan-ceo-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -306,20 +308,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/plan-ceo-review/SKILL.md.tmpl b/plan-ceo-review/SKILL.md.tmpl index d32a076ee..d0f747642 100644 --- a/plan-ceo-review/SKILL.md.tmpl +++ b/plan-ceo-review/SKILL.md.tmpl @@ -10,7 +10,7 @@ description: | Use when asked to "think bigger", "expand scope", "strategy review", "rethink this", or "is this ambitious enough". Proactively suggest when the user is questioning scope or ambition of a plan, - or when the plan feels like it could be thinking bigger. + or when the plan feels like it could be thinking bigger. (gstack) benefits-from: [office-hours] allowed-tools: - Read diff --git a/plan-design-review/SKILL.md b/plan-design-review/SKILL.md index a6be97823..452537cbe 100644 --- a/plan-design-review/SKILL.md +++ b/plan-design-review/SKILL.md @@ -9,7 +9,7 @@ description: | visual audits, use /design-review. Use when asked to "review the design plan" or "design critique". Proactively suggest when the user has a plan with UI/UX components that - should be reviewed before implementation. + should be reviewed before implementation. (gstack) allowed-tools: - Read - Edit @@ -29,7 +29,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -51,7 +51,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"plan-design-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"plan-design-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -304,20 +306,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/plan-design-review/SKILL.md.tmpl b/plan-design-review/SKILL.md.tmpl index cfafa6e6a..2edfe3795 100644 --- a/plan-design-review/SKILL.md.tmpl +++ b/plan-design-review/SKILL.md.tmpl @@ -9,7 +9,7 @@ description: | visual audits, use /design-review. Use when asked to "review the design plan" or "design critique". Proactively suggest when the user has a plan with UI/UX components that - should be reviewed before implementation. + should be reviewed before implementation. (gstack) allowed-tools: - Read - Edit diff --git a/plan-eng-review/SKILL.md b/plan-eng-review/SKILL.md index 52b6993d8..109f6b2be 100644 --- a/plan-eng-review/SKILL.md +++ b/plan-eng-review/SKILL.md @@ -8,7 +8,7 @@ description: | issues interactively with opinionated recommendations. Use when asked to "review the architecture", "engineering review", or "lock in the plan". Proactively suggest when the user has a plan or design doc and is about to - start coding — to catch architecture issues before implementation. + start coding — to catch architecture issues before implementation. (gstack) benefits-from: [office-hours] allowed-tools: - Read @@ -30,7 +30,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -52,7 +52,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"plan-eng-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"plan-eng-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -305,20 +307,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer @@ -361,7 +365,7 @@ plan's living status. Review this plan thoroughly before making any code changes. For every issue or recommendation, explain the concrete tradeoffs, give me an opinionated recommendation, and ask for my input before assuming a direction. ## Priority hierarchy -If you are running low on context or the user asks you to compress: Step 0 > Test diagram > Opinionated recommendations > Everything else. Never skip Step 0 or the test diagram. +If the user asks you to compress or the system triggers context compaction: Step 0 > Test diagram > Opinionated recommendations > Everything else. Never skip Step 0 or the test diagram. Do not preemptively warn about context limits -- the system handles compaction automatically. ## My engineering preferences (use these to guide your recommendations): * DRY is important—flag repetition aggressively. diff --git a/plan-eng-review/SKILL.md.tmpl b/plan-eng-review/SKILL.md.tmpl index 415315a83..f15fc7f58 100644 --- a/plan-eng-review/SKILL.md.tmpl +++ b/plan-eng-review/SKILL.md.tmpl @@ -8,7 +8,7 @@ description: | issues interactively with opinionated recommendations. Use when asked to "review the architecture", "engineering review", or "lock in the plan". Proactively suggest when the user has a plan or design doc and is about to - start coding — to catch architecture issues before implementation. + start coding — to catch architecture issues before implementation. (gstack) benefits-from: [office-hours] allowed-tools: - Read @@ -27,7 +27,7 @@ allowed-tools: Review this plan thoroughly before making any code changes. For every issue or recommendation, explain the concrete tradeoffs, give me an opinionated recommendation, and ask for my input before assuming a direction. ## Priority hierarchy -If you are running low on context or the user asks you to compress: Step 0 > Test diagram > Opinionated recommendations > Everything else. Never skip Step 0 or the test diagram. +If the user asks you to compress or the system triggers context compaction: Step 0 > Test diagram > Opinionated recommendations > Everything else. Never skip Step 0 or the test diagram. Do not preemptively warn about context limits -- the system handles compaction automatically. ## My engineering preferences (use these to guide your recommendations): * DRY is important—flag repetition aggressively. diff --git a/qa-only/SKILL.md b/qa-only/SKILL.md index 3aa960427..b88226141 100644 --- a/qa-only/SKILL.md +++ b/qa-only/SKILL.md @@ -7,7 +7,7 @@ description: | structured report with health score, screenshots, and repro steps — but never fixes anything. Use when asked to "just report bugs", "qa report only", or "test but don't fix". For the full test-fix-verify loop, use /qa instead. - Proactively suggest when the user wants a bug report without any code changes. + Proactively suggest when the user wants a bug report without any code changes. (gstack) allowed-tools: - Bash - Read @@ -26,7 +26,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -48,7 +48,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"qa-only","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"qa-only","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -301,20 +303,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/qa-only/SKILL.md.tmpl b/qa-only/SKILL.md.tmpl index 0bb59c0c0..d9fc96585 100644 --- a/qa-only/SKILL.md.tmpl +++ b/qa-only/SKILL.md.tmpl @@ -7,7 +7,7 @@ description: | structured report with health score, screenshots, and repro steps — but never fixes anything. Use when asked to "just report bugs", "qa report only", or "test but don't fix". For the full test-fix-verify loop, use /qa instead. - Proactively suggest when the user wants a bug report without any code changes. + Proactively suggest when the user wants a bug report without any code changes. (gstack) allowed-tools: - Bash - Read diff --git a/qa/SKILL.md b/qa/SKILL.md index 89d281e4f..f91207782 100644 --- a/qa/SKILL.md +++ b/qa/SKILL.md @@ -10,7 +10,7 @@ description: | Proactively suggest when the user says a feature is ready for testing or asks "does this work?". Three tiers: Quick (critical/high only), Standard (+ medium), Exhaustive (+ cosmetic). Produces before/after health scores, - fix evidence, and a ship-readiness summary. For report-only mode, use /qa-only. + fix evidence, and a ship-readiness summary. For report-only mode, use /qa-only. (gstack) allowed-tools: - Bash - Read @@ -32,7 +32,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -54,7 +54,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"qa","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"qa","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -307,20 +309,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/qa/SKILL.md.tmpl b/qa/SKILL.md.tmpl index 0283ffc7c..20f70ef94 100644 --- a/qa/SKILL.md.tmpl +++ b/qa/SKILL.md.tmpl @@ -10,7 +10,7 @@ description: | Proactively suggest when the user says a feature is ready for testing or asks "does this work?". Three tiers: Quick (critical/high only), Standard (+ medium), Exhaustive (+ cosmetic). Produces before/after health scores, - fix evidence, and a ship-readiness summary. For report-only mode, use /qa-only. + fix evidence, and a ship-readiness summary. For report-only mode, use /qa-only. (gstack) allowed-tools: - Bash - Read diff --git a/retro/SKILL.md b/retro/SKILL.md index 41058c8c4..7f4511586 100644 --- a/retro/SKILL.md +++ b/retro/SKILL.md @@ -7,7 +7,7 @@ description: | and code quality metrics with persistent history and trend tracking. Team-aware: breaks down per-person contributions with praise and growth areas. Use when asked to "weekly retro", "what did we ship", or "engineering retrospective". - Proactively suggest at the end of a work week or sprint. + Proactively suggest at the end of a work week or sprint. (gstack) allowed-tools: - Bash - Read @@ -26,7 +26,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -48,7 +48,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"retro","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"retro","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -283,20 +285,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/retro/SKILL.md.tmpl b/retro/SKILL.md.tmpl index b1b44ee1a..5b201cf66 100644 --- a/retro/SKILL.md.tmpl +++ b/retro/SKILL.md.tmpl @@ -7,7 +7,7 @@ description: | and code quality metrics with persistent history and trend tracking. Team-aware: breaks down per-person contributions with praise and growth areas. Use when asked to "weekly retro", "what did we ship", or "engineering retrospective". - Proactively suggest at the end of a work week or sprint. + Proactively suggest at the end of a work week or sprint. (gstack) allowed-tools: - Bash - Read diff --git a/review/SKILL.md b/review/SKILL.md index 52560d774..462123a66 100644 --- a/review/SKILL.md +++ b/review/SKILL.md @@ -6,7 +6,7 @@ description: | Pre-landing PR review. Analyzes diff against the base branch for SQL safety, LLM trust boundary violations, conditional side effects, and other structural issues. Use when asked to "review this PR", "code review", "pre-landing review", or "check my diff". - Proactively suggest when the user is about to merge or land code changes. + Proactively suggest when the user is about to merge or land code changes. (gstack) allowed-tools: - Bash - Read @@ -29,7 +29,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -51,7 +51,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -304,20 +306,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/review/SKILL.md.tmpl b/review/SKILL.md.tmpl index fa14f26a7..b748483a0 100644 --- a/review/SKILL.md.tmpl +++ b/review/SKILL.md.tmpl @@ -6,7 +6,7 @@ description: | Pre-landing PR review. Analyzes diff against the base branch for SQL safety, LLM trust boundary violations, conditional side effects, and other structural issues. Use when asked to "review this PR", "code review", "pre-landing review", or "check my diff". - Proactively suggest when the user is about to merge or land code changes. + Proactively suggest when the user is about to merge or land code changes. (gstack) allowed-tools: - Bash - Read diff --git a/scripts/resolvers/preamble.ts b/scripts/resolvers/preamble.ts index aa0441a20..cf88325a3 100644 --- a/scripts/resolvers/preamble.ts +++ b/scripts/resolvers/preamble.ts @@ -8,8 +8,9 @@ import type { TemplateContext } from './types'; * repo mode detection, and telemetry. * * Telemetry data flow: - * 1. Always: local JSONL append to ~/.gstack/analytics/ (inline, inspectable) + * 1. If _TEL != "off": local JSONL append to ~/.gstack/analytics/ (inline, inspectable) * 2. If _TEL != "off" AND binary exists: gstack-telemetry-log for remote reporting + * When telemetry is off, nothing is written anywhere. Clean trust contract. */ function generatePreambleBash(ctx: TemplateContext): string { @@ -32,7 +33,7 @@ ${runtimeRoot}_UPD=$(${ctx.paths.binDir}/gstack-update-check 2>/dev/null || ${ct mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(${ctx.paths.binDir}/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(${ctx.paths.binDir}/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -54,7 +55,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: \${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"${ctx.skillName}","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "\${_TEL:-off}" != "off" ]; then + echo '{"skill":"${ctx.skillName}","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -387,20 +390,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \\ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \\ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \\ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \\ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi \`\`\` Replace \`SKILL_NAME\` with the actual skill name from frontmatter, \`OUTCOME\` with success/error/abort, and \`USED_BROWSE\` with true/false based on whether \`$B\` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/setup b/setup index b9260713f..85d956726 100755 --- a/setup +++ b/setup @@ -46,6 +46,7 @@ esac # ─── Resolve skill prefix preference ───────────────────────── # Priority: CLI flag > saved config > interactive prompt (or flat default for non-TTY) GSTACK_CONFIG="$SOURCE_GSTACK_DIR/bin/gstack-config" +export GSTACK_SETUP_RUNNING=1 # Prevent gstack-config post-set hook from triggering relink mid-setup if [ "$SKILL_PREFIX_FLAG" -eq 0 ]; then _saved_prefix="$("$GSTACK_CONFIG" get skill_prefix 2>/dev/null || true)" if [ "$_saved_prefix" = "true" ]; then diff --git a/setup-browser-cookies/SKILL.md b/setup-browser-cookies/SKILL.md index 3272d6135..824d2059d 100644 --- a/setup-browser-cookies/SKILL.md +++ b/setup-browser-cookies/SKILL.md @@ -6,7 +6,7 @@ description: | Import cookies from your real Chromium browser into the headless browse session. Opens an interactive picker UI where you select which cookie domains to import. Use before QA testing authenticated pages. Use when asked to "import cookies", - "login to the site", or "authenticate the browser". + "login to the site", or "authenticate the browser". (gstack) allowed-tools: - Bash - Read @@ -23,7 +23,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -45,7 +45,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"setup-browser-cookies","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"setup-browser-cookies","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -215,20 +217,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/setup-browser-cookies/SKILL.md.tmpl b/setup-browser-cookies/SKILL.md.tmpl index 88b1f5533..f3b72b714 100644 --- a/setup-browser-cookies/SKILL.md.tmpl +++ b/setup-browser-cookies/SKILL.md.tmpl @@ -6,7 +6,7 @@ description: | Import cookies from your real Chromium browser into the headless browse session. Opens an interactive picker UI where you select which cookie domains to import. Use before QA testing authenticated pages. Use when asked to "import cookies", - "login to the site", or "authenticate the browser". + "login to the site", or "authenticate the browser". (gstack) allowed-tools: - Bash - Read diff --git a/setup-deploy/SKILL.md b/setup-deploy/SKILL.md index 3c353e912..f0879c966 100644 --- a/setup-deploy/SKILL.md +++ b/setup-deploy/SKILL.md @@ -29,7 +29,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -51,7 +51,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"setup-deploy","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"setup-deploy","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -286,20 +288,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer diff --git a/ship/SKILL.md b/ship/SKILL.md index a4ff1bd2b..4ce665fb0 100644 --- a/ship/SKILL.md +++ b/ship/SKILL.md @@ -3,8 +3,10 @@ name: ship preamble-tier: 4 version: 1.0.0 description: | - Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION, update CHANGELOG, commit, push, create PR. Use when asked to "ship", "deploy", "push to main", "create a PR", or "merge and push". - Proactively suggest when the user says code is ready or asks about deploying. + Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION, + update CHANGELOG, commit, push, create PR. Use when asked to "ship", "deploy", + "push to main", "create a PR", or "merge and push". + Proactively suggest when the user says code is ready or asks about deploying. (gstack) allowed-tools: - Bash - Read @@ -27,7 +29,7 @@ _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/sk mkdir -p ~/.gstack/sessions touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') -find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") @@ -49,7 +51,9 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -echo '{"skill":"ship","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +if [ "${_TEL:-off}" != "off" ]; then + echo '{"skill":"ship","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -302,20 +306,22 @@ Run this bash: _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true -# Local analytics (always available, no binary needed) -echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true -# Remote telemetry (opt-in, requires binary) -if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log \ - --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ - --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +# Local + remote telemetry (both gated by _TEL setting) +if [ "$_TEL" != "off" ]; then + echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true + if [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & + fi fi ``` Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. -If you cannot determine the outcome, use "unknown". The local JSONL always logs. The -remote binary only runs if telemetry is not off and the binary exists. +If you cannot determine the outcome, use "unknown". Both local JSONL and remote +telemetry only run if telemetry is not off. The remote binary additionally requires +the binary to exist. ## Plan Status Footer @@ -1701,9 +1707,10 @@ already knows. A good test: would this insight save time in a future session? If 2. **Auto-decide the bump level based on the diff:** - Count lines changed (`git diff origin/...HEAD --stat | tail -1`) + - Check for feature signals: new route/page files (e.g. `app/*/page.tsx`, `pages/*.ts`), new DB migration/schema files, new test files alongside new source files, or branch name starting with `feat/` - **MICRO** (4th digit): < 50 lines changed, trivial tweaks, typos, config - - **PATCH** (3rd digit): 50+ lines changed, bug fixes, small-medium features - - **MINOR** (2nd digit): **ASK the user** — only for major features or significant architectural changes + - **PATCH** (3rd digit): 50+ lines changed, no feature signals detected + - **MINOR** (2nd digit): **ASK the user** if ANY feature signal is detected, OR 500+ lines changed, OR new modules/packages added - **MAJOR** (1st digit): **ASK the user** — only for milestones or breaking changes 3. Compute the new version: diff --git a/ship/SKILL.md.tmpl b/ship/SKILL.md.tmpl index 98e2d8eab..7c7f1b2b1 100644 --- a/ship/SKILL.md.tmpl +++ b/ship/SKILL.md.tmpl @@ -3,8 +3,10 @@ name: ship preamble-tier: 4 version: 1.0.0 description: | - Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION, update CHANGELOG, commit, push, create PR. Use when asked to "ship", "deploy", "push to main", "create a PR", or "merge and push". - Proactively suggest when the user says code is ready or asks about deploying. + Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION, + update CHANGELOG, commit, push, create PR. Use when asked to "ship", "deploy", + "push to main", "create a PR", or "merge and push". + Proactively suggest when the user says code is ready or asks about deploying. (gstack) allowed-tools: - Bash - Read @@ -329,9 +331,10 @@ For each classified comment: 2. **Auto-decide the bump level based on the diff:** - Count lines changed (`git diff origin/...HEAD --stat | tail -1`) + - Check for feature signals: new route/page files (e.g. `app/*/page.tsx`, `pages/*.ts`), new DB migration/schema files, new test files alongside new source files, or branch name starting with `feat/` - **MICRO** (4th digit): < 50 lines changed, trivial tweaks, typos, config - - **PATCH** (3rd digit): 50+ lines changed, bug fixes, small-medium features - - **MINOR** (2nd digit): **ASK the user** — only for major features or significant architectural changes + - **PATCH** (3rd digit): 50+ lines changed, no feature signals detected + - **MINOR** (2nd digit): **ASK the user** if ANY feature signal is detected, OR 500+ lines changed, OR new modules/packages added - **MAJOR** (1st digit): **ASK the user** — only for milestones or breaking changes 3. Compute the new version: diff --git a/test/gen-skill-docs.test.ts b/test/gen-skill-docs.test.ts index 27672ede8..21aebb278 100644 --- a/test/gen-skill-docs.test.ts +++ b/test/gen-skill-docs.test.ts @@ -2036,6 +2036,100 @@ describe('telemetry', () => { }); }); +describe('community fixes wave', () => { + // Helper to get all generated SKILL.md files + function getAllSkillMds(): Array<{ name: string; content: string }> { + const results: Array<{ name: string; content: string }> = []; + const rootPath = path.join(ROOT, 'SKILL.md'); + if (fs.existsSync(rootPath)) { + results.push({ name: 'root', content: fs.readFileSync(rootPath, 'utf-8') }); + } + for (const entry of fs.readdirSync(ROOT, { withFileTypes: true })) { + if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules') continue; + const skillPath = path.join(ROOT, entry.name, 'SKILL.md'); + if (fs.existsSync(skillPath)) { + results.push({ name: entry.name, content: fs.readFileSync(skillPath, 'utf-8') }); + } + } + return results; + } + + // #594 — Discoverability: every SKILL.md.tmpl description contains "gstack" + test('every SKILL.md.tmpl description contains "gstack"', () => { + for (const skill of ALL_SKILLS) { + const tmplPath = skill.dir === '.' ? path.join(ROOT, 'SKILL.md.tmpl') : path.join(ROOT, skill.dir, 'SKILL.md.tmpl'); + const content = fs.readFileSync(tmplPath, 'utf-8'); + const desc = extractDescription(content); + expect(desc.toLowerCase()).toContain('gstack'); + } + }); + + // #594 — Discoverability: first line of each description is under 120 chars + test('every SKILL.md.tmpl description first line is under 120 chars', () => { + for (const skill of ALL_SKILLS) { + const tmplPath = skill.dir === '.' ? path.join(ROOT, 'SKILL.md.tmpl') : path.join(ROOT, skill.dir, 'SKILL.md.tmpl'); + const content = fs.readFileSync(tmplPath, 'utf-8'); + const desc = extractDescription(content); + const firstLine = desc.split('\n')[0]; + expect(firstLine.length).toBeLessThanOrEqual(120); + } + }); + + // #573 — Feature signals: ship/SKILL.md contains feature signal detection + test('ship/SKILL.md contains feature signal detection in Step 4', () => { + const content = fs.readFileSync(path.join(ROOT, 'ship', 'SKILL.md'), 'utf-8'); + expect(content.toLowerCase()).toContain('feature signal'); + }); + + // #510 — Context warnings: no SKILL.md contains "running low on context" + test('no generated SKILL.md contains "running low on context"', () => { + const skills = getAllSkillMds(); + for (const { name, content } of skills) { + expect(content).not.toContain('running low on context'); + } + }); + + // #510 — Context warnings: plan-eng-review has explicit anti-warning + test('plan-eng-review/SKILL.md contains "Do not preemptively warn"', () => { + const content = fs.readFileSync(path.join(ROOT, 'plan-eng-review', 'SKILL.md'), 'utf-8'); + expect(content).toContain('Do not preemptively warn'); + }); + + // #474 — Safety Net: no SKILL.md uses find with -delete + test('no generated SKILL.md contains find with -delete flag', () => { + const skills = getAllSkillMds(); + for (const { name, content } of skills) { + // Match find commands that use -delete (but not prose mentioning the word "delete") + const lines = content.split('\n'); + for (const line of lines) { + if (line.includes('find ') && line.includes('-delete')) { + throw new Error(`${name}/SKILL.md contains find with -delete: ${line.trim()}`); + } + } + } + }); + + // #467 — Telemetry: preamble JSONL writes are gated by telemetry setting + test('preamble JSONL writes are inside telemetry conditional', () => { + const preamble = fs.readFileSync(path.join(ROOT, 'scripts/resolvers/preamble.ts'), 'utf-8'); + // Find all skill-usage.jsonl write lines + const lines = preamble.split('\n'); + for (let i = 0; i < lines.length; i++) { + if (lines[i].includes('skill-usage.jsonl') && lines[i].includes('>>')) { + // Look backwards for a telemetry conditional within 5 lines + let foundConditional = false; + for (let j = i - 1; j >= Math.max(0, i - 5); j--) { + if (lines[j].includes('_TEL') && lines[j].includes('off')) { + foundConditional = true; + break; + } + } + expect(foundConditional).toBe(true); + } + } + }); +}); + describe('codex commands must not use inline $(git rev-parse --show-toplevel) for cwd', () => { // Regression test: inline $(git rev-parse --show-toplevel) in codex exec -C // or codex review without cd evaluates in whatever cwd the background shell diff --git a/test/relink.test.ts b/test/relink.test.ts new file mode 100644 index 000000000..39af8891b --- /dev/null +++ b/test/relink.test.ts @@ -0,0 +1,152 @@ +import { describe, test, expect, beforeEach, afterEach } from 'bun:test'; +import { execSync } from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; + +const ROOT = path.resolve(import.meta.dir, '..'); +const BIN = path.join(ROOT, 'bin'); + +let tmpDir: string; +let skillsDir: string; +let installDir: string; + +function run(cmd: string, env: Record = {}, expectFail = false): string { + try { + return execSync(cmd, { + cwd: ROOT, + env: { ...process.env, GSTACK_STATE_DIR: tmpDir, ...env }, + encoding: 'utf-8', + timeout: 10000, + stdio: ['pipe', 'pipe', 'pipe'], + }).trim(); + } catch (e: any) { + if (expectFail) return (e.stderr || e.stdout || '').toString().trim(); + throw e; + } +} + +// Create a mock gstack install directory with skill subdirs +function setupMockInstall(skills: string[]): void { + installDir = path.join(tmpDir, 'gstack-install'); + skillsDir = path.join(tmpDir, 'skills'); + fs.mkdirSync(installDir, { recursive: true }); + fs.mkdirSync(skillsDir, { recursive: true }); + + // Copy the real gstack-config and gstack-relink to the mock install + const mockBin = path.join(installDir, 'bin'); + fs.mkdirSync(mockBin, { recursive: true }); + fs.copyFileSync(path.join(BIN, 'gstack-config'), path.join(mockBin, 'gstack-config')); + fs.chmodSync(path.join(mockBin, 'gstack-config'), 0o755); + if (fs.existsSync(path.join(BIN, 'gstack-relink'))) { + fs.copyFileSync(path.join(BIN, 'gstack-relink'), path.join(mockBin, 'gstack-relink')); + fs.chmodSync(path.join(mockBin, 'gstack-relink'), 0o755); + } + + // Create mock skill directories + for (const skill of skills) { + fs.mkdirSync(path.join(installDir, skill), { recursive: true }); + fs.writeFileSync(path.join(installDir, skill, 'SKILL.md'), `# ${skill}`); + } +} + +beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-relink-test-')); +}); + +afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); +}); + +describe('gstack-relink (#578)', () => { + // Test 11: prefixed symlinks when skill_prefix=true + test('creates gstack-* symlinks when skill_prefix=true', () => { + setupMockInstall(['qa', 'ship', 'review']); + // Set config to prefix mode + run(`${path.join(installDir, 'bin', 'gstack-config')} set skill_prefix true`); + // Run relink with env pointing to the mock install + const output = run(`${path.join(installDir, 'bin', 'gstack-relink')}`, { + GSTACK_INSTALL_DIR: installDir, + GSTACK_SKILLS_DIR: skillsDir, + }); + // Verify gstack-* symlinks exist + expect(fs.existsSync(path.join(skillsDir, 'gstack-qa'))).toBe(true); + expect(fs.existsSync(path.join(skillsDir, 'gstack-ship'))).toBe(true); + expect(fs.existsSync(path.join(skillsDir, 'gstack-review'))).toBe(true); + expect(output).toContain('gstack-'); + }); + + // Test 12: flat symlinks when skill_prefix=false + test('creates flat symlinks when skill_prefix=false', () => { + setupMockInstall(['qa', 'ship', 'review']); + run(`${path.join(installDir, 'bin', 'gstack-config')} set skill_prefix false`); + const output = run(`${path.join(installDir, 'bin', 'gstack-relink')}`, { + GSTACK_INSTALL_DIR: installDir, + GSTACK_SKILLS_DIR: skillsDir, + }); + expect(fs.existsSync(path.join(skillsDir, 'qa'))).toBe(true); + expect(fs.existsSync(path.join(skillsDir, 'ship'))).toBe(true); + expect(fs.existsSync(path.join(skillsDir, 'review'))).toBe(true); + expect(output).toContain('flat'); + }); + + // Test 13: cleans stale symlinks from opposite mode + test('cleans up stale symlinks from opposite mode', () => { + setupMockInstall(['qa', 'ship']); + // Create prefixed symlinks first + run(`${path.join(installDir, 'bin', 'gstack-config')} set skill_prefix true`); + run(`${path.join(installDir, 'bin', 'gstack-relink')}`, { + GSTACK_INSTALL_DIR: installDir, + GSTACK_SKILLS_DIR: skillsDir, + }); + expect(fs.existsSync(path.join(skillsDir, 'gstack-qa'))).toBe(true); + + // Switch to flat mode + run(`${path.join(installDir, 'bin', 'gstack-config')} set skill_prefix false`); + run(`${path.join(installDir, 'bin', 'gstack-relink')}`, { + GSTACK_INSTALL_DIR: installDir, + GSTACK_SKILLS_DIR: skillsDir, + }); + + // Flat symlinks should exist, prefixed should be gone + expect(fs.existsSync(path.join(skillsDir, 'qa'))).toBe(true); + expect(fs.existsSync(path.join(skillsDir, 'gstack-qa'))).toBe(false); + }); + + // Test 14: error when install dir missing + test('prints error when install dir missing', () => { + const output = run(`${BIN}/gstack-relink`, { + GSTACK_INSTALL_DIR: '/nonexistent/path/gstack', + GSTACK_SKILLS_DIR: '/nonexistent/path/skills', + }, true); + expect(output).toContain('setup'); + }); + + // Test: gstack-upgrade does NOT get double-prefixed + test('does not double-prefix gstack-upgrade directory', () => { + setupMockInstall(['qa', 'ship', 'gstack-upgrade']); + run(`${path.join(installDir, 'bin', 'gstack-config')} set skill_prefix true`); + run(`${path.join(installDir, 'bin', 'gstack-relink')}`, { + GSTACK_INSTALL_DIR: installDir, + GSTACK_SKILLS_DIR: skillsDir, + }); + // gstack-upgrade should keep its name, NOT become gstack-gstack-upgrade + expect(fs.existsSync(path.join(skillsDir, 'gstack-upgrade'))).toBe(true); + expect(fs.existsSync(path.join(skillsDir, 'gstack-gstack-upgrade'))).toBe(false); + // Regular skills still get prefixed + expect(fs.existsSync(path.join(skillsDir, 'gstack-qa'))).toBe(true); + }); + + // Test 15: gstack-config set skill_prefix triggers relink + test('gstack-config set skill_prefix triggers relink', () => { + setupMockInstall(['qa', 'ship']); + // Run gstack-config set which should auto-trigger relink + run(`${path.join(installDir, 'bin', 'gstack-config')} set skill_prefix true`, { + GSTACK_INSTALL_DIR: installDir, + GSTACK_SKILLS_DIR: skillsDir, + }); + // If relink was triggered, symlinks should exist + expect(fs.existsSync(path.join(skillsDir, 'gstack-qa'))).toBe(true); + expect(fs.existsSync(path.join(skillsDir, 'gstack-ship'))).toBe(true); + }); +}); diff --git a/test/skill-validation.test.ts b/test/skill-validation.test.ts index 7bb163d84..46398d5ac 100644 --- a/test/skill-validation.test.ts +++ b/test/skill-validation.test.ts @@ -1547,3 +1547,30 @@ describe('Test failure triage in ship skill', () => { expect(content).toContain('In-branch test failures'); }); }); + +describe('sidebar agent (#584)', () => { + // #584 — Sidebar Write: sidebar-agent.ts allowedTools includes Write + test('sidebar-agent.ts allowedTools includes Write', () => { + const content = fs.readFileSync(path.join(ROOT, 'browse', 'src', 'sidebar-agent.ts'), 'utf-8'); + // Find the allowedTools line in the askClaude function + const match = content.match(/--allowedTools['"]\s*,\s*['"]([^'"]+)['"]/); + expect(match).not.toBeNull(); + expect(match![1]).toContain('Write'); + }); + + // #584 — Server Write: server.ts allowedTools includes Write (DRY parity) + test('server.ts allowedTools includes Write', () => { + const content = fs.readFileSync(path.join(ROOT, 'browse', 'src', 'server.ts'), 'utf-8'); + // Find the sidebar allowedTools in the headed-mode path + const match = content.match(/--allowedTools['"]\s*,\s*['"]([^'"]+)['"]/); + expect(match).not.toBeNull(); + expect(match![1]).toContain('Write'); + }); + + // #584 — Sidebar stderr: stderr handler is not empty + test('sidebar-agent.ts stderr handler is not empty', () => { + const content = fs.readFileSync(path.join(ROOT, 'browse', 'src', 'sidebar-agent.ts'), 'utf-8'); + // The stderr handler should NOT be an empty arrow function + expect(content).not.toContain("proc.stderr.on('data', () => {})"); + }); +}); diff --git a/test/telemetry.test.ts b/test/telemetry.test.ts index dd63509f6..96bdf54c7 100644 --- a/test/telemetry.test.ts +++ b/test/telemetry.test.ts @@ -396,3 +396,25 @@ describe('gstack-community-dashboard', () => { expect(output).not.toContain('Supabase not configured'); }); }); + +describe('preamble telemetry gating (#467)', () => { + test('preamble source does not write JSONL unconditionally', () => { + const preamble = fs.readFileSync(path.join(ROOT, 'scripts', 'resolvers', 'preamble.ts'), 'utf-8'); + const lines = preamble.split('\n'); + for (let i = 0; i < lines.length; i++) { + if (lines[i].includes('skill-usage.jsonl') && lines[i].includes('>>')) { + // Each JSONL write must be inside a _TEL conditional (within 5 lines above) + let foundConditional = false; + for (let j = i - 1; j >= Math.max(0, i - 5); j--) { + if (lines[j].includes('_TEL') && lines[j].includes('off')) { + foundConditional = true; + break; + } + } + if (!foundConditional) { + throw new Error(`Unconditional JSONL write at preamble.ts line ${i + 1}: ${lines[i].trim()}`); + } + } + } + }); +}); diff --git a/unfreeze/SKILL.md b/unfreeze/SKILL.md index d4ad37e2d..0d265f0d1 100644 --- a/unfreeze/SKILL.md +++ b/unfreeze/SKILL.md @@ -5,7 +5,7 @@ description: | Clear the freeze boundary set by /freeze, allowing edits to all directories again. Use when you want to widen edit scope without ending the session. Use when asked to "unfreeze", "unlock edits", "remove freeze", or - "allow all edits". + "allow all edits". (gstack) allowed-tools: - Bash - Read diff --git a/unfreeze/SKILL.md.tmpl b/unfreeze/SKILL.md.tmpl index 074ba8052..c35d42393 100644 --- a/unfreeze/SKILL.md.tmpl +++ b/unfreeze/SKILL.md.tmpl @@ -5,7 +5,7 @@ description: | Clear the freeze boundary set by /freeze, allowing edits to all directories again. Use when you want to widen edit scope without ending the session. Use when asked to "unfreeze", "unlock edits", "remove freeze", or - "allow all edits". + "allow all edits". (gstack) allowed-tools: - Bash - Read