mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-07 05:56:41 +02:00
merge: incorporate origin/main into community-mode branch
Conflicts resolved: - VERSION: accept main's 0.14.5.0 (higher than our 0.14.0.0) - package.json: same version resolution - CHANGELOG.md: drop duplicate 0.14.0.0 entry (already on main), keep main's entries for 0.14.1-0.14.5 and 0.13.7-0.13.10 - README.md: merge skill lists — keep main's /design-html + /learn, add our /gstack-submit to both install and troubleshooting lists - docs/skills.md: keep all three entries (/gstack-submit, /autoplan, /learn) Main brought in 0.14.1-0.14.5: design-to-code (/design-html), comparison board chooser, sidebar CSS inspector + per-tab agents, always-on adversarial review + scope drift, review army (7 parallel specialist reviewers), ship idempotency, skill prefix fix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,8 @@ fi
|
||||
echo "Launching Chrome with CDP on port $PORT..."
|
||||
"$CHROME" \
|
||||
--remote-debugging-port="$PORT" \
|
||||
--remote-debugging-address=127.0.0.1 \
|
||||
--remote-allow-origins="http://127.0.0.1:$PORT" \
|
||||
--user-data-dir="$CDP_DATA_DIR" \
|
||||
--restore-last-session &
|
||||
disown
|
||||
|
||||
+44
-3
@@ -13,6 +13,38 @@ set -euo pipefail
|
||||
STATE_DIR="${GSTACK_STATE_DIR:-$HOME/.gstack}"
|
||||
CONFIG_FILE="$STATE_DIR/config.yaml"
|
||||
|
||||
# Annotated header for new config files. Written once on first `set`.
|
||||
CONFIG_HEADER='# gstack configuration — edit freely, changes take effect on next skill run.
|
||||
# Docs: https://github.com/garrytan/gstack
|
||||
#
|
||||
# ─── Behavior ────────────────────────────────────────────────────────
|
||||
# proactive: true # Auto-invoke skills when your request matches one.
|
||||
# # Set to false to only run skills you type explicitly.
|
||||
#
|
||||
# routing_declined: false # Set to true to skip the CLAUDE.md routing injection
|
||||
# # prompt. Set back to false to be asked again.
|
||||
#
|
||||
# ─── Telemetry ───────────────────────────────────────────────────────
|
||||
# telemetry: anonymous # off | anonymous | community
|
||||
# # off — no data sent, no local analytics
|
||||
# # anonymous — counter only, no device ID
|
||||
# # community — usage data + stable device ID
|
||||
#
|
||||
# ─── Updates ─────────────────────────────────────────────────────────
|
||||
# auto_upgrade: false # true = silently upgrade on session start
|
||||
# update_check: true # false = suppress version check notifications
|
||||
#
|
||||
# ─── Skill naming ────────────────────────────────────────────────────
|
||||
# skill_prefix: false # true = namespace skills as /gstack-qa, /gstack-ship
|
||||
# # false = short names /qa, /ship
|
||||
#
|
||||
# ─── Advanced ────────────────────────────────────────────────────────
|
||||
# codex_reviews: enabled # disabled = skip Codex adversarial reviews in /ship
|
||||
# gstack_contributor: false # true = file field reports when gstack misbehaves
|
||||
# skip_eng_review: false # true = skip eng review gate in /ship (not recommended)
|
||||
#
|
||||
'
|
||||
|
||||
case "${1:-}" in
|
||||
get)
|
||||
KEY="${2:?Usage: gstack-config get <key>}"
|
||||
@@ -21,7 +53,7 @@ case "${1:-}" in
|
||||
echo "Error: key must contain only alphanumeric characters and underscores" >&2
|
||||
exit 1
|
||||
fi
|
||||
grep -F "${KEY}:" "$CONFIG_FILE" 2>/dev/null | tail -1 | awk '{print $2}' | tr -d '[:space:]' || true
|
||||
grep -E "^${KEY}:" "$CONFIG_FILE" 2>/dev/null | tail -1 | awk '{print $2}' | tr -d '[:space:]' || true
|
||||
;;
|
||||
set)
|
||||
KEY="${2:?Usage: gstack-config set <key> <value>}"
|
||||
@@ -32,15 +64,24 @@ case "${1:-}" in
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$STATE_DIR"
|
||||
# Write annotated header on first creation
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
printf '%s' "$CONFIG_HEADER" > "$CONFIG_FILE"
|
||||
fi
|
||||
# Escape sed special chars in value and drop embedded newlines
|
||||
ESC_VALUE="$(printf '%s' "$VALUE" | head -1 | sed 's/[&/\]/\\&/g')"
|
||||
if grep -qF "${KEY}:" "$CONFIG_FILE" 2>/dev/null; then
|
||||
if grep -qE "^${KEY}:" "$CONFIG_FILE" 2>/dev/null; then
|
||||
# Portable in-place edit (BSD sed uses -i '', GNU sed uses -i without arg)
|
||||
_tmpfile="$(mktemp "${CONFIG_FILE}.XXXXXX")"
|
||||
sed "s/^${KEY}:.*/${KEY}: ${ESC_VALUE}/" "$CONFIG_FILE" > "$_tmpfile" && mv "$_tmpfile" "$CONFIG_FILE"
|
||||
sed "/^${KEY}:/s/.*/${KEY}: ${ESC_VALUE}/" "$CONFIG_FILE" > "$_tmpfile" && mv "$_tmpfile" "$CONFIG_FILE"
|
||||
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
|
||||
|
||||
@@ -16,6 +16,9 @@ if [ -z "$FILES" ]; then
|
||||
echo "SCOPE_TESTS=false"
|
||||
echo "SCOPE_DOCS=false"
|
||||
echo "SCOPE_CONFIG=false"
|
||||
echo "SCOPE_MIGRATIONS=false"
|
||||
echo "SCOPE_API=false"
|
||||
echo "SCOPE_AUTH=false"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -25,6 +28,9 @@ PROMPTS=false
|
||||
TESTS=false
|
||||
DOCS=false
|
||||
CONFIG=false
|
||||
MIGRATIONS=false
|
||||
API=false
|
||||
AUTH=false
|
||||
|
||||
while IFS= read -r f; do
|
||||
case "$f" in
|
||||
@@ -57,6 +63,16 @@ while IFS= read -r f; do
|
||||
.github/*) CONFIG=true ;;
|
||||
requirements.txt|pyproject.toml|go.mod|Cargo.toml|composer.json) CONFIG=true ;;
|
||||
|
||||
# Migrations: database migration files
|
||||
db/migrate/*|*/migrations/*|alembic/*|prisma/migrations/*) MIGRATIONS=true ;;
|
||||
|
||||
# API: routes, controllers, endpoints, GraphQL/OpenAPI schemas
|
||||
*controller*|*route*|*endpoint*|*/api/*) API=true ;;
|
||||
*.graphql|*.gql|openapi.*|swagger.*) API=true ;;
|
||||
|
||||
# Auth: authentication, authorization, sessions, permissions
|
||||
*auth*|*session*|*jwt*|*oauth*|*permission*|*role*) AUTH=true ;;
|
||||
|
||||
# Backend: everything else that's code (excluding views/components already matched)
|
||||
*.rb|*.py|*.go|*.rs|*.java|*.php|*.ex|*.exs) BACKEND=true ;;
|
||||
*.ts|*.js) BACKEND=true ;; # Non-component TS/JS is backend
|
||||
@@ -69,3 +85,6 @@ echo "SCOPE_PROMPTS=$PROMPTS"
|
||||
echo "SCOPE_TESTS=$TESTS"
|
||||
echo "SCOPE_DOCS=$DOCS"
|
||||
echo "SCOPE_CONFIG=$CONFIG"
|
||||
echo "SCOPE_MIGRATIONS=$MIGRATIONS"
|
||||
echo "SCOPE_API=$API"
|
||||
echo "SCOPE_AUTH=$AUTH"
|
||||
|
||||
Executable
+14
@@ -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
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
# gstack-patch-names — patch name: field in SKILL.md frontmatter for prefix mode
|
||||
# Usage: gstack-patch-names <gstack-dir> <true|false|1|0>
|
||||
set -euo pipefail
|
||||
|
||||
GSTACK_DIR="$1"
|
||||
DO_PREFIX="$2"
|
||||
|
||||
# Normalize prefix arg
|
||||
case "$DO_PREFIX" in true|1) DO_PREFIX=1 ;; *) DO_PREFIX=0 ;; esac
|
||||
|
||||
PATCHED=0
|
||||
for skill_dir in "$GSTACK_DIR"/*/; do
|
||||
[ -f "$skill_dir/SKILL.md" ] || continue
|
||||
dir_name="$(basename "$skill_dir")"
|
||||
[ "$dir_name" = "node_modules" ] && continue
|
||||
cur=$(grep -m1 '^name:' "$skill_dir/SKILL.md" 2>/dev/null | sed 's/^name:[[:space:]]*//' | tr -d '[:space:]' || true)
|
||||
[ -z "$cur" ] && continue
|
||||
[ "$cur" = "gstack" ] && continue # never prefix root skill
|
||||
if [ "$DO_PREFIX" -eq 1 ]; then
|
||||
case "$cur" in gstack-*) continue ;; esac
|
||||
new="gstack-$cur"
|
||||
else
|
||||
case "$cur" in gstack-*) ;; *) continue ;; esac
|
||||
[ "$dir_name" = "$cur" ] && continue # inherently prefixed (gstack-upgrade)
|
||||
new="${cur#gstack-}"
|
||||
fi
|
||||
tmp="$(mktemp "${skill_dir}/SKILL.md.XXXXXX")"
|
||||
sed "1,/^---$/s/^name:[[:space:]]*${cur}/name: ${new}/" "$skill_dir/SKILL.md" > "$tmp" && mv "$tmp" "$skill_dir/SKILL.md"
|
||||
PATCHED=$((PATCHED + 1))
|
||||
done
|
||||
if [ "$PATCHED" -gt 0 ]; then
|
||||
echo " patched name: field in $PATCHED skills"
|
||||
fi
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
#!/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
|
||||
|
||||
# Patch SKILL.md name: fields to match prefix setting
|
||||
"$INSTALL_DIR/bin/gstack-patch-names" "$INSTALL_DIR" "$PREFIX"
|
||||
|
||||
if [ "$PREFIX" = "true" ]; then
|
||||
echo "Relinked $SKILL_COUNT skills as gstack-*"
|
||||
else
|
||||
echo "Relinked $SKILL_COUNT skills as flat names"
|
||||
fi
|
||||
Reference in New Issue
Block a user