This commit is contained in:
Nader Helmy 2026-07-16 01:26:43 +00:00 committed by GitHub
commit b02258864f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 232 additions and 77 deletions

View File

@ -61,7 +61,7 @@ Invoke them by name (e.g., `/office-hours`).
| `/learn` | Manage what gstack learned across sessions. |
| `/retro` | Weekly retro with per-person breakdowns and shipping streaks. |
| `/health` | Code quality dashboard (type checker, linter, tests, dead code). |
| `/benchmark` | Performance regression detection (page load, Core Web Vitals). |
| `/web-performance-benchmark` | Performance regression detection (page load, Core Web Vitals). |
| `/benchmark-models` | Cross-model benchmark for skills (Claude, GPT, Gemini side-by-side). |
| `/cso` | OWASP Top 10 + STRIDE security audit. |
| `/setup-gbrain` | Set up gbrain for cross-machine session memory sync. |

View File

@ -85,7 +85,7 @@ WebSocket — Claude's Bash tool already exists, so we use it.
Three escalating modes:
- **Headless** (default). Daemon runs Chromium with no visible window. Fastest,
cheapest, what skills like `/qa`, `/design-review`, `/benchmark` use by
cheapest, what skills like `/qa`, `/design-review`, `/web-performance-benchmark` use by
default.
- **Headed via `$B connect`**. Same daemon, but Chromium is visible (rebranded
as "GStack Browser") with the Side Panel extension auto-loaded. You watch

View File

@ -120,7 +120,7 @@ gstack/
├── plan-ceo-review/ # /plan-ceo-review skill
├── plan-eng-review/ # /plan-eng-review skill
├── autoplan/ # /autoplan skill (auto-review pipeline: CEO → design → eng)
├── benchmark/ # /benchmark skill (performance regression detection)
├── benchmark/ # /web-performance-benchmark skill (performance regression detection)
├── canary/ # /canary skill (post-deploy monitoring loop)
├── codex/ # /codex skill (multi-AI second opinion via OpenAI Codex CLI)
├── land-and-deploy/ # /land-and-deploy skill (merge → deploy → canary verify)

View File

