fix: dynamic base branch detection across all SKILL templates (v0.3.10) (#81)

* feat: add {{BASE_BRANCH_DETECT}} resolver to gen-skill-docs

DRY placeholder for dynamic base branch detection across PR-targeting
skills. Detects via gh pr view (existing PR base) → gh repo view
(repo default) → fallback to main.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: ship skill detects base branch instead of hardcoding main

Replaces ~14 hardcoded 'main' references with dynamic detection via
{{BASE_BRANCH_DETECT}}. Fixes stacked branches and Conductor workspaces
targeting non-main branches. Adds --base <base> to gh pr create.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: review, qa, plan-ceo-review detect base branch dynamically

Same pattern as ship: replaces hardcoded 'main' with {{BASE_BRANCH_DETECT}}.
Also cleans up qa bash-isms (REPORT_DIR variable, port chaining).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: retro detects default branch instead of hardcoding origin/main

Retro queries commit history (not PR targets), so uses simpler detection:
gh repo view defaultBranchRef. Replaces ~11 origin/main refs with
origin/<default>.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add explicit cross-step references in gstack-upgrade template

Bash blocks are self-contained, but cross-block variable references
(INSTALL_DIR from Step 2) were implicit. Adds prose making them explicit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs+test: SKILL authoring guidance + regression tests

Adds "Writing SKILL templates" section to CLAUDE.md explaining that
templates are prompts, not scripts. Adds validation test catching
hardcoded 'main' in git commands, and resolver content test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: update ARCHITECTURE + CONTRIBUTING for new placeholders

Add {{BASE_BRANCH_DETECT}} to ARCHITECTURE.md placeholder list.
Cross-reference CLAUDE.md template authoring guidance from CONTRIBUTING.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: bump version and changelog (v0.3.10)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add missing blank line between resolver functions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add 3 E2E smoke tests for base branch detection

- /review: verifies Step 0 detection + git diff against detected base
- /ship: truncated dry-run (Steps 0-1 only, no push/PR), asserts no
  destructive actions
- /retro: verifies default branch detection for git log queries

Covers the {{BASE_BRANCH_DETECT}} resolver path (review), the ship
template's dual abort check, and retro's inline detection pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: bump version and changelog (v0.4.2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-03-16 10:59:13 -05:00
committed by GitHub
parent 3e3843c4a9
commit 1e06b6a5c6
21 changed files with 514 additions and 77 deletions

View File

@@ -200,6 +200,8 @@ Templates contain the workflows, tips, and examples that require human judgment.
| `{{SNAPSHOT_FLAGS}}` | `snapshot.ts` | Flag reference with examples | | `{{SNAPSHOT_FLAGS}}` | `snapshot.ts` | Flag reference with examples |
| `{{PREAMBLE}}` | `gen-skill-docs.ts` | Startup block: update check, session tracking, contributor mode, AskUserQuestion format | | `{{PREAMBLE}}` | `gen-skill-docs.ts` | Startup block: update check, session tracking, contributor mode, AskUserQuestion format |
| `{{BROWSE_SETUP}}` | `gen-skill-docs.ts` | Binary discovery + setup instructions | | `{{BROWSE_SETUP}}` | `gen-skill-docs.ts` | Binary discovery + setup instructions |
| `{{BASE_BRANCH_DETECT}}` | `gen-skill-docs.ts` | Dynamic base branch detection for PR-targeting skills (ship, review, qa, plan-ceo-review) |
| `{{QA_METHODOLOGY}}` | `gen-skill-docs.ts` | Shared QA methodology block for /qa and /qa-only |
This is structurally sound — if a command exists in code, it appears in docs. If it doesn't exist, it can't appear. This is structurally sound — if a command exists in code, it appears in docs. If it doesn't exist, it can't appear.

View File

@@ -1,5 +1,19 @@
# Changelog # Changelog
## 0.4.2 — 2026-03-16
- **Skills now respect your branch target.** `/ship`, `/review`, `/qa`, and `/plan-ceo-review` detect which branch your PR actually targets instead of assuming `main`. Stacked branches, Conductor workspaces targeting feature branches, and repos using `master` all just work now.
- **`/retro` works on any default branch.** Repos using `master`, `develop`, or other default branch names are detected automatically — no more empty retros because the branch name was wrong.
- **New `{{BASE_BRANCH_DETECT}}` placeholder** for skill authors — drop it into any template and get 3-step branch detection (PR base → repo default → fallback) for free.
- **3 new E2E smoke tests** validate base branch detection works end-to-end across ship, review, and retro skills.
### For contributors
- Added "Writing SKILL templates" section to CLAUDE.md — rules for natural language over bash-isms, dynamic branch detection, self-contained code blocks.
- Hardcoded-main regression test scans all `.tmpl` files for git commands with hardcoded `main`.
- QA template cleaned up: removed `REPORT_DIR` shell variable, simplified port detection to prose.
- gstack-upgrade template: explicit cross-step prose for variable references between bash blocks.
## 0.4.1 — 2026-03-16 ## 0.4.1 — 2026-03-16
- **gstack now notices when it screws up.** Turn on contributor mode (`gstack-config set gstack_contributor true`) and gstack automatically writes up what went wrong — what you were doing, what broke, repro steps. Next time something annoys you, the bug report is already written. Fork gstack and fix it yourself. - **gstack now notices when it screws up.** Turn on contributor mode (`gstack-config set gstack_contributor true`) and gstack automatically writes up what went wrong — what you were doing, what broke, repro steps. Next time something annoys you, the bug report is already written. Fork gstack and fix it yourself.

View File

@@ -65,6 +65,23 @@ SKILL.md files are **generated** from `.tmpl` templates. To update docs:
To add a new browse command: add it to `browse/src/commands.ts` and rebuild. To add a new browse command: add it to `browse/src/commands.ts` and rebuild.
To add a snapshot flag: add it to `SNAPSHOT_FLAGS` in `browse/src/snapshot.ts` and rebuild. To add a snapshot flag: add it to `SNAPSHOT_FLAGS` in `browse/src/snapshot.ts` and rebuild.
## Writing SKILL templates
SKILL.md.tmpl files are **prompt templates read by Claude**, not bash scripts.
Each bash code block runs in a separate shell — variables do not persist between blocks.
Rules:
- **Use natural language for logic and state.** Don't use shell variables to pass
state between code blocks. Instead, tell Claude what to remember and reference
it in prose (e.g., "the base branch detected in Step 0").
- **Don't hardcode branch names.** Detect `main`/`master`/etc dynamically via
`gh pr view` or `gh repo view`. Use `{{BASE_BRANCH_DETECT}}` for PR-targeting
skills. Use "the base branch" in prose, `<base>` in code block placeholders.
- **Keep bash blocks self-contained.** Each code block should work independently.
If a block needs context from a previous step, restate it in the prose above.
- **Express conditionals as English.** Instead of nested `if/elif/else` in bash,
write numbered decision steps: "1. If X, do Y. 2. Otherwise, do Z."
## Browser interaction ## Browser interaction
When you need to interact with a browser (QA, dogfooding, cookie setup), use the When you need to interact with a browser (QA, dogfooding, cookie setup), use the

View File

@@ -217,6 +217,8 @@ bun run skill:check
bun run dev:skill bun run dev:skill
``` ```
For template authoring best practices (natural language over bash-isms, dynamic branch detection, `{{BASE_BRANCH_DETECT}}` usage), see CLAUDE.md's "Writing SKILL templates" section.
To add a browse command, add it to `browse/src/commands.ts`. To add a snapshot flag, add it to `SNAPSHOT_FLAGS` in `browse/src/snapshot.ts`. Then rebuild. To add a browse command, add it to `browse/src/commands.ts`. To add a snapshot flag, add it to `SNAPSHOT_FLAGS` in `browse/src/snapshot.ts`. Then rebuild.
## Conductor workspaces ## Conductor workspaces

View File

@@ -1 +1 @@
0.4.1 0.4.2

View File

@@ -94,14 +94,20 @@ fi
echo "Install type: $INSTALL_TYPE at $INSTALL_DIR" echo "Install type: $INSTALL_TYPE at $INSTALL_DIR"
``` ```
The install type and directory path printed above will be used in all subsequent steps.
### Step 3: Save old version ### Step 3: Save old version
Use the install directory from Step 2's output below:
```bash ```bash
OLD_VERSION=$(cat "$INSTALL_DIR/VERSION" 2>/dev/null || echo "unknown") OLD_VERSION=$(cat "$INSTALL_DIR/VERSION" 2>/dev/null || echo "unknown")
``` ```
### Step 4: Upgrade ### Step 4: Upgrade
Use the install type and directory detected in Step 2:
**For git installs** (global-git, local-git): **For git installs** (global-git, local-git):
```bash ```bash
cd "$INSTALL_DIR" cd "$INSTALL_DIR"
@@ -125,7 +131,7 @@ rm -rf "$INSTALL_DIR.bak" "$TMP_DIR"
### Step 4.5: Sync local vendored copy ### Step 4.5: Sync local vendored copy
After upgrading the primary install, check if there's also a local copy in the current project that needs updating: Use the install directory from Step 2. Check if there's also a local vendored copy that needs updating:
```bash ```bash
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) _ROOT=$(git rev-parse --show-toplevel 2>/dev/null)

View File

@@ -92,14 +92,20 @@ fi
echo "Install type: $INSTALL_TYPE at $INSTALL_DIR" echo "Install type: $INSTALL_TYPE at $INSTALL_DIR"
``` ```
The install type and directory path printed above will be used in all subsequent steps.
### Step 3: Save old version ### Step 3: Save old version
Use the install directory from Step 2's output below:
```bash ```bash
OLD_VERSION=$(cat "$INSTALL_DIR/VERSION" 2>/dev/null || echo "unknown") OLD_VERSION=$(cat "$INSTALL_DIR/VERSION" 2>/dev/null || echo "unknown")
``` ```
### Step 4: Upgrade ### Step 4: Upgrade
Use the install type and directory detected in Step 2:
**For git installs** (global-git, local-git): **For git installs** (global-git, local-git):
```bash ```bash
cd "$INSTALL_DIR" cd "$INSTALL_DIR"
@@ -123,7 +129,7 @@ rm -rf "$INSTALL_DIR.bak" "$TMP_DIR"
### Step 4.5: Sync local vendored copy ### Step 4.5: Sync local vendored copy
After upgrading the primary install, check if there's also a local copy in the current project that needs updating: Use the install directory from Step 2. Check if there's also a local vendored copy that needs updating:
```bash ```bash
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) _ROOT=$(git rev-parse --show-toplevel 2>/dev/null)

