merge: resolve version/changelog conflicts with main (v0.12.8.1)

Main shipped v0.12.8.1 (zsh glob compatibility — setopt guards and
find-based alternatives for 38 glob patterns across 13 templates).
Resolution: keep v0.13.0.0, both CHANGELOG entries preserved.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-03-27 00:28:37 -06:00
31 changed files with 119 additions and 19 deletions
+10
View File
@@ -29,6 +29,16 @@ gstack can generate real UI mockups. Not ASCII art, not text descriptions of hex
- `design/dist/` gitignored like `browse/dist/`
- Full design doc: `docs/designs/DESIGN_TOOLS_V1.md`
## [0.12.8.1] - 2026-03-27 — zsh Glob Compatibility
Skill scripts now work correctly in zsh. Previously, bash code blocks in skill templates used raw glob patterns like `.github/workflows/*.yaml` and `ls ~/.gstack/projects/$SLUG/*-design-*.md` that would throw "no matches found" errors in zsh when no files matched. Fixed 38 instances across 13 templates and 2 resolvers using two approaches: `find`-based alternatives for complex patterns, and `setopt +o nomatch` guards for simple `ls` commands.
### Fixed
- **`.github/workflows/` globs replaced with `find`.** `cat .github/workflows/*deploy*`, `for f in .github/workflows/*.yml`, and `ls .github/workflows/*.yaml` patterns in `/land-and-deploy`, `/setup-deploy`, `/cso`, and the deploy bootstrap resolver now use `find ... -name` instead of raw globs.
- **`~/.gstack/` and `~/.claude/` globs guarded with `setopt`.** Design doc lookups, eval result listings, test plan discovery, and retro history checks across 10 skills now prepend `setopt +o nomatch 2>/dev/null || true` (no-op in bash, disables NOMATCH in zsh).
- **Test framework detection globs guarded.** `ls jest.config.* vitest.config.*` in the testing resolver now has a setopt guard.
## [0.12.8.0] - 2026-03-27 — Codex No Longer Reviews the Wrong Project
When you run gstack in Conductor with multiple workspaces open, Codex could silently review the wrong project. The `codex exec -C` flag resolved the repo root inline via `$(git rev-parse --show-toplevel)`, which evaluates in whatever cwd the background shell inherits. In multi-workspace environments, that cwd might be a different project entirely.
+1
View File
@@ -408,6 +408,7 @@ If the Read fails (file not found), say:
After /office-hours completes, re-run the design doc check:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)")
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-' || echo 'no-branch')
DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1)
+1
View File
@@ -650,6 +650,7 @@ TMPERR=$(mktemp /tmp/codex-err-XXXXXX.txt)
3. **Plan review auto-detection:** If the user's prompt is about reviewing a plan,
or if plan files exist and the user said `/codex` with no arguments:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
ls -t ~/.claude/plans/*.md 2>/dev/null | xargs grep -l "$(basename $(pwd))" 2>/dev/null | head -1
```
If no project-scoped match, fall back to `ls -t ~/.claude/plans/*.md 2>/dev/null | head -1`
+1
View File
@@ -245,6 +245,7 @@ TMPERR=$(mktemp /tmp/codex-err-XXXXXX.txt)
3. **Plan review auto-detection:** If the user's prompt is about reviewing a plan,
or if plan files exist and the user said `/codex` with no arguments:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
ls -t ~/.claude/plans/*.md 2>/dev/null | xargs grep -l "$(basename $(pwd))" 2>/dev/null | head -1
```
If no project-scoped match, fall back to `ls -t ~/.claude/plans/*.md 2>/dev/null | head -1`
+4 -3
View File
@@ -358,7 +358,7 @@ ls go.mod 2>/dev/null && echo "STACK: Go"
ls Cargo.toml 2>/dev/null && echo "STACK: Rust"
ls pom.xml build.gradle 2>/dev/null && echo "STACK: JVM"
ls composer.json 2>/dev/null && echo "STACK: PHP"
ls *.csproj *.sln 2>/dev/null && echo "STACK: .NET"
find . -maxdepth 1 \( -name '*.csproj' -o -name '*.sln' \) 2>/dev/null | grep -q . && echo "STACK: .NET"
```
**Framework detection:**
@@ -395,7 +395,8 @@ Map what an attacker sees — both code surface and infrastructure surface.
**Infrastructure surface:**
```bash
ls .github/workflows/*.yml .github/workflows/*.yaml .gitlab-ci.yml 2>/dev/null | wc -l
setopt +o nomatch 2>/dev/null || true # zsh compat
{ find .github/workflows -maxdepth 1 \( -name '*.yml' -o -name '*.yaml' \) 2>/dev/null; [ -f .gitlab-ci.yml ] && echo .gitlab-ci.yml; } | wc -l
find . -maxdepth 4 -name "Dockerfile*" -o -name "docker-compose*.yml" 2>/dev/null
find . -maxdepth 4 -name "*.tf" -o -name "*.tfvars" -o -name "kustomization.yaml" 2>/dev/null
ls .env .env.* 2>/dev/null
@@ -445,7 +446,7 @@ grep -q "^\.env$\|^\.env\.\*" .gitignore 2>/dev/null && echo ".env IS gitignored
**CI configs with inline secrets (not using secret stores):**
```bash
for f in .github/workflows/*.yml .github/workflows/*.yaml .gitlab-ci.yml .circleci/config.yml; do
for f in $(find .github/workflows -maxdepth 1 \( -name '*.yml' -o -name '*.yaml' \) 2>/dev/null) .gitlab-ci.yml .circleci/config.yml; do
[ -f "$f" ] && grep -n "password:\|token:\|secret:\|api_key:" "$f" | grep -v '\${{' | grep -v 'secrets\.'
done 2>/dev/null
```
+4 -3
View File
@@ -73,7 +73,7 @@ ls go.mod 2>/dev/null && echo "STACK: Go"
ls Cargo.toml 2>/dev/null && echo "STACK: Rust"
ls pom.xml build.gradle 2>/dev/null && echo "STACK: JVM"
ls composer.json 2>/dev/null && echo "STACK: PHP"
ls *.csproj *.sln 2>/dev/null && echo "STACK: .NET"
find . -maxdepth 1 \( -name '*.csproj' -o -name '*.sln' \) 2>/dev/null | grep -q . && echo "STACK: .NET"
```
**Framework detection:**
@@ -110,7 +110,8 @@ Map what an attacker sees — both code surface and infrastructure surface.
**Infrastructure surface:**
```bash
ls .github/workflows/*.yml .github/workflows/*.yaml .gitlab-ci.yml 2>/dev/null | wc -l
setopt +o nomatch 2>/dev/null || true # zsh compat
{ find .github/workflows -maxdepth 1 \( -name '*.yml' -o -name '*.yaml' \) 2>/dev/null; [ -f .gitlab-ci.yml ] && echo .gitlab-ci.yml; } | wc -l
find . -maxdepth 4 -name "Dockerfile*" -o -name "docker-compose*.yml" 2>/dev/null
find . -maxdepth 4 -name "*.tf" -o -name "*.tfvars" -o -name "kustomization.yaml" 2>/dev/null
ls .env .env.* 2>/dev/null
@@ -160,7 +161,7 @@ grep -q "^\.env$\|^\.env\.\*" .gitignore 2>/dev/null && echo ".env IS gitignored
**CI configs with inline secrets (not using secret stores):**
```bash
for f in .github/workflows/*.yml .github/workflows/*.yaml .gitlab-ci.yml .circleci/config.yml; do
for f in $(find .github/workflows -maxdepth 1 \( -name '*.yml' -o -name '*.yaml' \) 2>/dev/null) .gitlab-ci.yml .circleci/config.yml; do
[ -f "$f" ] && grep -n "password:\|token:\|secret:\|api_key:" "$f" | grep -v '\${{' | grep -v 'secrets\.'
done 2>/dev/null
```
+1
View File
@@ -356,6 +356,7 @@ ls src/ app/ pages/ components/ 2>/dev/null | head -30
Look for office-hours output:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)"
ls ~/.gstack/projects/$SLUG/*office-hours* 2>/dev/null | head -5
ls .context/*office-hours* .context/attachments/*office-hours* 2>/dev/null | head -5
+1
View File
@@ -53,6 +53,7 @@ ls src/ app/ pages/ components/ 2>/dev/null | head -30
Look for office-hours output:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
{{SLUG_EVAL}}
ls ~/.gstack/projects/$SLUG/*office-hours* 2>/dev/null | head -5
ls .context/*office-hours* .context/attachments/*office-hours* 2>/dev/null | head -5
+1
View File
@@ -401,6 +401,7 @@ If `NEEDS_SETUP`:
**Detect existing test framework and project runtime:**
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
# Detect project runtime
[ -f Gemfile ] && echo "RUNTIME:ruby"
[ -f package.json ] && echo "RUNTIME:node"
+7 -5
View File
@@ -470,7 +470,7 @@ else
SAVED_HASH=$(cat ~/.gstack/projects/$SLUG/land-deploy-confirmed 2>/dev/null)
CURRENT_HASH=$(sed -n '/## Deploy Configuration/,/^## /p' CLAUDE.md 2>/dev/null | shasum -a 256 | cut -d' ' -f1)
# Also hash workflow files that affect deploy behavior
WORKFLOW_HASH=$(cat .github/workflows/*deploy* .github/workflows/*cd* 2>/dev/null | shasum -a 256 | cut -d' ' -f1)
WORKFLOW_HASH=$(find .github/workflows -maxdepth 1 \( -name '*deploy*' -o -name '*cd*' \) 2>/dev/null | xargs cat 2>/dev/null | shasum -a 256 | cut -d' ' -f1)
COMBINED_HASH="${CURRENT_HASH}-${WORKFLOW_HASH}"
if [ "$SAVED_HASH" != "$COMBINED_HASH" ] && [ -n "$SAVED_HASH" ]; then
echo "CONFIG_CHANGED"
@@ -527,7 +527,7 @@ fi
([ -f railway.json ] || [ -f railway.toml ]) && echo "PLATFORM:railway"
# Detect deploy workflows
for f in .github/workflows/*.yml .github/workflows/*.yaml; do
for f in $(find .github/workflows -maxdepth 1 \( -name '*.yml' -o -name '*.yaml' \) 2>/dev/null); do
[ -f "$f" ] && grep -qiE "deploy|release|production|cd" "$f" 2>/dev/null && echo "DEPLOY_WORKFLOW:$f"
[ -f "$f" ] && grep -qiE "staging" "$f" 2>/dev/null && echo "STAGING_WORKFLOW:$f"
done
@@ -613,7 +613,7 @@ grep -i "staging" CLAUDE.md 2>/dev/null | head -3
2. **GitHub Actions staging workflow:** Check for workflow files with "staging" in the name or content:
```bash
for f in .github/workflows/*.yml .github/workflows/*.yaml; do
for f in $(find .github/workflows -maxdepth 1 \( -name '*.yml' -o -name '*.yaml' \) 2>/dev/null); do
[ -f "$f" ] && grep -qiE "staging" "$f" 2>/dev/null && echo "STAGING_WORKFLOW:$f"
done
```
@@ -663,7 +663,7 @@ Save the deploy config fingerprint so we can detect future changes:
```bash
mkdir -p ~/.gstack/projects/$SLUG
CURRENT_HASH=$(sed -n '/## Deploy Configuration/,/^## /p' CLAUDE.md 2>/dev/null | shasum -a 256 | cut -d' ' -f1)
WORKFLOW_HASH=$(cat .github/workflows/*deploy* .github/workflows/*cd* 2>/dev/null | shasum -a 256 | cut -d' ' -f1)
WORKFLOW_HASH=$(find .github/workflows -maxdepth 1 \( -name '*deploy*' -o -name '*cd*' \) 2>/dev/null | xargs cat 2>/dev/null | shasum -a 256 | cut -d' ' -f1)
echo "${CURRENT_HASH}-${WORKFLOW_HASH}" > ~/.gstack/projects/$SLUG/land-deploy-confirmed
```
Continue to Step 2.
@@ -805,6 +805,7 @@ If tests fail: **BLOCKER.** Cannot merge with failing tests.
**E2E tests — check recent results:**
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
ls -t ~/.gstack-dev/evals/*-e2e-*-$(date +%Y-%m-%d)*.json 2>/dev/null | head -20
```
@@ -820,6 +821,7 @@ If E2E results exist but have failures: **WARNING — N tests failed.** List the
**LLM judge evals — check recent results:**
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
ls -t ~/.gstack-dev/evals/*-llm-judge-*-$(date +%Y-%m-%d)*.json 2>/dev/null | head -5
```
@@ -1025,7 +1027,7 @@ fi
([ -f railway.json ] || [ -f railway.toml ]) && echo "PLATFORM:railway"
# Detect deploy workflows
for f in .github/workflows/*.yml .github/workflows/*.yaml; do
for f in $(find .github/workflows -maxdepth 1 \( -name '*.yml' -o -name '*.yaml' \) 2>/dev/null); do
[ -f "$f" ] && grep -qiE "deploy|release|production|cd" "$f" 2>/dev/null && echo "DEPLOY_WORKFLOW:$f"
[ -f "$f" ] && grep -qiE "staging" "$f" 2>/dev/null && echo "STAGING_WORKFLOW:$f"
done
+5 -3
View File
@@ -113,7 +113,7 @@ else
SAVED_HASH=$(cat ~/.gstack/projects/$SLUG/land-deploy-confirmed 2>/dev/null)
CURRENT_HASH=$(sed -n '/## Deploy Configuration/,/^## /p' CLAUDE.md 2>/dev/null | shasum -a 256 | cut -d' ' -f1)
# Also hash workflow files that affect deploy behavior
WORKFLOW_HASH=$(cat .github/workflows/*deploy* .github/workflows/*cd* 2>/dev/null | shasum -a 256 | cut -d' ' -f1)
WORKFLOW_HASH=$(find .github/workflows -maxdepth 1 \( -name '*deploy*' -o -name '*cd*' \) 2>/dev/null | xargs cat 2>/dev/null | shasum -a 256 | cut -d' ' -f1)
COMBINED_HASH="${CURRENT_HASH}-${WORKFLOW_HASH}"
if [ "$SAVED_HASH" != "$COMBINED_HASH" ] && [ -n "$SAVED_HASH" ]; then
echo "CONFIG_CHANGED"
@@ -223,7 +223,7 @@ grep -i "staging" CLAUDE.md 2>/dev/null | head -3
2. **GitHub Actions staging workflow:** Check for workflow files with "staging" in the name or content:
```bash
for f in .github/workflows/*.yml .github/workflows/*.yaml; do
for f in $(find .github/workflows -maxdepth 1 \( -name '*.yml' -o -name '*.yaml' \) 2>/dev/null); do
[ -f "$f" ] && grep -qiE "staging" "$f" 2>/dev/null && echo "STAGING_WORKFLOW:$f"
done
```
@@ -273,7 +273,7 @@ Save the deploy config fingerprint so we can detect future changes:
```bash
mkdir -p ~/.gstack/projects/$SLUG
CURRENT_HASH=$(sed -n '/## Deploy Configuration/,/^## /p' CLAUDE.md 2>/dev/null | shasum -a 256 | cut -d' ' -f1)
WORKFLOW_HASH=$(cat .github/workflows/*deploy* .github/workflows/*cd* 2>/dev/null | shasum -a 256 | cut -d' ' -f1)
WORKFLOW_HASH=$(find .github/workflows -maxdepth 1 \( -name '*deploy*' -o -name '*cd*' \) 2>/dev/null | xargs cat 2>/dev/null | shasum -a 256 | cut -d' ' -f1)
echo "${CURRENT_HASH}-${WORKFLOW_HASH}" > ~/.gstack/projects/$SLUG/land-deploy-confirmed
```
Continue to Step 2.
@@ -415,6 +415,7 @@ If tests fail: **BLOCKER.** Cannot merge with failing tests.
**E2E tests — check recent results:**
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
ls -t ~/.gstack-dev/evals/*-e2e-*-$(date +%Y-%m-%d)*.json 2>/dev/null | head -20
```
@@ -430,6 +431,7 @@ If E2E results exist but have failures: **WARNING — N tests failed.** List the
**LLM judge evals — check recent results:**
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
ls -t ~/.gstack-dev/evals/*-llm-judge-*-$(date +%Y-%m-%d)*.json 2>/dev/null | head -5
```
+3
View File
@@ -368,6 +368,7 @@ eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)"
3. Use Grep/Glob to map the codebase areas most relevant to the user's request.
4. **List existing design docs for this project:**
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null
```
If design docs exist, list them: "Prior designs for this project: [titles + dates]"
@@ -598,6 +599,7 @@ After the user states the problem (first question in Phase 2A or 2B), search exi
Extract 3-5 significant keywords from the user's problem statement and grep across design docs:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
grep -li "<keyword1>\|<keyword2>\|<keyword3>" ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null
```
@@ -1007,6 +1009,7 @@ DATETIME=$(date +%Y%m%d-%H%M%S)
**Design lineage:** Before writing, check for existing design docs on this branch:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
PRIOR=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1)
```
If `$PRIOR` exists, the new doc gets a `Supersedes:` field referencing it. This creates a revision chain — you can trace how a design evolved across office hours sessions.
+3
View File
@@ -48,6 +48,7 @@ Understand the project and the area the user wants to change.
3. Use Grep/Glob to map the codebase areas most relevant to the user's request.
4. **List existing design docs for this project:**
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null
```
If design docs exist, list them: "Prior designs for this project: [titles + dates]"
@@ -278,6 +279,7 @@ After the user states the problem (first question in Phase 2A or 2B), search exi
Extract 3-5 significant keywords from the user's problem statement and grep across design docs:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
grep -li "<keyword1>\|<keyword2>\|<keyword3>" ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null
```
@@ -424,6 +426,7 @@ DATETIME=$(date +%Y%m%d-%H%M%S)
**Design lineage:** Before writing, check for existing design docs on this branch:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
PRIOR=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1)
```
If `$PRIOR` exists, the new doc gets a `Supersedes:` field referencing it. This creates a revision chain — you can trace how a design evolved across office hours sessions.
+4
View File
@@ -445,6 +445,7 @@ Then read CLAUDE.md, TODOS.md, and any existing architecture docs.
**Design doc check:**
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)")
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-' || echo 'no-branch')
DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1)
@@ -455,6 +456,7 @@ If a design doc exists (from `/office-hours`), read it. Use it as the source of
**Handoff note check** (reuses $SLUG and $BRANCH from the design doc check above):
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
HANDOFF=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-ceo-handoff-*.md 2>/dev/null | head -1)
[ -n "$HANDOFF" ] && echo "HANDOFF_FOUND: $HANDOFF" || echo "NO_HANDOFF"
```
@@ -509,6 +511,7 @@ If the Read fails (file not found), say:
After /office-hours completes, re-run the design doc check:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)")
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-' || echo 'no-branch')
DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1)
@@ -1270,6 +1273,7 @@ After producing the Completion Summary, clean up any handoff notes for this bran
the review is complete and the context is no longer needed.
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)"
rm -f ~/.gstack/projects/$SLUG/*-$BRANCH-ceo-handoff-*.md 2>/dev/null || true
```
+3
View File
@@ -105,6 +105,7 @@ Then read CLAUDE.md, TODOS.md, and any existing architecture docs.
**Design doc check:**
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)")
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-' || echo 'no-branch')
DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1)
@@ -115,6 +116,7 @@ If a design doc exists (from `/office-hours`), read it. Use it as the source of
**Handoff note check** (reuses $SLUG and $BRANCH from the design doc check above):
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
HANDOFF=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-ceo-handoff-*.md 2>/dev/null | head -1)
[ -n "$HANDOFF" ] && echo "HANDOFF_FOUND: $HANDOFF" || echo "NO_HANDOFF"
```
@@ -703,6 +705,7 @@ After producing the Completion Summary, clean up any handoff notes for this bran
the review is complete and the context is no longer needed.
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
{{SLUG_EVAL}}
rm -f ~/.gstack/projects/$SLUG/*-$BRANCH-ceo-handoff-*.md 2>/dev/null || true
```
+3
View File
@@ -371,6 +371,7 @@ When evaluating architecture, think "boring by default." When reviewing tests, t
### Design Doc Check
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)")
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-' || echo 'no-branch')
DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1)
@@ -420,6 +421,7 @@ If the Read fails (file not found), say:
After /office-hours completes, re-run the design doc check:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)")
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-' || echo 'no-branch')
DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1)
@@ -497,6 +499,7 @@ Before analyzing coverage, detect the project's test framework:
2. **If CLAUDE.md has no testing section, auto-detect:**
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
# Detect project runtime
[ -f Gemfile ] && echo "RUNTIME:ruby"
[ -f package.json ] && echo "RUNTIME:node"
+1
View File
@@ -68,6 +68,7 @@ When evaluating architecture, think "boring by default." When reviewing tests, t
### Design Doc Check
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)")
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-' || echo 'no-branch')
DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1)
+1
View File
@@ -375,6 +375,7 @@ Before falling back to git diff heuristics, check for richer test plan sources:
1. **Project-scoped test plans:** Check `~/.gstack/projects/` for recent `*-test-plan-*.md` files for this repo
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)"
ls -t ~/.gstack/projects/$SLUG/*-test-plan-*.md 2>/dev/null | head -1
```
+1
View File
@@ -55,6 +55,7 @@ Before falling back to git diff heuristics, check for richer test plan sources:
1. **Project-scoped test plans:** Check `~/.gstack/projects/` for recent `*-test-plan-*.md` files for this repo
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
{{SLUG_EVAL}}
ls -t ~/.gstack/projects/$SLUG/*-test-plan-*.md 2>/dev/null | head -1
```
+2
View File
@@ -442,6 +442,7 @@ If `NEEDS_SETUP`:
**Detect existing test framework and project runtime:**
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
# Detect project runtime
[ -f Gemfile ] && echo "RUNTIME:ruby"
[ -f package.json ] && echo "RUNTIME:node"
@@ -604,6 +605,7 @@ Before falling back to git diff heuristics, check for richer test plan sources:
1. **Project-scoped test plans:** Check `~/.gstack/projects/` for recent `*-test-plan-*.md` files for this repo
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)"
ls -t ~/.gstack/projects/$SLUG/*-test-plan-*.md 2>/dev/null | head -1
```
+1
View File
@@ -96,6 +96,7 @@ Before falling back to git diff heuristics, check for richer test plan sources:
1. **Project-scoped test plans:** Check `~/.gstack/projects/` for recent `*-test-plan-*.md` files for this repo
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
{{SLUG_EVAL}}
ls -t ~/.gstack/projects/$SLUG/*-test-plan-*.md 2>/dev/null | head -1
```
+5
View File
@@ -629,6 +629,7 @@ Count backward from today — how many consecutive days have at least one commit
Before saving the new snapshot, check for prior retro history:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
ls -t .context/retros/*.json 2>/dev/null
```
@@ -655,6 +656,7 @@ mkdir -p .context/retros
Determine the next sequence number for today (substitute the actual date for `$(date +%Y-%m-%d)`):
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
# Count existing retros for today to get next sequence number
today=$(date +%Y-%m-%d)
existing=$(ls .context/retros/${today}-*.json 2>/dev/null | wc -l | tr -d ' ')
@@ -778,6 +780,7 @@ Narrative covering:
Check review JSONL logs for plan completion data from /ship runs this period:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)"
cat ~/.gstack/projects/$SLUG/*-reviews.jsonl 2>/dev/null | grep '"skill":"ship"' | grep '"plan_items_total"' || echo "NO_PLAN_DATA"
```
@@ -1079,6 +1082,7 @@ Considering the full cross-project picture.
### Global Step 8: Load history & compare
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
ls -t ~/.gstack/retros/global-*.json 2>/dev/null | head -5
```
@@ -1096,6 +1100,7 @@ mkdir -p ~/.gstack/retros
Determine the next sequence number for today:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
today=$(date +%Y-%m-%d)
existing=$(ls ~/.gstack/retros/global-${today}-*.json 2>/dev/null | wc -l | tr -d ' ')
next=$((existing + 1))
+5
View File
@@ -307,6 +307,7 @@ Count backward from today — how many consecutive days have at least one commit
Before saving the new snapshot, check for prior retro history:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
ls -t .context/retros/*.json 2>/dev/null
```
@@ -333,6 +334,7 @@ mkdir -p .context/retros
Determine the next sequence number for today (substitute the actual date for `$(date +%Y-%m-%d)`):
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
# Count existing retros for today to get next sequence number
today=$(date +%Y-%m-%d)
existing=$(ls .context/retros/${today}-*.json 2>/dev/null | wc -l | tr -d ' ')
@@ -456,6 +458,7 @@ Narrative covering:
Check review JSONL logs for plan completion data from /ship runs this period:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)"
cat ~/.gstack/projects/$SLUG/*-reviews.jsonl 2>/dev/null | grep '"skill":"ship"' | grep '"plan_items_total"' || echo "NO_PLAN_DATA"
```
@@ -757,6 +760,7 @@ Considering the full cross-project picture.
### Global Step 8: Load history & compare
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
ls -t ~/.gstack/retros/global-*.json 2>/dev/null | head -5
```
@@ -774,6 +778,7 @@ mkdir -p ~/.gstack/retros
Determine the next sequence number for today:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
today=$(date +%Y-%m-%d)
existing=$(ls ~/.gstack/retros/global-${today}-*.json 2>/dev/null | wc -l | tr -d ' ')
next=$((existing + 1))
+2
View File
@@ -394,6 +394,7 @@ Before reviewing code quality, check: **did they build what was requested — no
2. **Content-based search (fallback):** If no plan file is referenced in conversation context, search by content:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
BRANCH=$(git branch --show-current 2>/dev/null | tr '/' '-')
REPO=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)")
# Search common plan file locations
@@ -650,6 +651,7 @@ Before analyzing coverage, detect the project's test framework:
2. **If CLAUDE.md has no testing section, auto-detect:**
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
# Detect project runtime
[ -f Gemfile ] && echo "RUNTIME:ruby"
[ -f package.json ] && echo "RUNTIME:node"
+2
View File
@@ -233,6 +233,7 @@ If the Read fails (file not found), say:
After /${first} completes, re-run the design doc check:
\`\`\`bash
setopt +o nomatch 2>/dev/null || true # zsh compat
SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)")
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-' || echo 'no-branch')
DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1)
@@ -614,6 +615,7 @@ function generatePlanFileDiscovery(): string {
2. **Content-based search (fallback):** If no plan file is referenced in conversation context, search by content:
\`\`\`bash
setopt +o nomatch 2>/dev/null || true # zsh compat
BRANCH=$(git branch --show-current 2>/dev/null | tr '/' '-')
REPO=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)")
# Search common plan file locations
+2
View File
@@ -6,6 +6,7 @@ export function generateTestBootstrap(_ctx: TemplateContext): string {
**Detect existing test framework and project runtime:**
\`\`\`bash
setopt +o nomatch 2>/dev/null || true # zsh compat
# Detect project runtime
[ -f Gemfile ] && echo "RUNTIME:ruby"
[ -f package.json ] && echo "RUNTIME:node"
@@ -200,6 +201,7 @@ Before analyzing coverage, detect the project's test framework:
2. **If CLAUDE.md has no testing section, auto-detect:**
\`\`\`bash
setopt +o nomatch 2>/dev/null || true # zsh compat
# Detect project runtime
[ -f Gemfile ] && echo "RUNTIME:ruby"
[ -f package.json ] && echo "RUNTIME:node"
+1 -1
View File
@@ -72,7 +72,7 @@ fi
([ -f railway.json ] || [ -f railway.toml ]) && echo "PLATFORM:railway"
# Detect deploy workflows
for f in .github/workflows/*.yml .github/workflows/*.yaml; do
for f in $(find .github/workflows -maxdepth 1 \\( -name '*.yml' -o -name '*.yaml' \\) 2>/dev/null); do
[ -f "$f" ] && grep -qiE "deploy|release|production|cd" "$f" 2>/dev/null && echo "DEPLOY_WORKFLOW:$f"
[ -f "$f" ] && grep -qiE "staging" "$f" 2>/dev/null && echo "STAGING_WORKFLOW:$f"
done
+2 -2
View File
@@ -349,13 +349,13 @@ Run the platform detection from the deploy bootstrap:
[ -f railway.json ] || [ -f railway.toml ] && echo "PLATFORM:railway"
# GitHub Actions deploy workflows
for f in .github/workflows/*.yml .github/workflows/*.yaml; do
for f in $(find .github/workflows -maxdepth 1 \( -name '*.yml' -o -name '*.yaml' \) 2>/dev/null); do
[ -f "$f" ] && grep -qiE "deploy|release|production|staging|cd" "$f" 2>/dev/null && echo "DEPLOY_WORKFLOW:$f"
done
# Project type
[ -f package.json ] && grep -q '"bin"' package.json 2>/dev/null && echo "PROJECT_TYPE:cli"
ls *.gemspec 2>/dev/null && echo "PROJECT_TYPE:library"
find . -maxdepth 1 -name '*.gemspec' 2>/dev/null | grep -q . && echo "PROJECT_TYPE:library"
```
### Step 3: Platform-specific setup
+2 -2
View File
@@ -64,13 +64,13 @@ Run the platform detection from the deploy bootstrap:
[ -f railway.json ] || [ -f railway.toml ] && echo "PLATFORM:railway"
# GitHub Actions deploy workflows
for f in .github/workflows/*.yml .github/workflows/*.yaml; do
for f in $(find .github/workflows -maxdepth 1 \( -name '*.yml' -o -name '*.yaml' \) 2>/dev/null); do
[ -f "$f" ] && grep -qiE "deploy|release|production|staging|cd" "$f" 2>/dev/null && echo "DEPLOY_WORKFLOW:$f"
done
# Project type
[ -f package.json ] && grep -q '"bin"' package.json 2>/dev/null && echo "PROJECT_TYPE:cli"
ls *.gemspec 2>/dev/null && echo "PROJECT_TYPE:library"
find . -maxdepth 1 -name '*.gemspec' 2>/dev/null | grep -q . && echo "PROJECT_TYPE:library"
```
### Step 3: Platform-specific setup
+3
View File
@@ -514,6 +514,7 @@ git fetch origin <base> && git merge origin/<base> --no-edit
**Detect existing test framework and project runtime:**
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
# Detect project runtime
[ -f Gemfile ] && echo "RUNTIME:ruby"
[ -f package.json ] && echo "RUNTIME:node"
@@ -866,6 +867,7 @@ Before analyzing coverage, detect the project's test framework:
2. **If CLAUDE.md has no testing section, auto-detect:**
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
# Detect project runtime
[ -f Gemfile ] && echo "RUNTIME:ruby"
[ -f package.json ] && echo "RUNTIME:node"
@@ -1124,6 +1126,7 @@ Repo: {owner/repo}
2. **Content-based search (fallback):** If no plan file is referenced in conversation context, search by content:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
BRANCH=$(git branch --show-current 2>/dev/null | tr '/' '-')
REPO=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)")
# Search common plan file locations
+37
View File
@@ -263,6 +263,43 @@ describe('gen-skill-docs', () => {
}
});
test('bash blocks with shell globs are zsh-safe (setopt guard or find)', () => {
for (const skill of ALL_SKILLS) {
const content = fs.readFileSync(path.join(ROOT, skill.dir, 'SKILL.md'), 'utf-8');
const bashBlocks = [...content.matchAll(/```bash\n([\s\S]*?)```/g)].map(m => m[1]);
for (const block of bashBlocks) {
const lines = block.split('\n');
for (const line of lines) {
const trimmed = line.trimStart();
if (trimmed.startsWith('#')) continue;
if (!trimmed.includes('*')) continue;
// Skip lines where * is inside find -name, git pathspecs, or $(find)
if (/\bfind\b/.test(trimmed)) continue;
if (/\bgit\b/.test(trimmed)) continue;
if (/\$\(find\b/.test(trimmed)) continue;
// Check 1: "for VAR in <glob>" must use $(find ...) — caught above by the
// $(find check, so any surviving for-in with a glob pattern is a violation
if (/\bfor\s+\w+\s+in\b/.test(trimmed) && /\*\./.test(trimmed)) {
throw new Error(
`Unsafe for-in glob in ${skill.dir}/SKILL.md: "${trimmed}". ` +
`Use \`for f in $(find ... -name '*.ext')\` for zsh compatibility.`
);
}
// Check 2: ls/cat/rm/grep with glob file args must have setopt guard
const isGlobCmd = /\b(?:ls|cat|rm|grep)\b/.test(trimmed) &&
/(?:\/\*[a-z.*]|\*\.[a-z])/.test(trimmed);
if (isGlobCmd) {
expect(block).toContain('setopt +o nomatch');
}
}
}
}
});
test('preamble-using skills have correct skill name in telemetry', () => {
const PREAMBLE_SKILLS = [
{ dir: '.', name: 'gstack' },