@ -48,7 +48,7 @@ Fork it. Improve it. Make it yours. And if you want to hate on free open source
Open Claude Code and paste this. Claude does the rest.
> Install gstack: run **`git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack && cd ~/.claude/skills/gstack && ./setup`** then add a "gstack" section to CLAUDE.md that says to use the /browse skill from gstack for all web browsing, never use mcp\_\_claude-in-chrome\_\_\* tools, and lists the available skills: /office-hours, /plan-ceo-review, /plan-eng-review, /plan-design-review, /design-consultation, /design-shotgun, /design-html, /review, /ship, /land-and-deploy, /canary, /benchmark, /browse, /connect-chrome, /qa, /qa-only, /design-review, /setup-browser-cookies, /setup-deploy, /setup-gbrain, /retro, /investigate, /document-release, /document-generate, /codex, /cso, /autoplan, /plan-devex-review, /devex-review, /careful, /freeze, /guard, /unfreeze, /gstack-upgrade, /learn. Then ask the user if they also want to add gstack to the current project so teammates get it.
> Install gstack: run **`git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack && cd ~/.claude/skills/gstack && ./setup`** then add a "gstack" section to CLAUDE.md that says to use the /browse skill from gstack for all web browsing, never use mcp\_\_claude-in-chrome\_\_\* tools, and lists the available skills: /office-hours, /plan-ceo-review, /plan-eng-review, /plan-design-review, /design-consultation, /design-shotgun, /design-html, /review, /ship, /land-and-deploy, /canary, /web-performance-benchmark, /browse, /connect-chrome, /qa, /qa-only, /design-review, /setup-browser-cookies, /setup-deploy, /setup-gbrain, /retro, /investigate, /document-release, /document-generate, /codex, /cso, /autoplan, /plan-devex-review, /devex-review, /careful, /freeze, /guard, /unfreeze, /gstack-upgrade, /learn. Then ask the user if they also want to add gstack to the current project so teammates get it.
### Step 2: Team mode — auto-update for shared repos (recommended)
@ -197,7 +197,7 @@ Each skill feeds into the next. `/office-hours` writes a design doc that `/plan-
| `/ship` | **Release Engineer** | Sync main, run tests, audit coverage, push, open PR. Bootstraps test frameworks if you don't have one. |
| `/land-and-deploy` | **Release Engineer** | Merge the PR, wait for CI and deploy, verify production health. One command from "approved" to "verified in production." |
| `/canary` | **SRE** | Post-deploy monitoring loop. Watches for console errors, performance regressions, and page failures. |
| `/benchmark` | **Performance Engineer** | Baseline page load times, Core Web Vitals, and resource sizes. Compare before/after on every PR. |
| `/web-performance-benchmark` | **Performance Engineer** | Baseline page load times, Core Web Vitals, and resource sizes. Compare before/after on every PR. |
| `/document-release` | **Technical Writer** | Update all project docs to match what you just shipped. Catches stale READMEs automatically. Builds a Diataxis coverage map (reference / how-to / tutorial / explanation) so gaps are visible in the PR body. |
| `/document-generate` | **Documentation Author** | Generate missing docs from scratch using the Diataxis framework. Researches the codebase first, then writes reference / how-to / tutorial / explanation docs that actually match the code. Invokable standalone or chained from `/document-release` when the coverage map finds gaps. Learn more: [tutorial](docs/tutorial-document-generate.md) • [how-to](docs/howto-document-a-shipped-feature.md) • [why Diataxis](docs/explanation-diataxis-in-gstack.md). |
| `/retro` | **Eng Manager** | Team-aware weekly retro. Per-person breakdowns, shipping streaks, test health trends, growth opportunities. `/retro global` runs across all your projects and AI tools (Claude Code, Codex, Gemini). |
@ -480,7 +480,7 @@ On Windows without Developer Mode (MSYS2 / Git Bash), `setup` falls back to file
Use /browse from gstack for all web browsing. Never use mcp__claude-in-chrome__* tools.
Available skills: /office-hours, /plan-ceo-review, /plan-eng-review, /plan-design-review,
/design-consultation, /design-shotgun, /design-html, /review, /ship, /land-and-deploy,
/canary, /benchmark, /browse, /open-gstack-browser, /qa, /qa-only, /design-review,
/canary, /web-performance-benchmark, /browse, /open-gstack-browser, /qa, /qa-only, /design-review,
/setup-browser-cookies, /setup-deploy, /setup-gbrain, /sync-gbrain, /retro, /investigate,
/document-release, /document-generate, /codex, /cso, /autoplan, /pair-agent, /careful, /freeze,
/guard, /unfreeze, /gstack-upgrade, /learn.

View File

@ -587,7 +587,7 @@ quality gates that produce better results than answering inline.
- User asks to make a PDF, document, publication → invoke `/make-pdf`
- User asks to launch a real browser for QA, "open the browser" → invoke `/open-gstack-browser`
- User asks to import cookies for authenticated testing → invoke `/setup-browser-cookies`
- User asks about page speed, performance regression, benchmarks → invoke `/benchmark`
- User asks about page speed, Core Web Vitals, or a web performance regression → invoke `/web-performance-benchmark`
- User asks what gstack has learned, "show learnings" → invoke `/learn`
- User asks to tune question sensitivity, "stop asking me that" → invoke `/plan-tune`
- User asks for code quality dashboard, "health check" → invoke `/health`

View File

@ -76,7 +76,7 @@ quality gates that produce better results than answering inline.
- User asks to make a PDF, document, publication → invoke `/make-pdf`
- User asks to launch a real browser for QA, "open the browser" → invoke `/open-gstack-browser`
- User asks to import cookies for authenticated testing → invoke `/setup-browser-cookies`
- User asks about page speed, performance regression, benchmarks → invoke `/benchmark`
- User asks about page speed, Core Web Vitals, or a web performance regression → invoke `/web-performance-benchmark`
- User asks what gstack has learned, "show learnings" → invoke `/learn`
- User asks to tune question sensitivity, "stop asking me that" → invoke `/plan-tune`
- User asks for code quality dashboard, "health check" → invoke `/health`

View File

@ -2191,7 +2191,7 @@ Shipped in v0.6.5. TemplateContext in gen-skill-docs.ts bakes skill name into pr
### Deploy pipeline (v0.9.8.0)
- /land-and-deploy — merge PR, wait for CI/deploy, canary verification
- /canary — post-deploy monitoring loop with anomaly detection
- /benchmark — performance regression detection with Core Web Vitals
- /web-performance-benchmark — performance regression detection with Core Web Vitals
- /setup-deploy — one-time deploy platform configuration
- /review Performance & Bundle Impact pass
- E2E model pinning (Sonnet default, Opus for quality tests)

View File

@ -22,7 +22,7 @@ allowed-tools:
Runs the same prompt through Claude,
GPT (via Codex CLI), and Gemini side-by-side — compares latency, tokens, cost,
and optionally quality via LLM judge. Answers "which model is actually best
for this skill?" with data instead of vibes. Separate from /benchmark, which
for this skill?" with data instead of vibes. Separate from /web-performance-benchmark, which
measures web page performance. Use when: "benchmark models", "compare models",
"which model is best for X", "cross-model comparison", "model shootout".
@ -539,7 +539,7 @@ Skills that run plan reviews (`/plan-*-review`, `/codex review`) include the EXI
You are running the `/benchmark-models` workflow. Wraps the `gstack-model-benchmark` binary with an interactive flow that picks a prompt, confirms providers, previews auth, and runs the benchmark.
Different from `/benchmark` — that skill measures web page performance (Core Web Vitals, load times). This skill measures AI model performance on gstack skills or arbitrary prompts.
Different from `/web-performance-benchmark` — that skill measures web page performance (Core Web Vitals, load times). This skill measures AI model performance on gstack skills or arbitrary prompts.
---

View File

@ -6,7 +6,7 @@ description: |
Cross-model benchmark for gstack skills. Runs the same prompt through Claude,
GPT (via Codex CLI), and Gemini side-by-side — compares latency, tokens, cost,
and optionally quality via LLM judge. Answers "which model is actually best
for this skill?" with data instead of vibes. Separate from /benchmark, which
for this skill?" with data instead of vibes. Separate from /web-performance-benchmark, which
measures web page performance. Use when: "benchmark models", "compare models",
"which model is best for X", "cross-model comparison", "model shootout". (gstack)
voice-triggers:
@ -30,7 +30,7 @@ allowed-tools:
You are running the `/benchmark-models` workflow. Wraps the `gstack-model-benchmark` binary with an interactive flow that picks a prompt, confirms providers, previews auth, and runs the benchmark.
Different from `/benchmark` — that skill measures web page performance (Core Web Vitals, load times). This skill measures AI model performance on gstack skills or arbitrary prompts.
Different from `/web-performance-benchmark` — that skill measures web page performance (Core Web Vitals, load times). This skill measures AI model performance on gstack skills or arbitrary prompts.
---

View File

@ -1,12 +1,12 @@
---
name: benchmark
name: web-performance-benchmark
preamble-tier: 1
version: 1.0.0
description: Performance regression detection using the browse daemon. (gstack)
description: Web page performance regression detection using the browse daemon. (gstack)
triggers:
- performance benchmark
- web performance benchmark
- check page speed
- detect performance regression
- detect page performance regression
allowed-tools:
- Bash
- Read
@ -23,8 +23,9 @@ allowed-tools:
Establishes
baselines for page load times, Core Web Vitals, and resource sizes.
Compares before/after on every PR. Tracks performance trends over time.
Use when: "performance", "benchmark", "page speed", "lighthouse", "web vitals",
"bundle size", "load time".
Use when: "web performance benchmark", "page speed", "lighthouse",
"web vitals", "bundle size", "load time". Do not use for AI model, product,
business, eval, or research benchmark work.
Voice triggers (speech-to-text aliases): "speed test", "check performance".
@ -84,7 +85,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
echo "QUESTION_TUNING: $_QUESTION_TUNING"
mkdir -p ~/.gstack/analytics
if [ "$_TEL" != "off" ]; then
echo '{"skill":"benchmark","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
echo '{"skill":"web-performance-benchmark","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
fi
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
if [ -f "$_PF" ]; then
@ -106,7 +107,7 @@ if [ -f "$_LEARN_FILE" ]; then
else
echo "LEARNINGS: 0"
fi
~/.claude/skills/gstack/bin/gstack-timeline-log '{"skill":"benchmark","event":"started","branch":"'"$_BRANCH"'","session":"'"$_SESSION_ID"'"}' 2>/dev/null &
~/.claude/skills/gstack/bin/gstack-timeline-log '{"skill":"web-performance-benchmark","event":"started","branch":"'"$_BRANCH"'","session":"'"$_SESSION_ID"'"}' 2>/dev/null &
_HAS_ROUTING="no"
if [ -f CLAUDE.md ] && grep -q "## Skill routing" CLAUDE.md 2>/dev/null; then
_HAS_ROUTING="yes"
@ -571,22 +572,33 @@ If `NEEDS_SETUP`:
fi
```
# /benchmark — Performance Regression Detection
# /web-performance-benchmark - Web Page Performance Regression Detection
You are a **Performance Engineer** who has optimized apps serving millions of requests. You know that performance doesn't degrade in one big regression — it dies by a thousand paper cuts. Each PR adds 50ms here, 20KB there, and one day the app takes 8 seconds to load and nobody knows when it got slow.
Your job is to measure, baseline, compare, and alert. You use the browse daemon's `perf` command and JavaScript evaluation to gather real performance data from running pages.
## Scope
Use this skill for web page speed, Core Web Vitals, resource sizes, and performance
regressions. Do not use it for model benchmarks, agent evals, business benchmark
research, product metric design, or atrib behavior-impact measurement. For
cross-model gstack skill comparisons, use `/benchmark-models`.
## User-invocable
When the user types `/benchmark`, run this skill.
Use `/web-performance-benchmark` for new work.
Compatibility aliases: `/benchmark` and `/gstack-benchmark` run this same workflow.
Keep them working for existing users, but use the canonical command in new docs,
examples, and routing rules.
## Arguments
- `/benchmark <url>` — full performance audit with baseline comparison
- `/benchmark <url> --baseline` — capture baseline (run before making changes)
- `/benchmark <url> --quick` — single-pass timing check (no baseline needed)
- `/benchmark <url> --pages /,/dashboard,/api/health` — specify pages
- `/benchmark --diff` — benchmark only pages affected by current branch
- `/benchmark --trend` — show performance trends from historical data
- `/web-performance-benchmark <url>` — full performance audit with baseline comparison
- `/web-performance-benchmark <url> --baseline` — capture baseline (run before making changes)
- `/web-performance-benchmark <url> --quick` — single-pass timing check (no baseline needed)
- `/web-performance-benchmark <url> --pages /,/dashboard,/api/health` — specify pages
- `/web-performance-benchmark --diff` — benchmark only pages affected by current branch
- `/web-performance-benchmark --trend` — show performance trends from historical data
## Instructions