View File

@@ -73,6 +73,25 @@ Then run: `mkdir -p ~/.gstack/contributor-logs && open ~/.gstack/contributor-log
Slug: lowercase, hyphens, max 60 chars (e.g. `browse-snapshot-ref-gap`). Skip if file already exists. Max 3 reports per session. File inline and continue — don't stop the workflow. Tell user: "Filed gstack field report: {title}" Slug: lowercase, hyphens, max 60 chars (e.g. `browse-snapshot-ref-gap`). Skip if file already exists. Max 3 reports per session. File inline and continue — don't stop the workflow. Tell user: "Filed gstack field report: {title}"
## Step 0: Detect base branch
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
1. Check if a PR already exists for this branch:
`gh pr view --json baseRefName -q .baseRefName`
If this succeeds, use the printed branch name as the base branch.
2. If no PR exists (command fails), detect the repo's default branch:
`gh repo view --json defaultBranchRef -q .defaultBranchRef.name`
3. If both commands fail, fall back to `main`.
Print the detected base branch name. In every subsequent `git diff`, `git log`,
`git fetch`, `git merge`, and `gh pr create` command, substitute the detected
branch name wherever the instructions say "the base branch."
---
# Mega Plan Review Mode # Mega Plan Review Mode
## Philosophy ## Philosophy
@@ -117,7 +136,7 @@ Before doing anything else, run a system audit. This is not the plan review —
Run the following commands: Run the following commands:
``` ```
git log --oneline -30 # Recent history git log --oneline -30 # Recent history
git diff main --stat # What's already changed git diff <base> --stat # What's already changed
git stash list # Any stashed work git stash list # Any stashed work
grep -r "TODO\|FIXME\|HACK\|XXX" --include="*.rb" --include="*.js" -l grep -r "TODO\|FIXME\|HACK\|XXX" --include="*.rb" --include="*.js" -l
find . -name "*.rb" -newer Gemfile.lock | head -20 # Recently touched files find . -name "*.rb" -newer Gemfile.lock | head -20 # Recently touched files

View File

@@ -16,6 +16,8 @@ allowed-tools:
{{PREAMBLE}} {{PREAMBLE}}
{{BASE_BRANCH_DETECT}}
# Mega Plan Review Mode # Mega Plan Review Mode
## Philosophy ## Philosophy
@@ -60,7 +62,7 @@ Before doing anything else, run a system audit. This is not the plan review —
Run the following commands: Run the following commands:
``` ```
git log --oneline -30 # Recent history git log --oneline -30 # Recent history
git diff main --stat # What's already changed git diff <base> --stat # What's already changed
git stash list # Any stashed work git stash list # Any stashed work
grep -r "TODO\|FIXME\|HACK\|XXX" --include="*.rb" --include="*.js" -l grep -r "TODO\|FIXME\|HACK\|XXX" --include="*.rb" --include="*.js" -l
find . -name "*.rb" -newer Gemfile.lock | head -20 # Recently touched files find . -name "*.rb" -newer Gemfile.lock | head -20 # Recently touched files

View File

@@ -77,6 +77,25 @@ Then run: `mkdir -p ~/.gstack/contributor-logs && open ~/.gstack/contributor-log
Slug: lowercase, hyphens, max 60 chars (e.g. `browse-snapshot-ref-gap`). Skip if file already exists. Max 3 reports per session. File inline and continue — don't stop the workflow. Tell user: "Filed gstack field report: {title}" Slug: lowercase, hyphens, max 60 chars (e.g. `browse-snapshot-ref-gap`). Skip if file already exists. Max 3 reports per session. File inline and continue — don't stop the workflow. Tell user: "Filed gstack field report: {title}"
## Step 0: Detect base branch
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
1. Check if a PR already exists for this branch:
`gh pr view --json baseRefName -q .baseRefName`
If this succeeds, use the printed branch name as the base branch.
2. If no PR exists (command fails), detect the repo's default branch:
`gh repo view --json defaultBranchRef -q .defaultBranchRef.name`
3. If both commands fail, fall back to `main`.
Print the detected base branch name. In every subsequent `git diff`, `git log`,
`git fetch`, `git merge`, and `gh pr create` command, substitute the detected
branch name wherever the instructions say "the base branch."
---
# /qa: Test → Fix → Verify # /qa: Test → Fix → Verify
You are a QA engineer AND a bug-fix engineer. Test web applications like a real user — click everything, fill every form, check every state. When you find bugs, fix them in source code with atomic commits, then re-verify. Produce a structured report with before/after evidence. You are a QA engineer AND a bug-fix engineer. Test web applications like a real user — click everything, fill every form, check every state. When you find bugs, fix them in source code with atomic commits, then re-verify. Produce a structured report with before/after evidence.
@@ -133,8 +152,7 @@ If `NEEDS_SETUP`:
**Create output directories:** **Create output directories:**
```bash ```bash
REPORT_DIR=".gstack/qa-reports" mkdir -p .gstack/qa-reports/screenshots
mkdir -p "$REPORT_DIR/screenshots"
``` ```
--- ---

View File

