M CHANGELOG.md => CHANGELOG.md +21 -0
@@ 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.
M SKILL.md => SKILL.md +16 -12
@@ 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
M SKILL.md.tmpl => SKILL.md.tmpl +1 -1
@@ 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
M VERSION => VERSION +1 -1
@@ 1,1 1,1 @@
-0.13.6.0
+0.13.7.0
M autoplan/SKILL.md => autoplan/SKILL.md +16 -12
@@ 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
M autoplan/SKILL.md.tmpl => autoplan/SKILL.md.tmpl +1 -1
@@ 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
M benchmark/SKILL.md => benchmark/SKILL.md +16 -12
@@ 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
M benchmark/SKILL.md.tmpl => benchmark/SKILL.md.tmpl +1 -1
@@ 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
M bin/gstack-config => bin/gstack-config +5 -0
@@ 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
A bin/gstack-open-url => bin/gstack-open-url +14 -0
@@ 0,0 1,14 @@
+#!/usr/bin/env bash
+# gstack-open-url — cross-platform URL opener
+#
+# Usage: gstack-open-url <url>
+set -euo pipefail
+
+URL="${1:?Usage: gstack-open-url <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
A bin/gstack-relink => bin/gstack-relink +73 -0
@@ 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
M browse/SKILL.md => browse/SKILL.md +16 -12
@@ 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
M browse/SKILL.md.tmpl => browse/SKILL.md.tmpl +1 -1
@@ 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
M browse/src/server.ts => browse/src/server.ts +1 -1
@@ 430,7 430,7 @@ function spawnClaude(userMessage: string, extensionUrl?: string | null): void {
const prompt = `${systemPrompt}\n\n<user-message>\n${escapedMessage}\n</user-message>`;
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);
}
M => +20 -5
@@ 161,8 161,10 @@ async function askClaude(queueEntry: any): Promise<void> {
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<void> {
}
});
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<string, any> = { 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<void> {
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();
});
M => +1 -1
@@ 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'");
});
});
M canary/SKILL.md => canary/SKILL.md +16 -12
@@ 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
M canary/SKILL.md.tmpl => canary/SKILL.md.tmpl +1 -1
@@ 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
M careful/SKILL.md => careful/SKILL.md +1 -1
@@ 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
M careful/SKILL.md.tmpl => careful/SKILL.md.tmpl +1 -1
@@ 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
M codex/SKILL.md => codex/SKILL.md +16 -12
@@ 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
M codex/SKILL.md.tmpl => codex/SKILL.md.tmpl +1 -1
@@ 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
M connect-chrome/SKILL.md => connect-chrome/SKILL.md +15 -11
@@ 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
M cso/SKILL.md => cso/SKILL.md +16 -12
@@ 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
M cso/SKILL.md.tmpl => cso/SKILL.md.tmpl +1 -1
@@ 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
M design-consultation/SKILL.md => design-consultation/SKILL.md +16 -12
@@ 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
M design-consultation/SKILL.md.tmpl => design-consultation/SKILL.md.tmpl +1 -1
@@ 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
M design-review/SKILL.md => design-review/SKILL.md +16 -12
@@ 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
M design-review/SKILL.md.tmpl => design-review/SKILL.md.tmpl +1 -1
@@ 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
M design-shotgun/SKILL.md => design-shotgun/SKILL.md +16 -12
@@ 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
M design-shotgun/SKILL.md.tmpl => design-shotgun/SKILL.md.tmpl +1 -1
@@ 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
M document-release/SKILL.md => document-release/SKILL.md +16 -12
@@ 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
M document-release/SKILL.md.tmpl => document-release/SKILL.md.tmpl +1 -1
@@ 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
M freeze/SKILL.md => freeze/SKILL.md +1 -1
@@ 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
M freeze/SKILL.md.tmpl => freeze/SKILL.md.tmpl +1 -1
@@ 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
M guard/SKILL.md => guard/SKILL.md +1 -1
@@ 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
M guard/SKILL.md.tmpl => guard/SKILL.md.tmpl +1 -1
@@ 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
M investigate/SKILL.md => investigate/SKILL.md +16 -12
@@ 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
M investigate/SKILL.md.tmpl => investigate/SKILL.md.tmpl +1 -1
@@ 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
M land-and-deploy/SKILL.md => land-and-deploy/SKILL.md +16 -12
@@ 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
M land-and-deploy/SKILL.md.tmpl => land-and-deploy/SKILL.md.tmpl +1 -1
@@ 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
M learn/SKILL.md => learn/SKILL.md +15 -11
@@ 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
M office-hours/SKILL.md => office-hours/SKILL.md +16 -12
@@ 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
M office-hours/SKILL.md.tmpl => office-hours/SKILL.md.tmpl +1 -1
@@ 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
M package.json => package.json +1 -1
@@ 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",
M plan-ceo-review/SKILL.md => plan-ceo-review/SKILL.md +16 -12
@@ 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
M plan-ceo-review/SKILL.md.tmpl => plan-ceo-review/SKILL.md.tmpl +1 -1
@@ 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
M plan-design-review/SKILL.md => plan-design-review/SKILL.md +16 -12
@@ 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
M plan-design-review/SKILL.md.tmpl => plan-design-review/SKILL.md.tmpl +1 -1
@@ 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
M plan-eng-review/SKILL.md => plan-eng-review/SKILL.md +17 -13
@@ 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.
M plan-eng-review/SKILL.md.tmpl => plan-eng-review/SKILL.md.tmpl +2 -2
@@ 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.
M qa-only/SKILL.md => qa-only/SKILL.md +16 -12
@@ 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
M qa-only/SKILL.md.tmpl => qa-only/SKILL.md.tmpl +1 -1
@@ 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
M qa/SKILL.md => qa/SKILL.md +16 -12
@@ 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
M qa/SKILL.md.tmpl => qa/SKILL.md.tmpl +1 -1
@@ 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
M retro/SKILL.md => retro/SKILL.md +16 -12
@@ 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
M retro/SKILL.md.tmpl => retro/SKILL.md.tmpl +1 -1
@@ 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
M review/SKILL.md => review/SKILL.md +16 -12
@@ 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
M review/SKILL.md.tmpl => review/SKILL.md.tmpl +1 -1
@@ 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
M scripts/resolvers/preamble.ts => scripts/resolvers/preamble.ts +17 -12
@@ 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
M setup => setup +1 -0
@@ 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
M setup-browser-cookies/SKILL.md => setup-browser-cookies/SKILL.md +16 -12
@@ 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
M setup-browser-cookies/SKILL.md.tmpl => setup-browser-cookies/SKILL.md.tmpl +1 -1
@@ 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
M setup-deploy/SKILL.md => setup-deploy/SKILL.md +15 -11
@@ 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
M ship/SKILL.md => ship/SKILL.md +22 -15
@@ 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/<base>...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:
M ship/SKILL.md.tmpl => ship/SKILL.md.tmpl +7 -4
@@ 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/<base>...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:
M test/gen-skill-docs.test.ts => test/gen-skill-docs.test.ts +94 -0
@@ 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
A test/relink.test.ts => test/relink.test.ts +152 -0
@@ 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<string, string> = {}, 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);
+ });
+});
M test/skill-validation.test.ts => test/skill-validation.test.ts +27 -0
@@ 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', () => {})");
+ });
+});
M test/telemetry.test.ts => test/telemetry.test.ts +22 -0
@@ 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()}`);
+ }
+ }
+ }
+ });
+});
M unfreeze/SKILL.md => unfreeze/SKILL.md +1 -1
@@ 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
M unfreeze/SKILL.md.tmpl => unfreeze/SKILL.md.tmpl +1 -1
@@ 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