gstack
Enables fast web browsing for Claude Code with a headless Chromium daemon, allowing navigation, content extraction, and interaction with web pages.
Install this skill
Security score
The gstack skill was audited on May 10, 2026 and we found 98 security issues across 4 threat categories, including 1 high-severity. Review the findings below before installing.
Categories Tested
Security Issues
Command substitution pattern
| 25 | |
| 26 | ```bash |
| 27 | _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/skills/gstack/bin/gstack-update-check 2>/dev/null || true) |
| 28 | [ -n "$_UPD" ] && echo "$_UPD" || true |
| 29 | mkdir -p ~/.gstack/sessions |
Command substitution pattern
| 29 | mkdir -p ~/.gstack/sessions |
| 30 | touch ~/.gstack/sessions/"$PPID" |
| 31 | _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') |
| 32 | find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true |
| 33 | _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") |
Command substitution pattern
| 31 | _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') |
| 32 | find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true |
| 33 | _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") |
| 34 | _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") |
| 35 | _BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") |
Command substitution pattern
| 32 | find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true |
| 33 | _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") |
| 34 | _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") |
| 35 | _BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") |
| 36 | echo "BRANCH: $_BRANCH" |
Command substitution pattern
| 33 | _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") |
| 34 | _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") |
| 35 | _BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") |
| 36 | echo "BRANCH: $_BRANCH" |
| 37 | _SKILL_PREFIX=$(~/.claude/skills/gstack/bin/gstack-config get skill_prefix 2>/dev/null || echo "false") |
Command substitution pattern
| 35 | _BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") |
| 36 | echo "BRANCH: $_BRANCH" |
| 37 | _SKILL_PREFIX=$(~/.claude/skills/gstack/bin/gstack-config get skill_prefix 2>/dev/null || echo "false") |
| 38 | echo "PROACTIVE: $_PROACTIVE" |
| 39 | echo "PROACTIVE_PROMPTED: $_PROACTIVE_PROMPTED" |
Command substitution pattern
| 42 | REPO_MODE=${REPO_MODE:-unknown} |
| 43 | echo "REPO_MODE: $REPO_MODE" |
| 44 | _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") |
| 45 | echo "LAKE_INTRO: $_LAKE_SEEN" |
| 46 | _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) |
Command substitution pattern
| 44 | _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") |
| 45 | echo "LAKE_INTRO: $_LAKE_SEEN" |
| 46 | _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) |
| 47 | _TEL_PROMPTED=$([ -f ~/.gstack/.telemetry-prompted ] && echo "yes" || echo "no") |
| 48 | _TEL_START=$(date +%s) |
Command substitution pattern
| 45 | echo "LAKE_INTRO: $_LAKE_SEEN" |
| 46 | _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) |
| 47 | _TEL_PROMPTED=$([ -f ~/.gstack/.telemetry-prompted ] && echo "yes" || echo "no") |
| 48 | _TEL_START=$(date +%s) |
| 49 | _SESSION_ID="$$-$(date +%s)" |
Command substitution pattern
| 50 | echo "TELEMETRY: ${_TEL:-off}" |
| 51 | echo "TEL_PROMPTED: $_TEL_PROMPTED" |
| 52 | _EXPLAIN_LEVEL=$(~/.claude/skills/gstack/bin/gstack-config get explain_level 2>/dev/null || echo "default") |
| 53 | if [ "$_EXPLAIN_LEVEL" != "default" ] && [ "$_EXPLAIN_LEVEL" != "terse" ]; then _EXPLAIN_LEVEL="default"; fi |
| 54 | echo "EXPLAIN_LEVEL: $_EXPLAIN_LEVEL" |
Command substitution pattern
| 53 | if [ "$_EXPLAIN_LEVEL" != "default" ] && [ "$_EXPLAIN_LEVEL" != "terse" ]; then _EXPLAIN_LEVEL="default"; fi |
| 54 | echo "EXPLAIN_LEVEL: $_EXPLAIN_LEVEL" |
| 55 | _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning 2>/dev/null || echo "false") |
| 56 | echo "QUESTION_TUNING: $_QUESTION_TUNING" |
| 57 | mkdir -p ~/.gstack/analytics |
Command substitution pattern
| 57 | mkdir -p ~/.gstack/analytics |
| 58 | if [ "$_TEL" != "off" ]; then |
| 59 | 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 |
| 60 | fi |
| 61 | for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do |
Command substitution pattern
| 59 | 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 |
| 60 | fi |
| 61 | for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do |
| 62 | if [ -f "$_PF" ]; then |
| 63 | if [ "$_TEL" != "off" ] && [ -x "~/.claude/skills/gstack/bin/gstack-telemetry-log" ]; then |
Command substitution pattern
| 68 | break |
| 69 | done |
| 70 | eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" 2>/dev/null || true |
| 71 | _LEARN_FILE="${GSTACK_HOME:-$HOME/.gstack}/projects/${SLUG:-unknown}/learnings.jsonl" |
| 72 | if [ -f "$_LEARN_FILE" ]; then |
Command substitution pattern
| 71 | _LEARN_FILE="${GSTACK_HOME:-$HOME/.gstack}/projects/${SLUG:-unknown}/learnings.jsonl" |
| 72 | if [ -f "$_LEARN_FILE" ]; then |
| 73 | _LEARN_COUNT=$(wc -l < "$_LEARN_FILE" 2>/dev/null | tr -d ' ') |
| 74 | echo "LEARNINGS: $_LEARN_COUNT entries loaded" |
| 75 | if [ "$_LEARN_COUNT" -gt 5 ] 2>/dev/null; then |
Command substitution pattern
| 84 | _HAS_ROUTING="yes" |
| 85 | fi |
| 86 | _ROUTING_DECLINED=$(~/.claude/skills/gstack/bin/gstack-config get routing_declined 2>/dev/null || echo "false") |
| 87 | echo "HAS_ROUTING: $_HAS_ROUTING" |
| 88 | echo "ROUTING_DECLINED: $_ROUTING_DECLINED" |
Command substitution pattern
| 95 | echo "VENDORED_GSTACK: $_VENDORED" |
| 96 | echo "MODEL_OVERLAY: claude" |
| 97 | _CHECKPOINT_MODE=$(~/.claude/skills/gstack/bin/gstack-config get checkpoint_mode 2>/dev/null || echo "explicit") |
| 98 | _CHECKPOINT_PUSH=$(~/.claude/skills/gstack/bin/gstack-config get checkpoint_push 2>/dev/null || echo "false") |
| 99 | echo "CHECKPOINT_MODE: $_CHECKPOINT_MODE" |
Command substitution pattern
| 96 | echo "MODEL_OVERLAY: claude" |
| 97 | _CHECKPOINT_MODE=$(~/.claude/skills/gstack/bin/gstack-config get checkpoint_mode 2>/dev/null || echo "explicit") |
| 98 | _CHECKPOINT_PUSH=$(~/.claude/skills/gstack/bin/gstack-config get checkpoint_push 2>/dev/null || echo "false") |
| 99 | echo "CHECKPOINT_MODE: $_CHECKPOINT_MODE" |
| 100 | echo "CHECKPOINT_PUSH: $_CHECKPOINT_PUSH" |
Command substitution pattern
| 258 | Always run (regardless of choice): |
| 259 | ```bash |
| 260 | eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" 2>/dev/null || true |
| 261 | touch ~/.gstack/.vendoring-warned-${SLUG:-unknown} |
| 262 | ``` |
Command substitution pattern
| 293 | _GBRAIN_CONFIG="$HOME/.gbrain/config.json" |
| 294 | if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then |
| 295 | _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) |
| 296 | if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then |
| 297 | _GBRAIN_PIN_PATH="" |
Command substitution pattern
| 296 | if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then |
| 297 | _GBRAIN_PIN_PATH="" |
| 298 | _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") |
| 299 | if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then |
| 300 | _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" |
Command substitution pattern
| 313 | fi |
| 314 | |
| 315 | _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || echo off) |
| 316 | |
| 317 | # Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is |
Command substitution pattern
| 321 | _GBRAIN_MCP_MODE="none" |
| 322 | if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then |
| 323 | _GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null) |
| 324 | case "$_GBRAIN_MCP_TYPE" in |
| 325 | url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;; |
Command substitution pattern
| 329 | |
| 330 | if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then |
| 331 | _BRAIN_NEW_URL=$(head -1 "$_BRAIN_REMOTE_FILE" 2>/dev/null | tr -d '[:space:]') |
| 332 | if [ -n "$_BRAIN_NEW_URL" ]; then |
| 333 | echo "ARTIFACTS_SYNC: artifacts repo detected: $_BRAIN_NEW_URL" |
Command substitution pattern
| 341 | _BRAIN_DO_PULL=1 |
| 342 | if [ -f "$_BRAIN_LAST_PULL_FILE" ]; then |
| 343 | _BRAIN_LAST=$(cat "$_BRAIN_LAST_PULL_FILE" 2>/dev/null || echo 0) |
| 344 | _BRAIN_AGE=$(( _BRAIN_NOW - _BRAIN_LAST )) |
| 345 | [ "$_BRAIN_AGE" -lt 86400 ] && _BRAIN_DO_PULL=0 |
Command substitution pattern
| 342 | if [ -f "$_BRAIN_LAST_PULL_FILE" ]; then |
| 343 | _BRAIN_LAST=$(cat "$_BRAIN_LAST_PULL_FILE" 2>/dev/null || echo 0) |
| 344 | _BRAIN_AGE=$(( _BRAIN_NOW - _BRAIN_LAST )) |
| 345 | [ "$_BRAIN_AGE" -lt 86400 ] && _BRAIN_DO_PULL=0 |
| 346 | fi |
Command substitution pattern
| 346 | fi |
| 347 | if [ "$_BRAIN_DO_PULL" = "1" ]; then |
| 348 | ( cd "$_GSTACK_HOME" && git fetch origin >/dev/null 2>&1 && git merge --ff-only "origin/$(git rev-parse --abbrev-ref HEAD)" >/dev/null 2>&1 ) || true |
| 349 | echo "$_BRAIN_NOW" > "$_BRAIN_LAST_PULL_FILE" |
| 350 | fi |
Command substitution pattern
| 355 | # Remote-MCP mode: local artifacts sync is a no-op (brain admin's server |
| 356 | # pulls from GitHub/GitLab). Show the user this is by design, not broken. |
| 357 | _GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|') |
| 358 | echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})" |
| 359 | elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then |
Command substitution pattern
| 359 | elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then |
| 360 | _BRAIN_QUEUE_DEPTH=0 |
| 361 | [ -f "$_GSTACK_HOME/.brain-queue.jsonl" ] && _BRAIN_QUEUE_DEPTH=$(wc -l < "$_GSTACK_HOME/.brain-queue.jsonl" | tr -d ' ') |
| 362 | _BRAIN_LAST_PUSH="never" |
| 363 | [ -f "$_GSTACK_HOME/.brain-last-push" ] && _BRAIN_LAST_PUSH=$(cat "$_GSTACK_HOME/.brain-last-push" 2>/dev/null || echo never) |
Command substitution pattern
| 361 | [ -f "$_GSTACK_HOME/.brain-queue.jsonl" ] && _BRAIN_QUEUE_DEPTH=$(wc -l < "$_GSTACK_HOME/.brain-queue.jsonl" | tr -d ' ') |
| 362 | _BRAIN_LAST_PUSH="never" |
| 363 | [ -f "$_GSTACK_HOME/.brain-last-push" ] && _BRAIN_LAST_PUSH=$(cat "$_GSTACK_HOME/.brain-last-push" 2>/dev/null || echo never) |
| 364 | echo "ARTIFACTS_SYNC: mode=$_BRAIN_SYNC_MODE | last_push=$_BRAIN_LAST_PUSH | queue=$_BRAIN_QUEUE_DEPTH" |
| 365 | else |
Command substitution pattern
| 454 | ```bash |
| 455 | _TEL_END=$(date +%s) |
| 456 | _TEL_DUR=$(( _TEL_END - _TEL_START )) |
| 457 | rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true |
| 458 | # Session timeline: record skill completion (local-only, never sent anywhere) |
Command substitution pattern
| 457 | rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true |
| 458 | # Session timeline: record skill completion (local-only, never sent anywhere) |
| 459 | ~/.claude/skills/gstack/bin/gstack-timeline-log '{"skill":"SKILL_NAME","event":"completed","branch":"'$(git branch --show-current 2>/dev/null || echo unknown)'","outcome":"OUTCOME","duration_s":"'"$_TEL_DUR"'","session":"'"$_SESSION_ID"'"}' 2>/dev/null || true |
| 460 | # Local analytics (gated on telemetry setting) |
| 461 | if [ "$_TEL" != "off" ]; then |
Command substitution pattern
| 460 | # Local analytics (gated on telemetry setting) |
| 461 | if [ "$_TEL" != "off" ]; then |
| 462 | 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 |
| 463 | fi |
| 464 | # Remote telemetry (opt-in, requires binary) |
Command substitution pattern
| 539 | |
| 540 | ```bash |
| 541 | _ROOT=$(git rev-parse --show-toplevel 2>/dev/null) |
| 542 | B="" |
| 543 | [ -n "$_ROOT" ] && [ -x "$_ROOT/.claude/skills/gstack/browse/dist/browse" ] && B="$_ROOT/.claude/skills/gstack/browse/dist/browse" |
Command substitution pattern
| 560 | tmpfile=$(mktemp) |
| 561 | curl -fsSL "https://bun.sh/install" -o "$tmpfile" |
| 562 | actual_sha=$(shasum -a 256 "$tmpfile" | awk '{print $1}') |
| 563 | if [ "$actual_sha" != "$BUN_INSTALL_SHA" ]; then |
| 564 | echo "ERROR: bun install script checksum mismatch" >&2 |
Curl to non-GitHub URL
| 559 | BUN_INSTALL_SHA="bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd" |
| 560 | tmpfile=$(mktemp) |
| 561 | curl -fsSL "https://bun.sh/install" -o "$tmpfile" |
| 562 | actual_sha=$(shasum -a 256 "$tmpfile" | awk '{print $1}') |
| 563 | if [ "$actual_sha" != "$BUN_INSTALL_SHA" ]; then |
Access to home directory dotfiles
| 25 | |
| 26 | ```bash |
| 27 | _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/skills/gstack/bin/gstack-update-check 2>/dev/null || true) |
| 28 | [ -n "$_UPD" ] && echo "$_UPD" || true |
| 29 | mkdir -p ~/.gstack/sessions |
Access to home directory dotfiles
| 27 | _UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/skills/gstack/bin/gstack-update-check 2>/dev/null || true) |
| 28 | [ -n "$_UPD" ] && echo "$_UPD" || true |
| 29 | mkdir -p ~/.gstack/sessions |
| 30 | touch ~/.gstack/sessions/"$PPID" |
| 31 | _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') |
Access to home directory dotfiles
| 28 | [ -n "$_UPD" ] && echo "$_UPD" || true |
| 29 | mkdir -p ~/.gstack/sessions |
| 30 | touch ~/.gstack/sessions/"$PPID" |
| 31 | _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') |
| 32 | find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true |
Access to home directory dotfiles
| 29 | mkdir -p ~/.gstack/sessions |
| 30 | touch ~/.gstack/sessions/"$PPID" |
| 31 | _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') |
| 32 | find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true |
| 33 | _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") |
Access to home directory dotfiles
| 30 | touch ~/.gstack/sessions/"$PPID" |
| 31 | _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') |
| 32 | find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true |
| 33 | _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") |
| 34 | _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") |
Access to home directory dotfiles
| 31 | _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') |
| 32 | find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true |
| 33 | _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") |
| 34 | _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") |
| 35 | _BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") |
Access to home directory dotfiles
| 32 | find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true |
| 33 | _PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") |
| 34 | _PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") |
| 35 | _BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") |
| 36 | echo "BRANCH: $_BRANCH" |
Access to home directory dotfiles
| 35 | _BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") |
| 36 | echo "BRANCH: $_BRANCH" |
| 37 | _SKILL_PREFIX=$(~/.claude/skills/gstack/bin/gstack-config get skill_prefix 2>/dev/null || echo "false") |
| 38 | echo "PROACTIVE: $_PROACTIVE" |
| 39 | echo "PROACTIVE_PROMPTED: $_PROACTIVE_PROMPTED" |
Access to home directory dotfiles
| 39 | echo "PROACTIVE_PROMPTED: $_PROACTIVE_PROMPTED" |
| 40 | echo "SKILL_PREFIX: $_SKILL_PREFIX" |
| 41 | source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true |
| 42 | REPO_MODE=${REPO_MODE:-unknown} |
| 43 | echo "REPO_MODE: $REPO_MODE" |
Access to home directory dotfiles
| 42 | REPO_MODE=${REPO_MODE:-unknown} |
| 43 | echo "REPO_MODE: $REPO_MODE" |
| 44 | _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") |
| 45 | echo "LAKE_INTRO: $_LAKE_SEEN" |
| 46 | _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) |
Access to home directory dotfiles
| 44 | _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") |
| 45 | echo "LAKE_INTRO: $_LAKE_SEEN" |
| 46 | _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) |
| 47 | _TEL_PROMPTED=$([ -f ~/.gstack/.telemetry-prompted ] && echo "yes" || echo "no") |
| 48 | _TEL_START=$(date +%s) |
Access to home directory dotfiles
| 45 | echo "LAKE_INTRO: $_LAKE_SEEN" |
| 46 | _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) |
| 47 | _TEL_PROMPTED=$([ -f ~/.gstack/.telemetry-prompted ] && echo "yes" || echo "no") |
| 48 | _TEL_START=$(date +%s) |
| 49 | _SESSION_ID="$$-$(date +%s)" |
Access to home directory dotfiles
| 50 | echo "TELEMETRY: ${_TEL:-off}" |
| 51 | echo "TEL_PROMPTED: $_TEL_PROMPTED" |
| 52 | _EXPLAIN_LEVEL=$(~/.claude/skills/gstack/bin/gstack-config get explain_level 2>/dev/null || echo "default") |
| 53 | if [ "$_EXPLAIN_LEVEL" != "default" ] && [ "$_EXPLAIN_LEVEL" != "terse" ]; then _EXPLAIN_LEVEL="default"; fi |
| 54 | echo "EXPLAIN_LEVEL: $_EXPLAIN_LEVEL" |
Access to home directory dotfiles
| 53 | if [ "$_EXPLAIN_LEVEL" != "default" ] && [ "$_EXPLAIN_LEVEL" != "terse" ]; then _EXPLAIN_LEVEL="default"; fi |
| 54 | echo "EXPLAIN_LEVEL: $_EXPLAIN_LEVEL" |
| 55 | _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning 2>/dev/null || echo "false") |
| 56 | echo "QUESTION_TUNING: $_QUESTION_TUNING" |
| 57 | mkdir -p ~/.gstack/analytics |
Access to home directory dotfiles
| 55 | _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning 2>/dev/null || echo "false") |
| 56 | echo "QUESTION_TUNING: $_QUESTION_TUNING" |
| 57 | mkdir -p ~/.gstack/analytics |
| 58 | if [ "$_TEL" != "off" ]; then |
| 59 | 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 |
Access to home directory dotfiles
| 57 | mkdir -p ~/.gstack/analytics |
| 58 | if [ "$_TEL" != "off" ]; then |
| 59 | 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 |
| 60 | fi |
| 61 | for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do |
Access to home directory dotfiles
| 59 | 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 |
| 60 | fi |
| 61 | for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do |
| 62 | if [ -f "$_PF" ]; then |
| 63 | if [ "$_TEL" != "off" ] && [ -x "~/.claude/skills/gstack/bin/gstack-telemetry-log" ]; then |
Access to home directory dotfiles
| 61 | for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do |
| 62 | if [ -f "$_PF" ]; then |
| 63 | if [ "$_TEL" != "off" ] && [ -x "~/.claude/skills/gstack/bin/gstack-telemetry-log" ]; then |
| 64 | ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true |
| 65 | fi |
Access to home directory dotfiles
| 62 | if [ -f "$_PF" ]; then |
| 63 | if [ "$_TEL" != "off" ] && [ -x "~/.claude/skills/gstack/bin/gstack-telemetry-log" ]; then |
| 64 | ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true |
| 65 | fi |
| 66 | rm -f "$_PF" 2>/dev/null || true |
Access to home directory dotfiles
| 68 | break |
| 69 | done |
| 70 | eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" 2>/dev/null || true |
| 71 | _LEARN_FILE="${GSTACK_HOME:-$HOME/.gstack}/projects/${SLUG:-unknown}/learnings.jsonl" |
| 72 | if [ -f "$_LEARN_FILE" ]; then |
Access to home directory dotfiles
| 74 | echo "LEARNINGS: $_LEARN_COUNT entries loaded" |
| 75 | if [ "$_LEARN_COUNT" -gt 5 ] 2>/dev/null; then |
| 76 | ~/.claude/skills/gstack/bin/gstack-learnings-search --limit 3 2>/dev/null || true |
| 77 | fi |
| 78 | else |
Access to home directory dotfiles
| 79 | echo "LEARNINGS: 0" |
| 80 | fi |
| 81 | ~/.claude/skills/gstack/bin/gstack-timeline-log '{"skill":"gstack","event":"started","branch":"'"$_BRANCH"'","session":"'"$_SESSION_ID"'"}' 2>/dev/null & |
| 82 | _HAS_ROUTING="no" |
| 83 | if [ -f CLAUDE.md ] && grep -q "## Skill routing" CLAUDE.md 2>/dev/null; then |
Access to home directory dotfiles
| 84 | _HAS_ROUTING="yes" |
| 85 | fi |
| 86 | _ROUTING_DECLINED=$(~/.claude/skills/gstack/bin/gstack-config get routing_declined 2>/dev/null || echo "false") |
| 87 | echo "HAS_ROUTING: $_HAS_ROUTING" |
| 88 | echo "ROUTING_DECLINED: $_ROUTING_DECLINED" |
Access to home directory dotfiles
| 95 | echo "VENDORED_GSTACK: $_VENDORED" |
| 96 | echo "MODEL_OVERLAY: claude" |
| 97 | _CHECKPOINT_MODE=$(~/.claude/skills/gstack/bin/gstack-config get checkpoint_mode 2>/dev/null || echo "explicit") |
| 98 | _CHECKPOINT_PUSH=$(~/.claude/skills/gstack/bin/gstack-config get checkpoint_push 2>/dev/null || echo "false") |
| 99 | echo "CHECKPOINT_MODE: $_CHECKPOINT_MODE" |
Access to home directory dotfiles
| 96 | echo "MODEL_OVERLAY: claude" |
| 97 | _CHECKPOINT_MODE=$(~/.claude/skills/gstack/bin/gstack-config get checkpoint_mode 2>/dev/null || echo "explicit") |
| 98 | _CHECKPOINT_PUSH=$(~/.claude/skills/gstack/bin/gstack-config get checkpoint_push 2>/dev/null || echo "false") |
| 99 | echo "CHECKPOINT_MODE: $_CHECKPOINT_MODE" |
| 100 | echo "CHECKPOINT_PUSH: $_CHECKPOINT_PUSH" |
Access to home directory dotfiles
| 104 | ## Plan Mode Safe Operations |
| 105 | |
| 106 | In plan mode, allowed because they inform the plan: `$B`, `$D`, `codex exec`/`codex review`, writes to `~/.gstack/`, writes to the plan file, and `open` for generated artifacts. |
| 107 | |
| 108 | ## Skill Invocation During Plan Mode |
Access to home directory dotfiles
| 112 | If `PROACTIVE` is `"false"`, do not auto-invoke or proactively suggest skills. If a skill seems useful, ask: "I think /skillname might help here — want me to run it?" |
| 113 | |
| 114 | If `SKILL_PREFIX` is `"true"`, suggest/invoke `/gstack-*` names. Disk paths stay `~/.claude/skills/gstack/[skill-name]/SKILL.md`. |
| 115 | |
| 116 | If output shows `UPGRADE_AVAILABLE <old> <new>`: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). |
Access to home directory dotfiles
| 114 | If `SKILL_PREFIX` is `"true"`, suggest/invoke `/gstack-*` names. Disk paths stay `~/.claude/skills/gstack/[skill-name]/SKILL.md`. |
| 115 | |
| 116 | If output shows `UPGRADE_AVAILABLE <old> <new>`: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). |
| 117 | |
| 118 | If output shows `JUST_UPGRADED <from> <to>`: print "Running gstack v{to} (just updated!)". If `SPAWNED_SESSION` is true, skip feature discovery. |
Access to home directory dotfiles
| 119 | |
| 120 | Feature discovery, max one prompt per session: |
| 121 | - Missing `~/.claude/skills/gstack/.feature-prompted-continuous-checkpoint`: AskUserQuestion for Continuous checkpoint auto-commits. If accepted, run `~/.claude/skills/gstack/bin/gstack-config set checkpoint_mode continuous`. Always touch marker. |
| 122 | - Missing `~/.claude/skills/gstack/.feature-prompted-model-overlay`: inform "Model overlays are active. MODEL_OVERLAY shows the patch." Always touch marker. |
| 123 |
Access to home directory dotfiles
| 120 | Feature discovery, max one prompt per session: |
| 121 | - Missing `~/.claude/skills/gstack/.feature-prompted-continuous-checkpoint`: AskUserQuestion for Continuous checkpoint auto-commits. If accepted, run `~/.claude/skills/gstack/bin/gstack-config set checkpoint_mode continuous`. Always touch marker. |
| 122 | - Missing `~/.claude/skills/gstack/.feature-prompted-model-overlay`: inform "Model overlays are active. MODEL_OVERLAY shows the patch." Always touch marker. |
| 123 | |
| 124 | After upgrade prompts, continue workflow. |
Access to home directory dotfiles
| 133 | |
| 134 | If A: leave `explain_level` unset (defaults to `default`). |
| 135 | If B: run `~/.claude/skills/gstack/bin/gstack-config set explain_level terse`. |
| 136 | |
| 137 | Always run (regardless of choice): |
Access to home directory dotfiles
| 137 | Always run (regardless of choice): |
| 138 | ```bash |
| 139 | rm -f ~/.gstack/.writing-style-prompt-pending |
| 140 | touch ~/.gstack/.writing-style-prompted |
| 141 | ``` |
Access to home directory dotfiles
| 138 | ```bash |
| 139 | rm -f ~/.gstack/.writing-style-prompt-pending |
| 140 | touch ~/.gstack/.writing-style-prompted |
| 141 | ``` |
| 142 |
Access to home directory dotfiles
| 147 | ```bash |
| 148 | open https://garryslist.org/posts/boil-the-ocean |
| 149 | touch ~/.gstack/.completeness-intro-seen |
| 150 | ``` |
| 151 |
Access to home directory dotfiles
| 160 | - B) No thanks |
| 161 | |
| 162 | If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry community` |
| 163 | |
| 164 | If B: ask follow-up: |
Access to home directory dotfiles
| 170 | - B) No thanks, fully off |
| 171 | |
| 172 | If B→A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous` |
| 173 | If B→B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off` |
| 174 |
Access to home directory dotfiles
| 171 | |
| 172 | If B→A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous` |
| 173 | If B→B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off` |
| 174 | |
| 175 | Always run: |
Access to home directory dotfiles
| 175 | Always run: |
| 176 | ```bash |
| 177 | touch ~/.gstack/.telemetry-prompted |
| 178 | ``` |
| 179 |
Access to home directory dotfiles
| 188 | - B) Turn it off — I'll type /commands myself |
| 189 | |
| 190 | If A: run `~/.claude/skills/gstack/bin/gstack-config set proactive true` |
| 191 | If B: run `~/.claude/skills/gstack/bin/gstack-config set proactive false` |
| 192 |
Access to home directory dotfiles
| 189 | |
| 190 | If A: run `~/.claude/skills/gstack/bin/gstack-config set proactive true` |
| 191 | If B: run `~/.claude/skills/gstack/bin/gstack-config set proactive false` |
| 192 | |
| 193 | Always run: |
Access to home directory dotfiles
| 193 | Always run: |
| 194 | ```bash |
| 195 | touch ~/.gstack/.proactive-prompted |
| 196 | ``` |
| 197 |
Access to home directory dotfiles
| 234 | Then commit the change: `git add CLAUDE.md && git commit -m "chore: add gstack skill routing rules to CLAUDE.md"` |
| 235 | |
| 236 | If B: run `~/.claude/skills/gstack/bin/gstack-config set routing_declined true` and say they can re-enable with `gstack-config set routing_declined false`. |
| 237 | |
| 238 | This only happens once per project. Skip if `HAS_ROUTING` is `yes` or `ROUTING_DECLINED` is `true`. |
Access to home directory dotfiles
| 238 | This only happens once per project. Skip if `HAS_ROUTING` is `yes` or `ROUTING_DECLINED` is `true`. |
| 239 | |
| 240 | If `VENDORED_GSTACK` is `yes`, warn once via AskUserQuestion unless `~/.gstack/.vendoring-warned-$SLUG` exists: |
| 241 | |
| 242 | > This project has gstack vendored in `.claude/skills/gstack/`. Vendoring is deprecated. |
Access to home directory dotfiles
| 250 | 1. Run `git rm -r .claude/skills/gstack/` |
| 251 | 2. Run `echo '.claude/skills/gstack/' >> .gitignore` |
| 252 | 3. Run `~/.claude/skills/gstack/bin/gstack-team-init required` (or `optional`) |
| 253 | 4. Run `git add .claude/ .gitignore CLAUDE.md && git commit -m "chore: migrate gstack from vendored to team mode"` |
| 254 | 5. Tell the user: "Done. Each developer now runs: `cd ~/.claude/skills/gstack && ./setup --team`" |
Access to home directory dotfiles
| 252 | 3. Run `~/.claude/skills/gstack/bin/gstack-team-init required` (or `optional`) |
| 253 | 4. Run `git add .claude/ .gitignore CLAUDE.md && git commit -m "chore: migrate gstack from vendored to team mode"` |
| 254 | 5. Tell the user: "Done. Each developer now runs: `cd ~/.claude/skills/gstack && ./setup --team`" |
| 255 | |
| 256 | If B: say "OK, you're on your own to keep the vendored copy up to date." |
Access to home directory dotfiles
| 258 | Always run (regardless of choice): |
| 259 | ```bash |
| 260 | eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" 2>/dev/null || true |
| 261 | touch ~/.gstack/.vendoring-warned-${SLUG:-unknown} |
| 262 | ``` |
Access to home directory dotfiles
| 259 | ```bash |
| 260 | eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" 2>/dev/null || true |
| 261 | touch ~/.gstack/.vendoring-warned-${SLUG:-unknown} |
| 262 | ``` |
| 263 |
Access to home directory dotfiles
| 282 | _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" |
| 283 | fi |
| 284 | _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" |
| 285 | _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" |
| 286 |
Access to home directory dotfiles
| 283 | fi |
| 284 | _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" |
| 285 | _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" |
| 286 | |
| 287 | # /sync-gbrain context-load: teach the agent to use gbrain when it's available. |
Access to home directory dotfiles
| 387 | ``` |
| 388 | |
| 389 | If A/B and `~/.gstack/.git` is missing, ask whether to run `gstack-artifacts-init`. Do not block the skill. |
| 390 | |
| 391 | At skill END before telemetry: |
Access to home directory dotfiles
| 392 | |
| 393 | ```bash |
| 394 | "~/.claude/skills/gstack/bin/gstack-brain-sync" --discover-new 2>/dev/null || true |
| 395 | "~/.claude/skills/gstack/bin/gstack-brain-sync" --once 2>/dev/null || true |
| 396 | ``` |
Access to home directory dotfiles
| 393 | ```bash |
| 394 | "~/.claude/skills/gstack/bin/gstack-brain-sync" --discover-new 2>/dev/null || true |
| 395 | "~/.claude/skills/gstack/bin/gstack-brain-sync" --once 2>/dev/null || true |
| 396 | ``` |
| 397 |
Access to home directory dotfiles
| 438 | |
| 439 | ```bash |
| 440 | ~/.claude/skills/gstack/bin/gstack-learnings-log '{"skill":"SKILL_NAME","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":N,"source":"observed"}' |
| 441 | ``` |
| 442 |
Access to home directory dotfiles
| 448 | |
| 449 | **PLAN MODE EXCEPTION — ALWAYS RUN:** This command writes telemetry to |
| 450 | `~/.gstack/analytics/`, matching preamble analytics writes. |
| 451 | |
| 452 | Run this bash: |
Access to home directory dotfiles
| 455 | _TEL_END=$(date +%s) |
| 456 | _TEL_DUR=$(( _TEL_END - _TEL_START )) |
| 457 | rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true |
| 458 | # Session timeline: record skill completion (local-only, never sent anywhere) |
| 459 | ~/.claude/skills/gstack/bin/gstack-timeline-log '{"skill":"SKILL_NAME","event":"completed","branch":"'$(git branch --show-current 2>/dev/null || echo unknown)'","outcome":"OUTCOME","duration_s":"'"$_TEL_DUR"'","session":"'"$_SESSION_ID"'"}' 2>/dev/null || true |
Access to home directory dotfiles
| 457 | rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true |
| 458 | # Session timeline: record skill completion (local-only, never sent anywhere) |
| 459 | ~/.claude/skills/gstack/bin/gstack-timeline-log '{"skill":"SKILL_NAME","event":"completed","branch":"'$(git branch --show-current 2>/dev/null || echo unknown)'","outcome":"OUTCOME","duration_s":"'"$_TEL_DUR"'","session":"'"$_SESSION_ID"'"}' 2>/dev/null || true |
| 460 | # Local analytics (gated on telemetry setting) |
| 461 | if [ "$_TEL" != "off" ]; then |
Access to home directory dotfiles
| 460 | # Local analytics (gated on telemetry setting) |
| 461 | if [ "$_TEL" != "off" ]; then |
| 462 | 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 |
| 463 | fi |
| 464 | # Remote telemetry (opt-in, requires binary) |
Access to home directory dotfiles
| 463 | fi |
| 464 | # Remote telemetry (opt-in, requires binary) |
| 465 | if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then |
| 466 | ~/.claude/skills/gstack/bin/gstack-telemetry-log \ |
| 467 | --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ |
Access to home directory dotfiles
| 464 | # Remote telemetry (opt-in, requires binary) |
| 465 | if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then |
| 466 | ~/.claude/skills/gstack/bin/gstack-telemetry-log \ |
| 467 | --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ |
| 468 | --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & |
Access to home directory dotfiles
| 474 | ## Plan Status Footer |
| 475 | |
| 476 | In plan mode before ExitPlanMode: if the plan file lacks `## GSTACK REVIEW REPORT`, run `~/.claude/skills/gstack/bin/gstack-review-read` and append the standard runs/status/findings table. With `NO_REVIEWS` or empty, append a 5-row placeholder with verdict "NO REVIEWS YET — run `/autoplan`". If a richer report exists, skip. |
| 477 | |
| 478 | PLAN MODE EXCEPTION — always allowed (it's the plan file). |
Access to home directory dotfiles
| 782 | |
| 783 | The snapshot is your primary tool for understanding and interacting with pages. |
| 784 | `$B` is the browse binary (resolved from `$_ROOT/.claude/skills/gstack/browse/dist/browse` or `~/.claude/skills/gstack/browse/dist/browse`). |
| 785 | |
| 786 | **Syntax:** `$B snapshot [flags]` |
Urgency-based manipulation
| 108 | ## Skill Invocation During Plan Mode |
| 109 | |
| 110 | If the user invokes a skill in plan mode, the skill takes precedence over generic plan mode behavior. **Treat the skill file as executable instructions, not reference.** Follow it step by step starting from Step 0; the first AskUserQuestion is the workflow entering plan mode, not a violation of it. AskUserQuestion (any variant — `mcp__*__AskUserQuestion` or native; see "AskUserQuestion Format → Tool resolution") satisfies plan mode's end-of-turn requirement. If no variant is callable, the skill is BLOCKED — stop and report `BLOCKED — AskUserQuestion unavailable` per the AskUserQuestion Format rule. At a STOP point, stop immediately. Do not continue the workflow or call ExitPlanMode there. Commands marked "PLAN MODE EXCEPTION — ALWAYS RUN" execute. Call ExitPlanMode only after the skill workflow completes, or if the user tells you to cancel the skill or leave plan mode. |
| 111 | |
| 112 | If `PROACTIVE` is `"false"`, do not auto-invoke or proactively suggest skills. If a skill seems useful, ask: "I think /skillname might help here — want me to run it?" |