@@ -20,6 +20,8 @@ allowed-tools:
{{PREAMBLE}} {{PREAMBLE}}
{{BASE_BRANCH_DETECT}}
# /qa: Test → Fix → Verify # /qa: Test → Fix → Verify
You are a QA engineer AND a bug-fix engineer. Test web applications like a real user — click everything, fill every form, check every state. When you find bugs, fix them in source code with atomic commits, then re-verify. Produce a structured report with before/after evidence. You are a QA engineer AND a bug-fix engineer. Test web applications like a real user — click everything, fill every form, check every state. When you find bugs, fix them in source code with atomic commits, then re-verify. Produce a structured report with before/after evidence.
@@ -59,8 +61,7 @@ fi
**Create output directories:** **Create output directories:**
```bash ```bash
REPORT_DIR=".gstack/qa-reports" mkdir -p .gstack/qa-reports/screenshots
mkdir -p "$REPORT_DIR/screenshots"
``` ```
--- ---

View File

@@ -72,6 +72,16 @@ Then run: `mkdir -p ~/.gstack/contributor-logs && open ~/.gstack/contributor-log
Slug: lowercase, hyphens, max 60 chars (e.g. `browse-snapshot-ref-gap`). Skip if file already exists. Max 3 reports per session. File inline and continue — don't stop the workflow. Tell user: "Filed gstack field report: {title}" Slug: lowercase, hyphens, max 60 chars (e.g. `browse-snapshot-ref-gap`). Skip if file already exists. Max 3 reports per session. File inline and continue — don't stop the workflow. Tell user: "Filed gstack field report: {title}"
## Detect default branch
Before gathering data, detect the repo's default branch name:
`gh repo view --json defaultBranchRef -q .defaultBranchRef.name`
If this fails, fall back to `main`. Use the detected name wherever the instructions
say `origin/<default>` below.
---
# /retro — Weekly Engineering Retrospective # /retro — Weekly Engineering Retrospective
Generates a comprehensive engineering retrospective analyzing commit history, work patterns, and code quality metrics. Team-aware: identifies the user running the command, then analyzes every contributor with per-person praise and growth opportunities. Designed for a senior IC/CTO-level builder using Claude Code as a force multiplier. Generates a comprehensive engineering retrospective analyzing commit history, work patterns, and code quality metrics. Team-aware: identifies the user running the command, then analyzes every contributor with per-person praise and growth opportunities. Designed for a senior IC/CTO-level builder using Claude Code as a force multiplier.
@@ -106,7 +116,7 @@ Usage: /retro [window]
First, fetch origin and identify the current user: First, fetch origin and identify the current user:
```bash ```bash
git fetch origin main --quiet git fetch origin <default> --quiet
# Identify who is running the retro # Identify who is running the retro
git config user.name git config user.name
git config user.email git config user.email
@@ -118,28 +128,28 @@ Run ALL of these git commands in parallel (they are independent):
```bash ```bash
# 1. All commits in window with timestamps, subject, hash, AUTHOR, files changed, insertions, deletions # 1. All commits in window with timestamps, subject, hash, AUTHOR, files changed, insertions, deletions
git log origin/main --since="<window>" --format="%H|%aN|%ae|%ai|%s" --shortstat git log origin/<default> --since="<window>" --format="%H|%aN|%ae|%ai|%s" --shortstat
# 2. Per-commit test vs total LOC breakdown with author # 2. Per-commit test vs total LOC breakdown with author
# Each commit block starts with COMMIT:<hash>|<author>, followed by numstat lines. # Each commit block starts with COMMIT:<hash>|<author>, followed by numstat lines.
# Separate test files (matching test/|spec/|__tests__/) from production files. # Separate test files (matching test/|spec/|__tests__/) from production files.
git log origin/main --since="<window>" --format="COMMIT:%H|%aN" --numstat git log origin/<default> --since="<window>" --format="COMMIT:%H|%aN" --numstat
# 3. Commit timestamps for session detection and hourly distribution (with author) # 3. Commit timestamps for session detection and hourly distribution (with author)
# Use TZ=America/Los_Angeles for Pacific time conversion # Use TZ=America/Los_Angeles for Pacific time conversion
TZ=America/Los_Angeles git log origin/main --since="<window>" --format="%at|%aN|%ai|%s" | sort -n TZ=America/Los_Angeles git log origin/<default> --since="<window>" --format="%at|%aN|%ai|%s" | sort -n
# 4. Files most frequently changed (hotspot analysis) # 4. Files most frequently changed (hotspot analysis)
git log origin/main --since="<window>" --format="" --name-only | grep -v '^$' | sort | uniq -c | sort -rn git log origin/<default> --since="<window>" --format="" --name-only | grep -v '^$' | sort | uniq -c | sort -rn
# 5. PR numbers from commit messages (extract #NNN patterns) # 5. PR numbers from commit messages (extract #NNN patterns)
git log origin/main --since="<window>" --format="%s" | grep -oE '#[0-9]+' | sed 's/^#//' | sort -n | uniq | sed 's/^/#/' git log origin/<default> --since="<window>" --format="%s" | grep -oE '#[0-9]+' | sed 's/^#//' | sort -n | uniq | sed 's/^/#/'
# 6. Per-author file hotspots (who touches what) # 6. Per-author file hotspots (who touches what)
git log origin/main --since="<window>" --format="AUTHOR:%aN" --name-only git log origin/<default> --since="<window>" --format="AUTHOR:%aN" --name-only
# 7. Per-author commit counts (quick summary) # 7. Per-author commit counts (quick summary)
git shortlog origin/main --since="<window>" -sn --no-merges git shortlog origin/<default> --since="<window>" -sn --no-merges
# 8. Greptile triage history (if available) # 8. Greptile triage history (if available)
cat ~/.gstack/greptile-history.md 2>/dev/null || true cat ~/.gstack/greptile-history.md 2>/dev/null || true
@@ -298,14 +308,14 @@ If the time window is 14 days or more, split into weekly buckets and show trends
### Step 11: Streak Tracking ### Step 11: Streak Tracking
Count consecutive days with at least 1 commit to origin/main, going back from today. Track both team streak and personal streak: Count consecutive days with at least 1 commit to origin/<default>, going back from today. Track both team streak and personal streak:
```bash ```bash
# Team streak: all unique commit dates (Pacific time) — no hard cutoff # Team streak: all unique commit dates (Pacific time) — no hard cutoff
TZ=America/Los_Angeles git log origin/main --format="%ad" --date=format:"%Y-%m-%d" | sort -u TZ=America/Los_Angeles git log origin/<default> --format="%ad" --date=format:"%Y-%m-%d" | sort -u
# Personal streak: only the current user's commits # Personal streak: only the current user's commits
TZ=America/Los_Angeles git log origin/main --author="<user_name>" --format="%ad" --date=format:"%Y-%m-%d" | sort -u TZ=America/Los_Angeles git log origin/<default> --author="<user_name>" --format="%ad" --date=format:"%Y-%m-%d" | sort -u
``` ```
Count backward from today — how many consecutive days have at least one commit? This queries the full history so streaks of any length are reported accurately. Display both: Count backward from today — how many consecutive days have at least one commit? This queries the full history so streaks of any length are reported accurately. Display both:
@@ -523,7 +533,7 @@ When the user runs `/retro compare` (or `/retro compare 14d`):
## Important Rules ## Important Rules
- ALL narrative output goes directly to the user in the conversation. The ONLY file written is the `.context/retros/` JSON snapshot. - ALL narrative output goes directly to the user in the conversation. The ONLY file written is the `.context/retros/` JSON snapshot.
- Use `origin/main` for all git queries (not local main which may be stale) - Use `origin/<default>` for all git queries (not local main which may be stale)
- Convert all timestamps to Pacific time for display (use `TZ=America/Los_Angeles`) - Convert all timestamps to Pacific time for display (use `TZ=America/Los_Angeles`)
- If the window has zero commits, say so and suggest a different window - If the window has zero commits, say so and suggest a different window
- Round LOC/hour to nearest 50 - Round LOC/hour to nearest 50

View File

@@ -15,6 +15,16 @@ allowed-tools:
{{PREAMBLE}} {{PREAMBLE}}
## Detect default branch
Before gathering data, detect the repo's default branch name:
`gh repo view --json defaultBranchRef -q .defaultBranchRef.name`
If this fails, fall back to `main`. Use the detected name wherever the instructions
say `origin/<default>` below.
---
# /retro — Weekly Engineering Retrospective # /retro — Weekly Engineering Retrospective
Generates a comprehensive engineering retrospective analyzing commit history, work patterns, and code quality metrics. Team-aware: identifies the user running the command, then analyzes every contributor with per-person praise and growth opportunities. Designed for a senior IC/CTO-level builder using Claude Code as a force multiplier. Generates a comprehensive engineering retrospective analyzing commit history, work patterns, and code quality metrics. Team-aware: identifies the user running the command, then analyzes every contributor with per-person praise and growth opportunities. Designed for a senior IC/CTO-level builder using Claude Code as a force multiplier.
@@ -49,7 +59,7 @@ Usage: /retro [window]
First, fetch origin and identify the current user: First, fetch origin and identify the current user:
```bash ```bash
git fetch origin main --quiet git fetch origin <default> --quiet
# Identify who is running the retro # Identify who is running the retro
git config user.name git config user.name
git config user.email git config user.email
@@ -61,28 +71,28 @@ Run ALL of these git commands in parallel (they are independent):
```bash ```bash
# 1. All commits in window with timestamps, subject, hash, AUTHOR, files changed, insertions, deletions # 1. All commits in window with timestamps, subject, hash, AUTHOR, files changed, insertions, deletions
git log origin/main --since="<window>" --format="%H|%aN|%ae|%ai|%s" --shortstat git log origin/<default> --since="<window>" --format="%H|%aN|%ae|%ai|%s" --shortstat
# 2. Per-commit test vs total LOC breakdown with author # 2. Per-commit test vs total LOC breakdown with author
# Each commit block starts with COMMIT:<hash>|<author>, followed by numstat lines. # Each commit block starts with COMMIT:<hash>|<author>, followed by numstat lines.
# Separate test files (matching test/|spec/|__tests__/) from production files. # Separate test files (matching test/|spec/|__tests__/) from production files.
git log origin/main --since="<window>" --format="COMMIT:%H|%aN" --numstat git log origin/<default> --since="<window>" --format="COMMIT:%H|%aN" --numstat
# 3. Commit timestamps for session detection and hourly distribution (with author) # 3. Commit timestamps for session detection and hourly distribution (with author)
# Use TZ=America/Los_Angeles for Pacific time conversion # Use TZ=America/Los_Angeles for Pacific time conversion
TZ=America/Los_Angeles git log origin/main --since="<window>" --format="%at|%aN|%ai|%s" | sort -n TZ=America/Los_Angeles git log origin/<default> --since="<window>" --format="%at|%aN|%ai|%s" | sort -n
# 4. Files most frequently changed (hotspot analysis) # 4. Files most frequently changed (hotspot analysis)
git log origin/main --since="<window>" --format="" --name-only | grep -v '^$' | sort | uniq -c | sort -rn git log origin/<default> --since="<window>" --format="" --name-only | grep -v '^$' | sort | uniq -c | sort -rn
# 5. PR numbers from commit messages (extract #NNN patterns) # 5. PR numbers from commit messages (extract #NNN patterns)
git log origin/main --since="<window>" --format="%s" | grep -oE '#[0-9]+' | sed 's/^#//' | sort -n | uniq | sed 's/^/#/' git log origin/<default> --since="<window>" --format="%s" | grep -oE '#[0-9]+' | sed 's/^#//' | sort -n | uniq | sed 's/^/#/'
# 6. Per-author file hotspots (who touches what) # 6. Per-author file hotspots (who touches what)
git log origin/main --since="<window>" --format="AUTHOR:%aN" --name-only git log origin/<default> --since="<window>" --format="AUTHOR:%aN" --name-only
# 7. Per-author commit counts (quick summary) # 7. Per-author commit counts (quick summary)
git shortlog origin/main --since="<window>" -sn --no-merges git shortlog origin/<default> --since="<window>" -sn --no-merges
# 8. Greptile triage history (if available) # 8. Greptile triage history (if available)
cat ~/.gstack/greptile-history.md 2>/dev/null || true cat ~/.gstack/greptile-history.md 2>/dev/null || true
@@ -241,14 +251,14 @@ If the time window is 14 days or more, split into weekly buckets and show trends
### Step 11: Streak Tracking ### Step 11: Streak Tracking
Count consecutive days with at least 1 commit to origin/main, going back from today. Track both team streak and personal streak: Count consecutive days with at least 1 commit to origin/<default>, going back from today. Track both team streak and personal streak:
```bash ```bash
# Team streak: all unique commit dates (Pacific time) — no hard cutoff # Team streak: all unique commit dates (Pacific time) — no hard cutoff
TZ=America/Los_Angeles git log origin/main --format="%ad" --date=format:"%Y-%m-%d" | sort -u TZ=America/Los_Angeles git log origin/<default> --format="%ad" --date=format:"%Y-%m-%d" | sort -u
# Personal streak: only the current user's commits # Personal streak: only the current user's commits
TZ=America/Los_Angeles git log origin/main --author="<user_name>" --format="%ad" --date=format:"%Y-%m-%d" | sort -u TZ=America/Los_Angeles git log origin/<default> --author="<user_name>" --format="%ad" --date=format:"%Y-%m-%d" | sort -u
``` ```
Count backward from today — how many consecutive days have at least one commit? This queries the full history so streaks of any length are reported accurately. Display both: Count backward from today — how many consecutive days have at least one commit? This queries the full history so streaks of any length are reported accurately. Display both:
@@ -466,7 +476,7 @@ When the user runs `/retro compare` (or `/retro compare 14d`):
## Important Rules ## Important Rules
- ALL narrative output goes directly to the user in the conversation. The ONLY file written is the `.context/retros/` JSON snapshot. - ALL narrative output goes directly to the user in the conversation. The ONLY file written is the `.context/retros/` JSON snapshot.
- Use `origin/main` for all git queries (not local main which may be stale) - Use `origin/<default>` for all git queries (not local main which may be stale)
- Convert all timestamps to Pacific time for display (use `TZ=America/Los_Angeles`) - Convert all timestamps to Pacific time for display (use `TZ=America/Los_Angeles`)
- If the window has zero commits, say so and suggest a different window - If the window has zero commits, say so and suggest a different window
- Round LOC/hour to nearest 50 - Round LOC/hour to nearest 50

View File

@@ -2,7 +2,7 @@
name: review name: review
version: 1.0.0 version: 1.0.0
description: | description: |
Pre-landing PR review. Analyzes diff against main for SQL safety, LLM trust Pre-landing PR review. Analyzes diff against the base branch for SQL safety, LLM trust
boundary violations, conditional side effects, and other structural issues. boundary violations, conditional side effects, and other structural issues.
allowed-tools: allowed-tools:
- Bash - Bash
@@ -73,17 +73,36 @@ Then run: `mkdir -p ~/.gstack/contributor-logs && open ~/.gstack/contributor-log
Slug: lowercase, hyphens, max 60 chars (e.g. `browse-snapshot-ref-gap`). Skip if file already exists. Max 3 reports per session. File inline and continue — don't stop the workflow. Tell user: "Filed gstack field report: {title}" Slug: lowercase, hyphens, max 60 chars (e.g. `browse-snapshot-ref-gap`). Skip if file already exists. Max 3 reports per session. File inline and continue — don't stop the workflow. Tell user: "Filed gstack field report: {title}"
## Step 0: Detect base branch
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
1. Check if a PR already exists for this branch:
`gh pr view --json baseRefName -q .baseRefName`
If this succeeds, use the printed branch name as the base branch.
2. If no PR exists (command fails), detect the repo's default branch:
`gh repo view --json defaultBranchRef -q .defaultBranchRef.name`
3. If both commands fail, fall back to `main`.
Print the detected base branch name. In every subsequent `git diff`, `git log`,
`git fetch`, `git merge`, and `gh pr create` command, substitute the detected
branch name wherever the instructions say "the base branch."
---
# Pre-Landing PR Review # Pre-Landing PR Review
You are running the `/review` workflow. Analyze the current branch's diff against main for structural issues that tests don't catch. You are running the `/review` workflow. Analyze the current branch's diff against the base branch for structural issues that tests don't catch.
--- ---
## Step 1: Check branch ## Step 1: Check branch
1. Run `git branch --show-current` to get the current branch. 1. Run `git branch --show-current` to get the current branch.
2. If on `main`, output: **"Nothing to review — you're on main or have no changes against main."** and stop. 2. If on the base branch, output: **"Nothing to review — you're on the base branch or have no changes against it."** and stop.
3. Run `git fetch origin main --quiet && git diff origin/main --stat` to check if there's a diff. If no diff, output the same message and stop. 3. Run `git fetch origin <base> --quiet && git diff origin/<base> --stat` to check if there's a diff. If no diff, output the same message and stop.
--- ---
@@ -107,13 +126,13 @@ Read `.claude/skills/review/greptile-triage.md` and follow the fetch, filter, cl
## Step 3: Get the diff ## Step 3: Get the diff
Fetch the latest main to avoid false positives from a stale local main: Fetch the latest base branch to avoid false positives from stale local state:
```bash ```bash
git fetch origin main --quiet git fetch origin <base> --quiet
``` ```
Run `git diff origin/main` to get the full diff. This includes both committed and uncommitted changes against the latest main. Run `git diff origin/<base>` to get the full diff. This includes both committed and uncommitted changes against the latest base branch.
--- ---