View File

@ -1,20 +1,21 @@
---
name: benchmark
name: web-performance-benchmark
preamble-tier: 1
version: 1.0.0
description: |
Performance regression detection using the browse daemon. Establishes
Web page performance regression detection using the browse daemon. Establishes
baselines for page load times, Core Web Vitals, and resource sizes.
Compares before/after on every PR. Tracks performance trends over time.
Use when: "performance", "benchmark", "page speed", "lighthouse", "web vitals",
"bundle size", "load time". (gstack)
Use when: "web performance benchmark", "page speed", "lighthouse",
"web vitals", "bundle size", "load time". Do not use for AI model, product,
business, eval, or research benchmark work. (gstack)
voice-triggers:
- "speed test"
- "check performance"
triggers:
- performance benchmark
- web performance benchmark
- check page speed
- detect performance regression
- detect page performance regression
allowed-tools:
- Bash
- Read
@ -27,22 +28,33 @@ allowed-tools:
{{BROWSE_SETUP}}
# /benchmark — Performance Regression Detection
# /web-performance-benchmark - Web Page Performance Regression Detection
You are a **Performance Engineer** who has optimized apps serving millions of requests. You know that performance doesn't degrade in one big regression — it dies by a thousand paper cuts. Each PR adds 50ms here, 20KB there, and one day the app takes 8 seconds to load and nobody knows when it got slow.
Your job is to measure, baseline, compare, and alert. You use the browse daemon's `perf` command and JavaScript evaluation to gather real performance data from running pages.
## Scope
Use this skill for web page speed, Core Web Vitals, resource sizes, and performance
regressions. Do not use it for model benchmarks, agent evals, business benchmark
research, product metric design, or atrib behavior-impact measurement. For
cross-model gstack skill comparisons, use `/benchmark-models`.
## User-invocable
When the user types `/benchmark`, run this skill.
Use `/web-performance-benchmark` for new work.
Compatibility aliases: `/benchmark` and `/gstack-benchmark` run this same workflow.
Keep them working for existing users, but use the canonical command in new docs,
examples, and routing rules.
## Arguments
- `/benchmark <url>` — full performance audit with baseline comparison
- `/benchmark <url> --baseline` — capture baseline (run before making changes)
- `/benchmark <url> --quick` — single-pass timing check (no baseline needed)
- `/benchmark <url> --pages /,/dashboard,/api/health` — specify pages
- `/benchmark --diff` — benchmark only pages affected by current branch
- `/benchmark --trend` — show performance trends from historical data
- `/web-performance-benchmark <url>` — full performance audit with baseline comparison
- `/web-performance-benchmark <url> --baseline` — capture baseline (run before making changes)
- `/web-performance-benchmark <url> --quick` — single-pass timing check (no baseline needed)
- `/web-performance-benchmark <url> --pages /,/dashboard,/api/health` — specify pages
- `/web-performance-benchmark --diff` — benchmark only pages affected by current branch
- `/web-performance-benchmark --trend` — show performance trends from historical data
## Instructions