View File

@@ -2,7 +2,7 @@
name: review name: review
version: 1.0.0 version: 1.0.0
description: | description: |
Pre-landing PR review. Analyzes diff against main for SQL safety, LLM trust Pre-landing PR review. Analyzes diff against the base branch for SQL safety, LLM trust
boundary violations, conditional side effects, and other structural issues. boundary violations, conditional side effects, and other structural issues.
allowed-tools: allowed-tools:
- Bash - Bash
@@ -16,17 +16,19 @@ allowed-tools:
{{PREAMBLE}} {{PREAMBLE}}
{{BASE_BRANCH_DETECT}}
# Pre-Landing PR Review # Pre-Landing PR Review
You are running the `/review` workflow. Analyze the current branch's diff against main for structural issues that tests don't catch. You are running the `/review` workflow. Analyze the current branch's diff against the base branch for structural issues that tests don't catch.
--- ---
## Step 1: Check branch ## Step 1: Check branch
1. Run `git branch --show-current` to get the current branch. 1. Run `git branch --show-current` to get the current branch.
2. If on `main`, output: **"Nothing to review — you're on main or have no changes against main."** and stop. 2. If on the base branch, output: **"Nothing to review — you're on the base branch or have no changes against it."** and stop.
3. Run `git fetch origin main --quiet && git diff origin/main --stat` to check if there's a diff. If no diff, output the same message and stop. 3. Run `git fetch origin <base> --quiet && git diff origin/<base> --stat` to check if there's a diff. If no diff, output the same message and stop.
--- ---
@@ -50,13 +52,13 @@ Read `.claude/skills/review/greptile-triage.md` and follow the fetch, filter, cl
## Step 3: Get the diff ## Step 3: Get the diff
Fetch the latest main to avoid false positives from a stale local main: Fetch the latest base branch to avoid false positives from stale local state:
```bash ```bash
git fetch origin main --quiet git fetch origin <base> --quiet
``` ```
Run `git diff origin/main` to get the full diff. This includes both committed and uncommitted changes against the latest main. Run `git diff origin/<base>` to get the full diff. This includes both committed and uncommitted changes against the latest base branch.
--- ---

View File

@@ -174,6 +174,27 @@ If \`NEEDS_SETUP\`:
3. If \`bun\` is not installed: \`curl -fsSL https://bun.sh/install | bash\``; 3. If \`bun\` is not installed: \`curl -fsSL https://bun.sh/install | bash\``;
} }
function generateBaseBranchDetect(): string {
return `## Step 0: Detect base branch
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
1. Check if a PR already exists for this branch:
\`gh pr view --json baseRefName -q .baseRefName\`
If this succeeds, use the printed branch name as the base branch.
2. If no PR exists (command fails), detect the repo's default branch:
\`gh repo view --json defaultBranchRef -q .defaultBranchRef.name\`
3. If both commands fail, fall back to \`main\`.
Print the detected base branch name. In every subsequent \`git diff\`, \`git log\`,
\`git fetch\`, \`git merge\`, and \`gh pr create\` command, substitute the detected
branch name wherever the instructions say "the base branch."
---`;
}
function generateQAMethodology(): string { function generateQAMethodology(): string {
return `## Modes return `## Modes
@@ -455,6 +476,7 @@ const RESOLVERS: Record<string, () => string> = {
SNAPSHOT_FLAGS: generateSnapshotFlags, SNAPSHOT_FLAGS: generateSnapshotFlags,
PREAMBLE: generatePreamble, PREAMBLE: generatePreamble,
BROWSE_SETUP: generateBrowseSetup, BROWSE_SETUP: generateBrowseSetup,
BASE_BRANCH_DETECT: generateBaseBranchDetect,
QA_METHODOLOGY: generateQAMethodology, QA_METHODOLOGY: generateQAMethodology,
}; };

View File

@@ -2,7 +2,7 @@
name: ship name: ship
version: 1.0.0 version: 1.0.0
description: | description: |
Ship workflow: merge main, run tests, review diff, bump VERSION, update CHANGELOG, commit, push, create PR. Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION, update CHANGELOG, commit, push, create PR.
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
@@ -72,12 +72,31 @@ Then run: `mkdir -p ~/.gstack/contributor-logs && open ~/.gstack/contributor-log
Slug: lowercase, hyphens, max 60 chars (e.g. `browse-snapshot-ref-gap`). Skip if file already exists. Max 3 reports per session. File inline and continue — don't stop the workflow. Tell user: "Filed gstack field report: {title}" Slug: lowercase, hyphens, max 60 chars (e.g. `browse-snapshot-ref-gap`). Skip if file already exists. Max 3 reports per session. File inline and continue — don't stop the workflow. Tell user: "Filed gstack field report: {title}"
## Step 0: Detect base branch
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
1. Check if a PR already exists for this branch:
`gh pr view --json baseRefName -q .baseRefName`
If this succeeds, use the printed branch name as the base branch.
2. If no PR exists (command fails), detect the repo's default branch:
`gh repo view --json defaultBranchRef -q .defaultBranchRef.name`
3. If both commands fail, fall back to `main`.
Print the detected base branch name. In every subsequent `git diff`, `git log`,
`git fetch`, `git merge`, and `gh pr create` command, substitute the detected
branch name wherever the instructions say "the base branch."
---
# Ship: Fully Automated Ship Workflow # Ship: Fully Automated Ship Workflow
You are running the `/ship` workflow. This is a **non-interactive, fully automated** workflow. Do NOT ask for confirmation at any step. The user said `/ship` which means DO IT. Run straight through and output the PR URL at the end. You are running the `/ship` workflow. This is a **non-interactive, fully automated** workflow. Do NOT ask for confirmation at any step. The user said `/ship` which means DO IT. Run straight through and output the PR URL at the end.
**Only stop for:** **Only stop for:**
- On `main` branch (abort) - On the base branch (abort)
- Merge conflicts that can't be auto-resolved (stop, show conflicts) - Merge conflicts that can't be auto-resolved (stop, show conflicts)
- Test failures (stop, show failures) - Test failures (stop, show failures)
- Pre-landing review finds CRITICAL issues and user chooses to fix (not acknowledge or skip) - Pre-landing review finds CRITICAL issues and user chooses to fix (not acknowledge or skip)
@@ -98,20 +117,20 @@ You are running the `/ship` workflow. This is a **non-interactive, fully automat
## Step 1: Pre-flight ## Step 1: Pre-flight
1. Check the current branch. If on `main`, **abort**: "You're on main. Ship from a feature branch." 1. Check the current branch. If on the base branch or the repo's default branch, **abort**: "You're on the base branch. Ship from a feature branch."
2. Run `git status` (never use `-uall`). Uncommitted changes are always included — no need to ask. 2. Run `git status` (never use `-uall`). Uncommitted changes are always included — no need to ask.
3. Run `git diff main...HEAD --stat` and `git log main..HEAD --oneline` to understand what's being shipped. 3. Run `git diff <base>...HEAD --stat` and `git log <base>..HEAD --oneline` to understand what's being shipped.
--- ---
## Step 2: Merge origin/main (BEFORE tests) ## Step 2: Merge the base branch (BEFORE tests)
Fetch and merge `origin/main` into the feature branch so tests run against the merged state: Fetch and merge the base branch into the feature branch so tests run against the merged state:
```bash ```bash
git fetch origin main && git merge origin/main --no-edit git fetch origin <base> && git merge origin/<base> --no-edit
``` ```
**If there are merge conflicts:** Try to auto-resolve if they are simple (VERSION, schema.rb, CHANGELOG ordering). If conflicts are complex or ambiguous, **STOP** and show them. **If there are merge conflicts:** Try to auto-resolve if they are simple (VERSION, schema.rb, CHANGELOG ordering). If conflicts are complex or ambiguous, **STOP** and show them.
@@ -149,7 +168,7 @@ Evals are mandatory when prompt-related files change. Skip this step entirely if
**1. Check if the diff touches prompt-related files:** **1. Check if the diff touches prompt-related files:**
```bash ```bash
git diff origin/main --name-only git diff origin/<base> --name-only
``` ```
Match against these patterns (from CLAUDE.md): Match against these patterns (from CLAUDE.md):
@@ -210,7 +229,7 @@ Review the diff for structural issues that tests don't catch.
1. Read `.claude/skills/review/checklist.md`. If the file cannot be read, **STOP** and report the error. 1. Read `.claude/skills/review/checklist.md`. If the file cannot be read, **STOP** and report the error.
2. Run `git diff origin/main` to get the full diff (scoped to feature changes against the freshly-fetched remote main). 2. Run `git diff origin/<base>` to get the full diff (scoped to feature changes against the freshly-fetched base branch).
3. Apply the review checklist in two passes: 3. Apply the review checklist in two passes:
- **Pass 1 (CRITICAL):** SQL & Data Safety, LLM Output Trust Boundary - **Pass 1 (CRITICAL):** SQL & Data Safety, LLM Output Trust Boundary
@@ -278,7 +297,7 @@ For each classified comment:
1. Read the current `VERSION` file (4-digit format: `MAJOR.MINOR.PATCH.MICRO`) 1. Read the current `VERSION` file (4-digit format: `MAJOR.MINOR.PATCH.MICRO`)
2. **Auto-decide the bump level based on the diff:** 2. **Auto-decide the bump level based on the diff:**
- Count lines changed (`git diff origin/main...HEAD --stat | tail -1`) - Count lines changed (`git diff origin/<base>...HEAD --stat | tail -1`)
- **MICRO** (4th digit): < 50 lines changed, trivial tweaks, typos, config - **MICRO** (4th digit): < 50 lines changed, trivial tweaks, typos, config
- **PATCH** (3rd digit): 50+ lines changed, bug fixes, small-medium features - **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 - **MINOR** (2nd digit): **ASK the user** — only for major features or significant architectural changes
@@ -297,8 +316,8 @@ For each classified comment:
1. Read `CHANGELOG.md` header to know the format. 1. Read `CHANGELOG.md` header to know the format.
2. Auto-generate the entry from **ALL commits on the branch** (not just recent ones): 2. Auto-generate the entry from **ALL commits on the branch** (not just recent ones):
- Use `git log main..HEAD --oneline` to see every commit being shipped - Use `git log <base>..HEAD --oneline` to see every commit being shipped
- Use `git diff main...HEAD` to see the full diff against main - Use `git diff <base>...HEAD` to see the full diff against the base branch
- The CHANGELOG entry must be comprehensive of ALL changes going into the PR - The CHANGELOG entry must be comprehensive of ALL changes going into the PR
- If existing CHANGELOG entries on the branch already cover some commits, replace them with one unified entry for the new version - If existing CHANGELOG entries on the branch already cover some commits, replace them with one unified entry for the new version
- Categorize changes into applicable sections: - Categorize changes into applicable sections:
@@ -346,8 +365,8 @@ Read TODOS.md and verify it follows the recommended structure:
This step is fully automatic — no user interaction. This step is fully automatic — no user interaction.
Use the diff and commit history already gathered in earlier steps: Use the diff and commit history already gathered in earlier steps:
- `git diff main...HEAD` (full diff against main) - `git diff <base>...HEAD` (full diff against the base branch)
- `git log main..HEAD --oneline` (all commits being shipped) - `git log <base>..HEAD --oneline` (all commits being shipped)
For each TODO item, check if the changes in this PR complete it by: For each TODO item, check if the changes in this PR complete it by:
- Matching commit messages against the TODO title and description - Matching commit messages against the TODO title and description
@@ -422,7 +441,7 @@ git push -u origin <branch-name>
Create a pull request using `gh`: Create a pull request using `gh`:
```bash ```bash
gh pr create --title "<type>: <summary>" --body "$(cat <<'EOF' gh pr create --base <base> --title "<type>: <summary>" --body "$(cat <<'EOF'
## Summary ## Summary
<bullet points from CHANGELOG> <bullet points from CHANGELOG>

View File

@@ -2,7 +2,7 @@
name: ship name: ship
version: 1.0.0 version: 1.0.0
description: | description: |
Ship workflow: merge main, run tests, review diff, bump VERSION, update CHANGELOG, commit, push, create PR. Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION, update CHANGELOG, commit, push, create PR.
allowed-tools: allowed-tools:
- Bash - Bash
- Read - Read
@@ -15,12 +15,14 @@ allowed-tools:
{{PREAMBLE}} {{PREAMBLE}}
{{BASE_BRANCH_DETECT}}
# Ship: Fully Automated Ship Workflow # Ship: Fully Automated Ship Workflow
You are running the `/ship` workflow. This is a **non-interactive, fully automated** workflow. Do NOT ask for confirmation at any step. The user said `/ship` which means DO IT. Run straight through and output the PR URL at the end. You are running the `/ship` workflow. This is a **non-interactive, fully automated** workflow. Do NOT ask for confirmation at any step. The user said `/ship` which means DO IT. Run straight through and output the PR URL at the end.
**Only stop for:** **Only stop for:**
- On `main` branch (abort) - On the base branch (abort)
- Merge conflicts that can't be auto-resolved (stop, show conflicts) - Merge conflicts that can't be auto-resolved (stop, show conflicts)
- Test failures (stop, show failures) - Test failures (stop, show failures)
- Pre-landing review finds CRITICAL issues and user chooses to fix (not acknowledge or skip) - Pre-landing review finds CRITICAL issues and user chooses to fix (not acknowledge or skip)
@@ -41,20 +43,20 @@ You are running the `/ship` workflow. This is a **non-interactive, fully automat
## Step 1: Pre-flight ## Step 1: Pre-flight
1. Check the current branch. If on `main`, **abort**: "You're on main. Ship from a feature branch." 1. Check the current branch. If on the base branch or the repo's default branch, **abort**: "You're on the base branch. Ship from a feature branch."
2. Run `git status` (never use `-uall`). Uncommitted changes are always included — no need to ask. 2. Run `git status` (never use `-uall`). Uncommitted changes are always included — no need to ask.
3. Run `git diff main...HEAD --stat` and `git log main..HEAD --oneline` to understand what's being shipped. 3. Run `git diff <base>...HEAD --stat` and `git log <base>..HEAD --oneline` to understand what's being shipped.
--- ---
## Step 2: Merge origin/main (BEFORE tests) ## Step 2: Merge the base branch (BEFORE tests)
Fetch and merge `origin/main` into the feature branch so tests run against the merged state: Fetch and merge the base branch into the feature branch so tests run against the merged state:
```bash ```bash
git fetch origin main && git merge origin/main --no-edit git fetch origin <base> && git merge origin/<base> --no-edit
``` ```
**If there are merge conflicts:** Try to auto-resolve if they are simple (VERSION, schema.rb, CHANGELOG ordering). If conflicts are complex or ambiguous, **STOP** and show them. **If there are merge conflicts:** Try to auto-resolve if they are simple (VERSION, schema.rb, CHANGELOG ordering). If conflicts are complex or ambiguous, **STOP** and show them.
@@ -92,7 +94,7 @@ Evals are mandatory when prompt-related files change. Skip this step entirely if
**1. Check if the diff touches prompt-related files:** **1. Check if the diff touches prompt-related files:**
```bash ```bash
git diff origin/main --name-only git diff origin/<base> --name-only
``` ```
Match against these patterns (from CLAUDE.md): Match against these patterns (from CLAUDE.md):
@@ -153,7 +155,7 @@ Review the diff for structural issues that tests don't catch.
1. Read `.claude/skills/review/checklist.md`. If the file cannot be read, **STOP** and report the error. 1. Read `.claude/skills/review/checklist.md`. If the file cannot be read, **STOP** and report the error.
2. Run `git diff origin/main` to get the full diff (scoped to feature changes against the freshly-fetched remote main). 2. Run `git diff origin/<base>` to get the full diff (scoped to feature changes against the freshly-fetched base branch).
3. Apply the review checklist in two passes: 3. Apply the review checklist in two passes:
- **Pass 1 (CRITICAL):** SQL & Data Safety, LLM Output Trust Boundary - **Pass 1 (CRITICAL):** SQL & Data Safety, LLM Output Trust Boundary
@@ -221,7 +223,7 @@ For each classified comment:
1. Read the current `VERSION` file (4-digit format: `MAJOR.MINOR.PATCH.MICRO`) 1. Read the current `VERSION` file (4-digit format: `MAJOR.MINOR.PATCH.MICRO`)
2. **Auto-decide the bump level based on the diff:** 2. **Auto-decide the bump level based on the diff:**
- Count lines changed (`git diff origin/main...HEAD --stat | tail -1`) - Count lines changed (`git diff origin/<base>...HEAD --stat | tail -1`)
- **MICRO** (4th digit): < 50 lines changed, trivial tweaks, typos, config - **MICRO** (4th digit): < 50 lines changed, trivial tweaks, typos, config
- **PATCH** (3rd digit): 50+ lines changed, bug fixes, small-medium features - **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 - **MINOR** (2nd digit): **ASK the user** — only for major features or significant architectural changes
@@ -240,8 +242,8 @@ For each classified comment:
1. Read `CHANGELOG.md` header to know the format. 1. Read `CHANGELOG.md` header to know the format.
2. Auto-generate the entry from **ALL commits on the branch** (not just recent ones): 2. Auto-generate the entry from **ALL commits on the branch** (not just recent ones):
- Use `git log main..HEAD --oneline` to see every commit being shipped - Use `git log <base>..HEAD --oneline` to see every commit being shipped
- Use `git diff main...HEAD` to see the full diff against main - Use `git diff <base>...HEAD` to see the full diff against the base branch
- The CHANGELOG entry must be comprehensive of ALL changes going into the PR - The CHANGELOG entry must be comprehensive of ALL changes going into the PR
- If existing CHANGELOG entries on the branch already cover some commits, replace them with one unified entry for the new version - If existing CHANGELOG entries on the branch already cover some commits, replace them with one unified entry for the new version
- Categorize changes into applicable sections: - Categorize changes into applicable sections:
@@ -289,8 +291,8 @@ Read TODOS.md and verify it follows the recommended structure:
This step is fully automatic — no user interaction. This step is fully automatic — no user interaction.
Use the diff and commit history already gathered in earlier steps: Use the diff and commit history already gathered in earlier steps:
- `git diff main...HEAD` (full diff against main) - `git diff <base>...HEAD` (full diff against the base branch)
- `git log main..HEAD --oneline` (all commits being shipped) - `git log <base>..HEAD --oneline` (all commits being shipped)
For each TODO item, check if the changes in this PR complete it by: For each TODO item, check if the changes in this PR complete it by:
- Matching commit messages against the TODO title and description - Matching commit messages against the TODO title and description
@@ -365,7 +367,7 @@ git push -u origin <branch-name>
Create a pull request using `gh`: Create a pull request using `gh`:
```bash ```bash
gh pr create --title "<type>: <summary>" --body "$(cat <<'EOF' gh pr create --base <base> --title "<type>: <summary>" --body "$(cat <<'EOF'
## Summary ## Summary
<bullet points from CHANGELOG> <bullet points from CHANGELOG>

View File

@@ -203,6 +203,27 @@ describe('gen-skill-docs', () => {
}); });
}); });
describe('BASE_BRANCH_DETECT resolver', () => {
// Find a generated SKILL.md that uses the placeholder (ship is guaranteed to)
const shipContent = fs.readFileSync(path.join(ROOT, 'ship', 'SKILL.md'), 'utf-8');
test('resolver output contains PR base detection command', () => {
expect(shipContent).toContain('gh pr view --json baseRefName');
});
test('resolver output contains repo default branch detection command', () => {
expect(shipContent).toContain('gh repo view --json defaultBranchRef');
});
test('resolver output contains fallback to main', () => {
expect(shipContent).toMatch(/fall\s*back\s+to\s+`main`/i);
});
test('resolver output uses "the base branch" phrasing', () => {
expect(shipContent).toContain('the base branch');
});
});
/** /**
* Quality evals — catch description regressions. * Quality evals — catch description regressions.
* *

View File

@@ -1344,6 +1344,193 @@ Write your review to ${planDir}/review-output.md`,
}, 420_000); }, 420_000);
}); });
// --- Base branch detection smoke tests ---
describeE2E('Base branch detection', () => {
let baseBranchDir: string;
const run = (cmd: string, args: string[], cwd: string) =>
spawnSync(cmd, args, { cwd, stdio: 'pipe', timeout: 5000 });
beforeAll(() => {
baseBranchDir = fs.mkdtempSync(path.join(os.tmpdir(), 'skill-e2e-basebranch-'));
});
afterAll(() => {
try { fs.rmSync(baseBranchDir, { recursive: true, force: true }); } catch {}
});
test('/review detects base branch and diffs against it', async () => {
const dir = path.join(baseBranchDir, 'review-base');
fs.mkdirSync(dir, { recursive: true });
// Create git repo with a feature branch off main
run('git', ['init'], dir);
run('git', ['config', 'user.email', 'test@test.com'], dir);
run('git', ['config', 'user.name', 'Test'], dir);
fs.writeFileSync(path.join(dir, 'app.rb'), '# clean base\nclass App\nend\n');
run('git', ['add', 'app.rb'], dir);
run('git', ['commit', '-m', 'initial commit'], dir);
// Create feature branch with a change
run('git', ['checkout', '-b', 'feature/test-review'], dir);
fs.writeFileSync(path.join(dir, 'app.rb'), '# clean base\nclass App\n def hello; "world"; end\nend\n');
run('git', ['add', 'app.rb'], dir);
run('git', ['commit', '-m', 'feat: add hello method'], dir);
// Copy review skill files
fs.copyFileSync(path.join(ROOT, 'review', 'SKILL.md'), path.join(dir, 'review-SKILL.md'));
fs.copyFileSync(path.join(ROOT, 'review', 'checklist.md'), path.join(dir, 'review-checklist.md'));
fs.copyFileSync(path.join(ROOT, 'review', 'greptile-triage.md'), path.join(dir, 'review-greptile-triage.md'));
const result = await runSkillTest({
prompt: `You are in a git repo on a feature branch with changes.
Read review-SKILL.md for the review workflow instructions.
Also read review-checklist.md and apply it.
IMPORTANT: Follow Step 0 to detect the base branch. Since there is no remote, gh commands will fail — fall back to main.
Then run the review against the detected base branch.
Write your findings to ${dir}/review-output.md`,
workingDirectory: dir,
maxTurns: 15,
timeout: 90_000,
testName: 'review-base-branch',
runId,
});
logCost('/review base-branch', result);
recordE2E('/review base branch detection', 'Base branch detection', result);
expect(result.exitReason).toBe('success');
// Verify the review used "base branch" language (from Step 0)
const toolOutputs = result.toolCalls.map(tc => tc.output || '').join('\n');
const allOutput = (result.output || '') + toolOutputs;
// The agent should have run git diff against main (the fallback)
const usedGitDiff = result.toolCalls.some(tc =>
tc.tool === 'Bash' && typeof tc.input === 'string' && tc.input.includes('git diff')
);
expect(usedGitDiff).toBe(true);
}, 120_000);
test('/ship Step 0-1 detects base branch without destructive actions', async () => {
const dir = path.join(baseBranchDir, 'ship-base');
fs.mkdirSync(dir, { recursive: true });
// Create git repo with feature branch
run('git', ['init'], dir);
run('git', ['config', 'user.email', 'test@test.com'], dir);
run('git', ['config', 'user.name', 'Test'], dir);
fs.writeFileSync(path.join(dir, 'app.ts'), 'console.log("v1");\n');
run('git', ['add', 'app.ts'], dir);
run('git', ['commit', '-m', 'initial'], dir);
run('git', ['checkout', '-b', 'feature/ship-test'], dir);
fs.writeFileSync(path.join(dir, 'app.ts'), 'console.log("v2");\n');
run('git', ['add', 'app.ts'], dir);
run('git', ['commit', '-m', 'feat: update to v2'], dir);
// Copy ship skill
fs.copyFileSync(path.join(ROOT, 'ship', 'SKILL.md'), path.join(dir, 'ship-SKILL.md'));
const result = await runSkillTest({
prompt: `Read ship-SKILL.md for the ship workflow.
Run ONLY Step 0 (Detect base branch) and Step 1 (Pre-flight) from the ship workflow.
Since there is no remote, gh commands will fail — fall back to main.
After completing Step 0 and Step 1, STOP. Do NOT proceed to Step 2 or beyond.
Do NOT push, create PRs, or modify VERSION/CHANGELOG.
Write a summary of what you detected to ${dir}/ship-preflight.md including:
- The detected base branch name
- The current branch name
- The diff stat against the base branch`,
workingDirectory: dir,
maxTurns: 10,
timeout: 60_000,
testName: 'ship-base-branch',
runId,
});
logCost('/ship base-branch', result);
recordE2E('/ship base branch detection', 'Base branch detection', result);
expect(result.exitReason).toBe('success');
// Verify preflight output was written
const preflightPath = path.join(dir, 'ship-preflight.md');
if (fs.existsSync(preflightPath)) {
const content = fs.readFileSync(preflightPath, 'utf-8');
expect(content.length).toBeGreaterThan(20);
// Should mention the branch name
expect(content.toLowerCase()).toMatch(/main|base/);
}
// Verify no destructive actions — no push, no PR creation
const destructiveTools = result.toolCalls.filter(tc =>
tc.tool === 'Bash' && typeof tc.input === 'string' &&
(tc.input.includes('git push') || tc.input.includes('gh pr create'))
);
expect(destructiveTools).toHaveLength(0);
}, 90_000);
test('/retro detects default branch for git queries', async () => {
const dir = path.join(baseBranchDir, 'retro-base');
fs.mkdirSync(dir, { recursive: true });
// Create git repo with commit history
run('git', ['init'], dir);
run('git', ['config', 'user.email', 'dev@example.com'], dir);
run('git', ['config', 'user.name', 'Dev'], dir);
fs.writeFileSync(path.join(dir, 'app.ts'), 'console.log("hello");\n');
run('git', ['add', 'app.ts'], dir);
run('git', ['commit', '-m', 'feat: initial app', '--date', '2026-03-14T09:00:00'], dir);
fs.writeFileSync(path.join(dir, 'auth.ts'), 'export function login() {}\n');
run('git', ['add', 'auth.ts'], dir);
run('git', ['commit', '-m', 'feat: add auth', '--date', '2026-03-15T10:00:00'], dir);
fs.writeFileSync(path.join(dir, 'test.ts'), 'test("it works", () => {});\n');
run('git', ['add', 'test.ts'], dir);
run('git', ['commit', '-m', 'test: add tests', '--date', '2026-03-16T11:00:00'], dir);
// Copy retro skill
fs.mkdirSync(path.join(dir, 'retro'), { recursive: true });
fs.copyFileSync(path.join(ROOT, 'retro', 'SKILL.md'), path.join(dir, 'retro', 'SKILL.md'));
const result = await runSkillTest({
prompt: `Read retro/SKILL.md for instructions on how to run a retrospective.
IMPORTANT: Follow the "Detect default branch" step first. Since there is no remote, gh will fail — fall back to main.
Then use the detected branch name for all git queries.
Run /retro for the last 7 days of this git repo. Skip any AskUserQuestion calls — this is non-interactive.
This is a local-only repo so use the local branch (main) instead of origin/main for all git log commands.
Write your retrospective to ${dir}/retro-output.md`,
workingDirectory: dir,
maxTurns: 25,
timeout: 240_000,
testName: 'retro-base-branch',
runId,
});
logCost('/retro base-branch', result);
recordE2E('/retro default branch detection', 'Base branch detection', result, {
passed: ['success', 'error_max_turns'].includes(result.exitReason),
});
expect(['success', 'error_max_turns']).toContain(result.exitReason);
// Verify retro output was produced
const retroPath = path.join(dir, 'retro-output.md');
if (fs.existsSync(retroPath)) {
const content = fs.readFileSync(retroPath, 'utf-8');
expect(content.length).toBeGreaterThan(100);
}
}, 300_000);
});
// --- Deferred skill E2E tests (destructive or require interactive UI) --- // --- Deferred skill E2E tests (destructive or require interactive UI) ---
describeE2E('Deferred skill E2E', () => { describeE2E('Deferred skill E2E', () => {

View File

@@ -388,6 +388,64 @@ describe('Greptile history format consistency', () => {
}); });
}); });
// --- Hardcoded branch name detection in templates ---
describe('No hardcoded branch names in SKILL templates', () => {
const tmplFiles = [
'ship/SKILL.md.tmpl',
'review/SKILL.md.tmpl',
'qa/SKILL.md.tmpl',
'plan-ceo-review/SKILL.md.tmpl',
'retro/SKILL.md.tmpl',
];
// Patterns that indicate hardcoded 'main' in git commands
const gitMainPatterns = [
/\bgit\s+diff\s+(?:origin\/)?main\b/,
/\bgit\s+log\s+(?:origin\/)?main\b/,
/\bgit\s+fetch\s+origin\s+main\b/,
/\bgit\s+merge\s+origin\/main\b/,
/\borigin\/main\b/,
];
// Lines that are allowed to mention 'main' (fallback logic, prose)
const allowlist = [
/fall\s*back\s+to\s+`main`/i,
/fall\s*back\s+to\s+`?main`?/i,
/typically\s+`?main`?/i,
/If\s+on\s+`main`/i, // old pattern — should not exist
];
for (const tmplFile of tmplFiles) {
test(`${tmplFile} has no hardcoded 'main' in git commands`, () => {
const filePath = path.join(ROOT, tmplFile);
if (!fs.existsSync(filePath)) return;
const lines = fs.readFileSync(filePath, 'utf-8').split('\n');
const violations: string[] = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const isAllowlisted = allowlist.some(p => p.test(line));
if (isAllowlisted) continue;
for (const pattern of gitMainPatterns) {
if (pattern.test(line)) {
violations.push(`Line ${i + 1}: ${line.trim()}`);
break;
}
}
}
if (violations.length > 0) {
throw new Error(
`${tmplFile} has hardcoded 'main' in git commands:\n` +
violations.map(v => ` ${v}`).join('\n')
);
}
});
}
});
// --- Part 7b: TODOS-format.md reference consistency --- // --- Part 7b: TODOS-format.md reference consistency ---
describe('TODOS-format.md reference consistency', () => { describe('TODOS-format.md reference consistency', () => {