View File

@ -193,7 +193,7 @@ Every gstack skill becomes a browser capability.
| `/qa` | Test every page, find bugs, fix them, verify fixes |
| `/design-review` | Screenshot → analyze → fix CSS → screenshot again |
| `/investigate` | See the error in browser → trace to code → fix → verify |
| `/benchmark` | Measure page performance → detect regressions → alert |
| `/web-performance-benchmark` | Measure page performance → detect regressions → alert |
| `/canary` | Monitor deployed site → screenshot periodically → alert on changes |
| `/ship` | Run tests → review diff → create PR → verify deployment in browser |
| `/cso` | Audit page for XSS, open redirects, clickjacking in real browser |

View File

@ -22,7 +22,7 @@ Detailed guides for every gstack skill — philosophy, workflow, and examples.
| [`/ship`](#ship) | **Release Engineer** | Sync main, run tests, audit coverage, push, open PR. Bootstraps test frameworks if you don't have one. One command. |
| [`/land-and-deploy`](#land-and-deploy) | **Release Engineer** | Merge the PR, wait for CI and deploy, verify production health. One command from "approved" to "verified in production." |
| [`/canary`](#canary) | **SRE** | Post-deploy monitoring loop. Watches for console errors, performance regressions, and page failures using the browse daemon. |
| [`/benchmark`](#benchmark) | **Performance Engineer** | Baseline page load times, Core Web Vitals, and resource sizes. Compare before/after on every PR. Track trends over time. |
| [`/web-performance-benchmark`](#web-performance-benchmark) | **Performance Engineer** | Baseline page load times, Core Web Vitals, and resource sizes. Compare before/after on every PR. Track trends over time. |
| [`/cso`](#cso) | **Chief Security Officer** | OWASP Top 10 + STRIDE threat modeling security audit. Scans for injection, auth, crypto, and access control issues. |
| [`/document-release`](#document-release) | **Technical Writer** | Update all project docs to match what you just shipped. Catches stale READMEs automatically. |
| [`/document-generate`](#document-generate) | **Technical Writer** | Generate Diataxis docs (tutorial / how-to / reference / explanation) for a feature from code. |
@ -715,16 +715,16 @@ Claude: Monitoring 8 pages every 2 minutes...
---
## `/benchmark`
## `/web-performance-benchmark`
This is my **performance engineer mode**.
`/benchmark` establishes performance baselines for your pages: load time, Core Web Vitals (LCP, CLS, INP), resource counts, and total transfer size. Run it before and after a PR to catch regressions.
`/web-performance-benchmark` establishes performance baselines for your pages: load time, Core Web Vitals (LCP, CLS, INP), resource counts, and total transfer size. Run it before and after a PR to catch regressions.
It uses the browse daemon for real Chromium measurements, not synthetic estimates. Multiple runs averaged. Results persist so you can track trends across PRs.
```
You: /benchmark https://myapp.com
You: /web-performance-benchmark https://myapp.com
Claude: Benchmarking 5 pages (3 runs each)...

View File

@ -11,12 +11,10 @@ Conventions:
## Skills
- [/autoplan](autoplan/SKILL.md): Auto-review pipeline — reads the full CEO, design, eng, and DX review skills from disk and runs them sequentially with auto-decisions using 6 decision principles.
- [/benchmark](benchmark/SKILL.md): Performance regression detection using the browse daemon.
- [/benchmark-models](benchmark-models/SKILL.md): Cross-model benchmark for gstack skills.
- [/browse](browse/SKILL.md): Fast headless browser for QA testing and site dogfooding.
- [/canary](canary/SKILL.md): Post-deploy canary monitoring.
- [/careful](careful/SKILL.md): Safety guardrails for destructive commands.
- [/claude](claude/SKILL.md): Claude Code CLI wrapper for non-Claude hosts - three modes.
- [/codex](codex/SKILL.md): OpenAI Codex CLI wrapper — three modes.
- [/context-restore](context-restore/SKILL.md): Restore working context saved earlier by /context-save.
- [/context-save](context-save/SKILL.md): Save working context.
@ -30,7 +28,7 @@ Conventions:
- [/document-generate](document-generate/SKILL.md): Generate missing documentation from scratch for a feature, module, or entire project.
- [/document-release](document-release/SKILL.md): Post-ship documentation update.
- [/freeze](freeze/SKILL.md): Restrict file edits to a specific directory for the session.
- [/gstack](gstack/SKILL.md): Router for the gstack skill suite.
- [/gstack](SKILL.md): Router for the gstack skill suite.
- [/gstack-upgrade](gstack-upgrade/SKILL.md): Upgrade gstack to the latest version.
- [/guard](guard/SKILL.md): Full safety mode: destructive command warnings + directory-scoped edits.
- [/health](health/SKILL.md): Code quality dashboard.
@ -65,6 +63,7 @@ Conventions:
- [/spec](spec/SKILL.md): Turn vague intent into a precise, executable spec in five phases.
- [/sync-gbrain](sync-gbrain/SKILL.md): Keep gbrain current with this repo's code and refresh agent search guidance in CLAUDE.md.
- [/unfreeze](unfreeze/SKILL.md): Clear the freeze boundary set by /freeze, allowing edits to all directories again.
- [/web-performance-benchmark](benchmark/SKILL.md): Web page performance regression detection using the browse daemon.
## Browse Commands

View File

@ -1898,7 +1898,7 @@ If verdict is REVERTED: Tell the user "The merge was reverted. Your changes are
Then suggest relevant follow-ups:
- If a production URL was verified: "Want extended monitoring? Run `/canary <url>` to watch the site for the next 10 minutes."
- If performance data was collected: "Want a deeper performance analysis? Run `/benchmark <url>`."
- If performance data was collected: "Want a deeper performance analysis? Run `/web-performance-benchmark <url>`."
- "Need to update docs? Run `/document-release` to sync README, CHANGELOG, and other docs with what you just shipped."
---

View File

@ -988,7 +988,7 @@ If verdict is REVERTED: Tell the user "The merge was reverted. Your changes are
Then suggest relevant follow-ups:
- If a production URL was verified: "Want extended monitoring? Run `/canary <url>` to watch the site for the next 10 minutes."
- If performance data was collected: "Want a deeper performance analysis? Run `/benchmark <url>`."
- If performance data was collected: "Want a deeper performance analysis? Run `/web-performance-benchmark <url>`."
- "Need to update docs? Run `/document-release` to sync README, CHANGELOG, and other docs with what you just shipped."
---

View File

@ -992,7 +992,7 @@ Tell the user:
> You're all set! Here's what you can do with the connected Chrome:
>
> **Watch Claude work in real time:**
> - Run any gstack skill (`/qa`, `/design-review`, `/benchmark`) and watch
> - Run any gstack skill (`/qa`, `/design-review`, `/web-performance-benchmark`) and watch
> every action happen in the visible Chrome window + Side Panel feed
> - No cookie import needed — the Playwright browser shares its own session
>
@ -1010,7 +1010,7 @@ Tell the user:
> - `/qa` runs its full test suite in the visible browser — you see every page
> load, every click, every assertion
> - `/design-review` takes screenshots in the real browser — same pixels you see
> - `/benchmark` measures performance in the headed browser
> - `/web-performance-benchmark` measures performance in the headed browser
Then proceed with whatever the user asked to do. If they didn't specify a task,
ask what they'd like to test or browse.

View File

@ -185,7 +185,7 @@ Tell the user:
> You're all set! Here's what you can do with the connected Chrome:
>
> **Watch Claude work in real time:**
> - Run any gstack skill (`/qa`, `/design-review`, `/benchmark`) and watch
> - Run any gstack skill (`/qa`, `/design-review`, `/web-performance-benchmark`) and watch
> every action happen in the visible Chrome window + Side Panel feed
> - No cookie import needed — the Playwright browser shares its own session
>
@ -203,7 +203,7 @@ Tell the user:
> - `/qa` runs its full test suite in the visible browser — you see every page
> load, every click, every assertion
> - `/design-review` takes screenshots in the real browser — same pixels you see
> - `/benchmark` measures performance in the headed browser
> - `/web-performance-benchmark` measures performance in the headed browser
Then proceed with whatever the user asked to do. If they didn't specify a task,
ask what they'd like to test or browse.

View File

@ -28,7 +28,7 @@ When asked for coding work, pick the dispatch tier:
**HEAVY:** needs a specific gstack methodology
→ sessions_spawn(runtime: "acp", prompt: "Load gstack. Run /qa https://...")
Skills: /cso, /review, /qa, /ship, /investigate, /design-review, /benchmark, /gstack-upgrade
Skills: /cso, /review, /qa, /ship, /investigate, /design-review, /web-performance-benchmark, /gstack-upgrade
**FULL:** build a complete feature, multi-day scope, needs planning + review
→ sessions_spawn(runtime: "acp", prompt: "<gstack-full content>\n\n<task>")

View File

@ -28,6 +28,7 @@ const OUTPUT = path.join(ROOT, 'gstack', 'llms.txt');
interface SkillEntry {
name: string;
description: string;
href: string;
}
/**
@ -113,6 +114,11 @@ function oneLine(text: string): string {
return first.replace(/\s+/g, ' ').trim();
}
function skillDirForOutput(output: string): string {
const dir = path.dirname(output);
return dir === '.' ? '.' : dir;
}
interface GenerateOptions {
/** Override repo root (for tests). */
root?: string;
@ -135,6 +141,9 @@ export async function generateLlmsTxt(opts: GenerateOptions = {}): Promise<Gener
const templates = discoverTemplates(root);
const skills: SkillEntry[] = [];
for (const t of templates) {
if (skillDirForOutput(t.output) === 'claude') {
continue;
}
const filePath = path.join(root, t.tmpl);
const entry = parseSkillFrontmatter(filePath);
if (!entry) {
@ -144,7 +153,7 @@ export async function generateLlmsTxt(opts: GenerateOptions = {}): Promise<Gener
}
continue;
}
skills.push(entry);
skills.push({ ...entry, href: t.output });
}
skills.sort((a, b) => a.name.localeCompare(b.name));
@ -167,7 +176,7 @@ export async function generateLlmsTxt(opts: GenerateOptions = {}): Promise<Gener
lines.push('');
for (const skill of skills) {
const summary = oneLine(skill.description);
lines.push(`- [/${skill.name}](${skill.name}/SKILL.md): ${summary}`);
lines.push(`- [/${skill.name}](${skill.href}): ${summary}`);
}
lines.push('');

View File

@ -9,13 +9,13 @@
"voice_line": "Voice triggers (speech-to-text aliases): \"auto plan\", \"automatic review\"."
},
"benchmark": {
"lead": "Performance regression detection using the browse daemon.",
"routing": "Establishes\nbaselines for page load times, Core Web Vitals, and resource sizes.\nCompares before/after on every PR. Tracks performance trends over time.\nUse when: \"performance\", \"benchmark\", \"page speed\", \"lighthouse\", \"web vitals\",\n\"bundle size\", \"load time\".",
"lead": "Web page performance regression detection using the browse daemon.",
"routing": "Establishes\nbaselines for page load times, Core Web Vitals, and resource sizes.\nCompares before/after on every PR. Tracks performance trends over time.\nUse when: \"web performance benchmark\", \"page speed\", \"lighthouse\",\n\"web vitals\", \"bundle size\", \"load time\". Do not use for AI model, product,\nbusiness, eval, or research benchmark work.",
"voice_line": "Voice triggers (speech-to-text aliases): \"speed test\", \"check performance\"."
},
"benchmark-models": {
"lead": "Cross-model benchmark for gstack skills.",
"routing": "Runs the same prompt through Claude,\nGPT (via Codex CLI), and Gemini side-by-side — compares latency, tokens, cost,\nand optionally quality via LLM judge. Answers \"which model is actually best\nfor this skill?\" with data instead of vibes. Separate from /benchmark, which\nmeasures web page performance. Use when: \"benchmark models\", \"compare models\",\n\"which model is best for X\", \"cross-model comparison\", \"model shootout\".",
"routing": "Runs the same prompt through Claude,\nGPT (via Codex CLI), and Gemini side-by-side — compares latency, tokens, cost,\nand optionally quality via LLM judge. Answers \"which model is actually best\nfor this skill?\" with data instead of vibes. Separate from /web-performance-benchmark, which\nmeasures web page performance. Use when: \"benchmark models\", \"compare models\",\n\"which model is best for X\", \"cross-model comparison\", \"model shootout\".",
"voice_line": "Voice triggers (speech-to-text aliases): \"compare models\", \"model shootout\", \"which model is best\"."
},
"browse": {

8
setup
View File

@ -1008,6 +1008,14 @@ if [ "$INSTALL_CLAUDE" -eq 1 ]; then
if [ -L "$_OGB_LINK" ] || [ ! -e "$_OGB_LINK" ]; then
_link_or_copy "gstack/open-gstack-browser" "$_OGB_LINK"
fi
# Compatibility aliases for the former generic benchmark command. Keep
# both paths available while new docs use /web-performance-benchmark.
for _WEB_PERF_ALIAS in benchmark gstack-benchmark; do
_WEB_PERF_LINK="$INSTALL_SKILLS_DIR/$_WEB_PERF_ALIAS"
if [ -L "$_WEB_PERF_LINK" ] || [ ! -e "$_WEB_PERF_LINK" ]; then
_link_or_copy "$SOURCE_GSTACK_DIR/benchmark" "$_WEB_PERF_LINK"
fi
done
if [ "$LOCAL_INSTALL" -eq 1 ]; then
log "gstack ready (project-local)."
log " skills: $INSTALL_SKILLS_DIR"

View File

@ -467,6 +467,37 @@ describe('gen-skill-docs', () => {
}
});
test('benchmark skill is scoped to web performance', () => {
const generated = fs.readFileSync(path.join(ROOT, 'benchmark', 'SKILL.md'), 'utf-8');
const template = fs.readFileSync(path.join(ROOT, 'benchmark', 'SKILL.md.tmpl'), 'utf-8');
for (const content of [generated, template]) {
const frontmatter = content.match(/^---\n([\s\S]*?)\n---/)?.[1] ?? '';
expect(frontmatter).toContain('name: web-performance-benchmark');
expect(frontmatter).not.toContain('name: benchmark\n');
expect(frontmatter).toContain('web performance benchmark');
expect(content).toContain('Use `/web-performance-benchmark` for new work.');
expect(content).toContain('Compatibility aliases: `/benchmark` and `/gstack-benchmark` run this same workflow.');
expect(content).toContain('/web-performance-benchmark <url>');
}
expect(template).toContain('cross-model gstack skill comparisons, use `/benchmark-models`.');
});
test('setup retains benchmark command compatibility aliases', () => {
const setup = fs.readFileSync(path.join(ROOT, 'setup'), 'utf-8');
expect(setup).toContain('for _WEB_PERF_ALIAS in benchmark gstack-benchmark; do');
expect(setup).toContain('_link_or_copy "$SOURCE_GSTACK_DIR/benchmark" "$_WEB_PERF_LINK"');
});
test('active browser design docs route to the canonical command', () => {
const design = fs.readFileSync(path.join(ROOT, 'docs', 'designs', 'GSTACK_BROWSER_V0.md'), 'utf-8');
expect(design).toContain('| `/web-performance-benchmark` | Measure page performance');
expect(design).not.toContain('| `/benchmark` | Measure page performance');
});
test('qa and qa-only templates use QA_METHODOLOGY placeholder', () => {
const qaTmpl = fs.readFileSync(path.join(ROOT, 'qa', 'SKILL.md.tmpl'), 'utf-8');
expect(qaTmpl).toContain('{{QA_METHODOLOGY}}');
@ -1691,7 +1722,9 @@ describe('Codex generation (--host codex)', () => {
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules') continue;
if (entry.name === 'codex') continue; // /codex is excluded from Codex output
if (!fs.existsSync(path.join(ROOT, entry.name, 'SKILL.md.tmpl'))) continue;
const codexName = entry.name.startsWith('gstack-') ? entry.name : `gstack-${entry.name}`;
const template = fs.readFileSync(path.join(ROOT, entry.name, 'SKILL.md.tmpl'), 'utf-8');
const frontmatterName = template.match(/^name:\s*(.+)$/m)?.[1]?.trim() ?? entry.name;
const codexName = frontmatterName.startsWith('gstack-') ? frontmatterName : `gstack-${frontmatterName}`;
if (isSymlinkLoop(codexName)) continue;
skills.push({ dir: entry.name, codexName });
}
@ -2010,7 +2043,9 @@ describe('Factory generation (--host factory)', () => {
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules') continue;
if (entry.name === 'codex') continue;
if (!fs.existsSync(path.join(ROOT, entry.name, 'SKILL.md.tmpl'))) continue;
const factoryName = entry.name.startsWith('gstack-') ? entry.name : `gstack-${entry.name}`;
const template = fs.readFileSync(path.join(ROOT, entry.name, 'SKILL.md.tmpl'), 'utf-8');
const frontmatterName = template.match(/^name:\s*(.+)$/m)?.[1]?.trim() ?? entry.name;
const factoryName = frontmatterName.startsWith('gstack-') ? frontmatterName : `gstack-${frontmatterName}`;
if (isSymlinkLoop(factoryName)) continue;
skills.push({ dir: entry.name, factoryName });
}

View File

@ -367,6 +367,8 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
'journey-retro': ['*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
'journey-design-system': ['*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
'journey-visual-qa': ['*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
'journey-web-performance-benchmark': ['benchmark/**', 'scripts/gen-skill-docs.ts'],
'journey-web-performance-benchmark-legacy-alias': ['benchmark/**', 'scripts/gen-skill-docs.ts', 'setup'],
// Opus 4.7 behavior evals — keys match testName: values in the test file.
// Routing sub-tests use template literal `routing-${c.name}` testNames,
@ -735,6 +737,8 @@ export const E2E_TIERS: Record<string, 'gate' | 'periodic'> = {
'journey-retro': 'periodic',
'journey-design-system': 'periodic',
'journey-visual-qa': 'periodic',
'journey-web-performance-benchmark': 'periodic',
'journey-web-performance-benchmark-legacy-alias': 'periodic',
// Opus 4.7 overlay evals — periodic (non-deterministic LLM behavior + Opus cost)
'fanout-arm-overlay-on': 'periodic',

View File

@ -25,17 +25,29 @@ describe('gen-llms-txt — shape', () => {
expect(generated.content).toContain('auto-generated');
});
test('every skill .tmpl in the repo appears in the index', () => {
test('every generated Claude skill appears in the index', () => {
const templates = discoverTemplates(ROOT);
const claudeGeneratedTemplates = templates.filter((t) => path.dirname(t.output) !== 'claude');
// Filter to those that successfully parsed (have name + description).
expect(generated.skills.length).toBeGreaterThan(0);
expect(generated.skills.length).toBeLessThanOrEqual(templates.length);
expect(generated.skills.length).toBeLessThanOrEqual(claudeGeneratedTemplates.length);
expect(generated.content).not.toContain('[/claude]');
for (const skill of generated.skills) {
expect(generated.content).toMatch(new RegExp(`/${skill.name}\\b`));
}
});
test('every skill link points at an existing generated file', () => {
const skillsSection = generated.content.split('## Skills')[1].split('## Browse Commands')[0];
const links = [...skillsSection.matchAll(/- \[\/[^\]]+\]\(([^)]+)\):/g)].map((m) => m[1]);
expect(links.length).toBe(generated.skills.length);
for (const href of links) {
expect(fs.existsSync(path.join(ROOT, href)), href).toBe(true);
}
});
test('every browse command in COMMAND_DESCRIPTIONS appears in the index', () => {
expect(generated.browseCommands.length).toBeGreaterThan(0);
for (const cmd of generated.browseCommands) {

View File

@ -328,7 +328,7 @@ describeIfSelected('Benchmark skill E2E', ['benchmark-workflow'], () => {
testConcurrentIfSelected('benchmark-workflow', async () => {
const result = await runSkillTest({
prompt: `Read benchmark/SKILL.md for the /benchmark skill instructions.
prompt: `Read benchmark/SKILL.md for the /web-performance-benchmark skill instructions.
You are simulating a benchmark run. There is NO browse daemon available and NO production URL.
@ -353,8 +353,8 @@ Just create the files showing the correct schema and report format.`,
runId,
});
logCost('/benchmark', result);
recordE2E(evalCollector, '/benchmark workflow', 'Benchmark skill E2E', result);
logCost('/web-performance-benchmark', result);
recordE2E(evalCollector, '/web-performance-benchmark workflow', 'Web performance benchmark skill E2E', result);
expect(result.exitReason).toBe('success');
expect(fs.existsSync(path.join(benchDir, '.gstack', 'benchmark-reports'))).toBe(true);

View File

@ -65,20 +65,26 @@ if (evalsEnabled && process.env.EVALS_TIER) {
* install that point to different worktrees or dangling targets. */
function installSkills(tmpDir: string) {
const skillDirs = [
'', // root gstack SKILL.md
'qa', 'qa-only', 'ship', 'review', 'plan-ceo-review', 'plan-eng-review',
'plan-design-review', 'design-review', 'design-consultation', 'retro',
'document-release', 'investigate', 'office-hours', 'browse', 'setup-browser-cookies',
'gstack-upgrade', 'humanizer',
{ source: '', command: 'gstack' },
{ source: 'qa' }, { source: 'qa-only' }, { source: 'ship' }, { source: 'review' },
{ source: 'plan-ceo-review' }, { source: 'plan-eng-review' },
{ source: 'plan-design-review' }, { source: 'design-review' },
{ source: 'design-consultation' }, { source: 'retro' },
{ source: 'document-release' }, { source: 'investigate' }, { source: 'office-hours' },
{ source: 'browse' },
{ source: 'benchmark', command: 'web-performance-benchmark' },
{ source: 'benchmark', command: 'benchmark' },
{ source: 'benchmark', command: 'gstack-benchmark' },
{ source: 'setup-browser-cookies' }, { source: 'gstack-upgrade' }, { source: 'humanizer' },
];
const targetBase = path.join(tmpDir, '.claude', 'skills');
for (const skill of skillDirs) {
const srcPath = path.join(ROOT, skill, 'SKILL.md');
const srcPath = path.join(ROOT, skill.source, 'SKILL.md');
if (!fs.existsSync(srcPath)) continue;
const skillName = skill || 'gstack';
const skillName = skill.command || skill.source;
const destDir = path.join(targetBase, skillName);
fs.mkdirSync(destDir, { recursive: true });
fs.copyFileSync(srcPath, path.join(destDir, 'SKILL.md'));
@ -107,6 +113,8 @@ Key routing rules:
- Design system, brand invoke design-consultation
- Visual audit, design polish invoke design-review
- Architecture review invoke plan-eng-review
- Web page speed, Core Web Vitals, or performance regression invoke web-performance-benchmark
- /benchmark and /gstack-benchmark are compatibility aliases for web-performance-benchmark
`);
}
@ -181,6 +189,62 @@ describeE2E('Skill Routing E2E — Developer Journey', () => {
evalCollector?.finalize();
});
testIfSelected('journey-web-performance-benchmark', async () => {
const tmpDir = createRoutingWorkDir('web-performance-benchmark');
try {
const testName = 'journey-web-performance-benchmark';
const expectedSkill = 'web-performance-benchmark';
const result = await runSkillTest({
prompt: 'Our checkout page got slower after the latest release. Measure Core Web Vitals, compare the current page load against a baseline, and identify any web performance regression.',
workingDirectory: tmpDir,
maxTurns: 5,
allowedTools: ['Skill', 'Read', 'Bash', 'Glob', 'Grep'],
timeout: 60_000,
testName,
runId,
});
const skillCalls = result.toolCalls.filter(tc => tc.tool === 'Skill');
const actualSkill = skillCalls[0]?.input?.skill;
logCost(`journey: ${testName}`, result);
recordRouting(testName, result, expectedSkill, actualSkill);
expect(skillCalls.length, `Expected Skill tool to be called but got 0 calls. Claude may have answered directly without invoking a skill. Tool calls: ${result.toolCalls.map(tc => tc.tool).join(', ')}`).toBeGreaterThan(0);
expect(actualSkill).toBe(expectedSkill);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
}, 150_000);
testIfSelected('journey-web-performance-benchmark-legacy-alias', async () => {
const tmpDir = createRoutingWorkDir('web-performance-benchmark-legacy-alias');
try {
const testName = 'journey-web-performance-benchmark-legacy-alias';
const expectedSkill = 'web-performance-benchmark';
const result = await runSkillTest({
prompt: 'Run /benchmark for the checkout page. Compare the current Core Web Vitals against the baseline and identify any performance regression.',
workingDirectory: tmpDir,
maxTurns: 5,
allowedTools: ['Skill', 'Read', 'Bash', 'Glob', 'Grep'],
timeout: 60_000,
testName,
runId,
});
const skillCalls = result.toolCalls.filter(tc => tc.tool === 'Skill');
const actualSkill = skillCalls[0]?.input?.skill;
logCost(`journey: ${testName}`, result);
recordRouting(testName, result, expectedSkill, actualSkill);
expect(skillCalls.length, `Expected Skill tool to be called but got 0 calls. Claude may have answered directly without invoking a skill. Tool calls: ${result.toolCalls.map(tc => tc.tool).join(', ')}`).toBeGreaterThan(0);
expect(actualSkill).toBe(expectedSkill);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
}, 150_000);
testIfSelected('journey-ideation', async () => {
const tmpDir = createRoutingWorkDir('ideation');
try {