mirror of https://github.com/garrytan/gstack.git
fix: name web performance benchmark
This commit is contained in:
parent
b76a2d9555
commit
6148354529
|
|
@ -61,7 +61,7 @@ Invoke them by name (e.g., `/office-hours`).
|
||||||
| `/learn` | Manage what gstack learned across sessions. |
|
| `/learn` | Manage what gstack learned across sessions. |
|
||||||
| `/retro` | Weekly retro with per-person breakdowns and shipping streaks. |
|
| `/retro` | Weekly retro with per-person breakdowns and shipping streaks. |
|
||||||
| `/health` | Code quality dashboard (type checker, linter, tests, dead code). |
|
| `/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). |
|
| `/benchmark-models` | Cross-model benchmark for skills (Claude, GPT, Gemini side-by-side). |
|
||||||
| `/cso` | OWASP Top 10 + STRIDE security audit. |
|
| `/cso` | OWASP Top 10 + STRIDE security audit. |
|
||||||
| `/setup-gbrain` | Set up gbrain for cross-machine session memory sync. |
|
| `/setup-gbrain` | Set up gbrain for cross-machine session memory sync. |
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ WebSocket — Claude's Bash tool already exists, so we use it.
|
||||||
Three escalating modes:
|
Three escalating modes:
|
||||||
|
|
||||||
- **Headless** (default). Daemon runs Chromium with no visible window. Fastest,
|
- **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.
|
default.
|
||||||
- **Headed via `$B connect`**. Same daemon, but Chromium is visible (rebranded
|
- **Headed via `$B connect`**. Same daemon, but Chromium is visible (rebranded
|
||||||
as "GStack Browser") with the Side Panel extension auto-loaded. You watch
|
as "GStack Browser") with the Side Panel extension auto-loaded. You watch
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ gstack/
|
||||||
├── plan-ceo-review/ # /plan-ceo-review skill
|
├── plan-ceo-review/ # /plan-ceo-review skill
|
||||||
├── plan-eng-review/ # /plan-eng-review skill
|
├── plan-eng-review/ # /plan-eng-review skill
|
||||||
├── autoplan/ # /autoplan skill (auto-review pipeline: CEO → design → eng)
|
├── 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)
|
├── canary/ # /canary skill (post-deploy monitoring loop)
|
||||||
├── codex/ # /codex skill (multi-AI second opinion via OpenAI Codex CLI)
|
├── codex/ # /codex skill (multi-AI second opinion via OpenAI Codex CLI)
|
||||||
├── land-and-deploy/ # /land-and-deploy skill (merge → deploy → canary verify)
|
├── land-and-deploy/ # /land-and-deploy skill (merge → deploy → canary verify)
|
||||||
|
|
|
||||||
|
|
@ -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.
|
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)
|
### 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. |
|
| `/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." |
|
| `/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. |
|
| `/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-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). |
|
| `/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). |
|
| `/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.
|
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,
|
Available skills: /office-hours, /plan-ceo-review, /plan-eng-review, /plan-design-review,
|
||||||
/design-consultation, /design-shotgun, /design-html, /review, /ship, /land-and-deploy,
|
/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,
|
/setup-browser-cookies, /setup-deploy, /setup-gbrain, /sync-gbrain, /retro, /investigate,
|
||||||
/document-release, /document-generate, /codex, /cso, /autoplan, /pair-agent, /careful, /freeze,
|
/document-release, /document-generate, /codex, /cso, /autoplan, /pair-agent, /careful, /freeze,
|
||||||
/guard, /unfreeze, /gstack-upgrade, /learn.
|
/guard, /unfreeze, /gstack-upgrade, /learn.
|
||||||
|
|
|
||||||
2
SKILL.md
2
SKILL.md
|
|
@ -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 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 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 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 what gstack has learned, "show learnings" → invoke `/learn`
|
||||||
- User asks to tune question sensitivity, "stop asking me that" → invoke `/plan-tune`
|
- User asks to tune question sensitivity, "stop asking me that" → invoke `/plan-tune`
|
||||||
- User asks for code quality dashboard, "health check" → invoke `/health`
|
- User asks for code quality dashboard, "health check" → invoke `/health`
|
||||||
|
|
|
||||||
|
|
@ -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 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 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 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 what gstack has learned, "show learnings" → invoke `/learn`
|
||||||
- User asks to tune question sensitivity, "stop asking me that" → invoke `/plan-tune`
|
- User asks to tune question sensitivity, "stop asking me that" → invoke `/plan-tune`
|
||||||
- User asks for code quality dashboard, "health check" → invoke `/health`
|
- User asks for code quality dashboard, "health check" → invoke `/health`
|
||||||
|
|
|
||||||
2
TODOS.md
2
TODOS.md
|
|
@ -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)
|
### Deploy pipeline (v0.9.8.0)
|
||||||
- /land-and-deploy — merge PR, wait for CI/deploy, canary verification
|
- /land-and-deploy — merge PR, wait for CI/deploy, canary verification
|
||||||
- /canary — post-deploy monitoring loop with anomaly detection
|
- /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
|
- /setup-deploy — one-time deploy platform configuration
|
||||||
- /review Performance & Bundle Impact pass
|
- /review Performance & Bundle Impact pass
|
||||||
- E2E model pinning (Sonnet default, Opus for quality tests)
|
- E2E model pinning (Sonnet default, Opus for quality tests)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ allowed-tools:
|
||||||
Runs the same prompt through Claude,
|
Runs the same prompt through Claude,
|
||||||
GPT (via Codex CLI), and Gemini side-by-side — compares latency, tokens, cost,
|
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
|
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",
|
measures web page performance. Use when: "benchmark models", "compare models",
|
||||||
"which model is best for X", "cross-model comparison", "model shootout".
|
"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.
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ description: |
|
||||||
Cross-model benchmark for gstack skills. Runs the same prompt through Claude,
|
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,
|
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
|
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",
|
measures web page performance. Use when: "benchmark models", "compare models",
|
||||||
"which model is best for X", "cross-model comparison", "model shootout". (gstack)
|
"which model is best for X", "cross-model comparison", "model shootout". (gstack)
|
||||||
voice-triggers:
|
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.
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
name: gstack-benchmark
|
name: web-performance-benchmark
|
||||||
preamble-tier: 1
|
preamble-tier: 1
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
description: Web page performance regression detection using the browse daemon. (gstack)
|
description: Web page performance regression detection using the browse daemon. (gstack)
|
||||||
|
|
@ -85,7 +85,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"gstack-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
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -107,7 +107,7 @@ if [ -f "$_LEARN_FILE" ]; then
|
||||||
else
|
else
|
||||||
echo "LEARNINGS: 0"
|
echo "LEARNINGS: 0"
|
||||||
fi
|
fi
|
||||||
~/.claude/skills/gstack/bin/gstack-timeline-log '{"skill":"gstack-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"
|
_HAS_ROUTING="no"
|
||||||
if [ -f CLAUDE.md ] && grep -q "## Skill routing" CLAUDE.md 2>/dev/null; then
|
if [ -f CLAUDE.md ] && grep -q "## Skill routing" CLAUDE.md 2>/dev/null; then
|
||||||
_HAS_ROUTING="yes"
|
_HAS_ROUTING="yes"
|
||||||
|
|
@ -572,29 +572,33 @@ If `NEEDS_SETUP`:
|
||||||
fi
|
fi
|
||||||
```
|
```
|
||||||
|
|
||||||
# /gstack-benchmark - Web Page 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.
|
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.
|
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.
|
||||||
|
|
||||||
## Not For
|
## Scope
|
||||||
|
|
||||||
Do not run this skill for the bare word "benchmark" unless the request is clearly
|
Use this skill for web page speed, Core Web Vitals, resource sizes, and performance
|
||||||
about web page performance. Skip this skill for model benchmarks, agent evals,
|
regressions. Do not use it for model benchmarks, agent evals, business benchmark
|
||||||
business benchmark research, product metric design, or atrib behavior-impact
|
research, product metric design, or atrib behavior-impact measurement. For
|
||||||
measurement. For cross-model gstack skill comparisons, use `/benchmark-models`.
|
cross-model gstack skill comparisons, use `/benchmark-models`.
|
||||||
|
|
||||||
## User-invocable
|
## User-invocable
|
||||||
When the user types `/gstack-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
|
## Arguments
|
||||||
- `/gstack-benchmark <url>` — full performance audit with baseline comparison
|
- `/web-performance-benchmark <url>` — full performance audit with baseline comparison
|
||||||
- `/gstack-benchmark <url> --baseline` — capture baseline (run before making changes)
|
- `/web-performance-benchmark <url> --baseline` — capture baseline (run before making changes)
|
||||||
- `/gstack-benchmark <url> --quick` — single-pass timing check (no baseline needed)
|
- `/web-performance-benchmark <url> --quick` — single-pass timing check (no baseline needed)
|
||||||
- `/gstack-benchmark <url> --pages /,/dashboard,/api/health` — specify pages
|
- `/web-performance-benchmark <url> --pages /,/dashboard,/api/health` — specify pages
|
||||||
- `/gstack-benchmark --diff` — benchmark only pages affected by current branch
|
- `/web-performance-benchmark --diff` — benchmark only pages affected by current branch
|
||||||
- `/gstack-benchmark --trend` — show performance trends from historical data
|
- `/web-performance-benchmark --trend` — show performance trends from historical data
|
||||||
|
|
||||||
## Instructions
|
## Instructions
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
name: gstack-benchmark
|
name: web-performance-benchmark
|
||||||
preamble-tier: 1
|
preamble-tier: 1
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
description: |
|
description: |
|
||||||
|
|
@ -28,29 +28,33 @@ allowed-tools:
|
||||||
|
|
||||||
{{BROWSE_SETUP}}
|
{{BROWSE_SETUP}}
|
||||||
|
|
||||||
# /gstack-benchmark - Web Page 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.
|
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.
|
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.
|
||||||
|
|
||||||
## Not For
|
## Scope
|
||||||
|
|
||||||
Do not run this skill for the bare word "benchmark" unless the request is clearly
|
Use this skill for web page speed, Core Web Vitals, resource sizes, and performance
|
||||||
about web page performance. Skip this skill for model benchmarks, agent evals,
|
regressions. Do not use it for model benchmarks, agent evals, business benchmark
|
||||||
business benchmark research, product metric design, or atrib behavior-impact
|
research, product metric design, or atrib behavior-impact measurement. For
|
||||||
measurement. For cross-model gstack skill comparisons, use `/benchmark-models`.
|
cross-model gstack skill comparisons, use `/benchmark-models`.
|
||||||
|
|
||||||
## User-invocable
|
## User-invocable
|
||||||
When the user types `/gstack-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
|
## Arguments
|
||||||
- `/gstack-benchmark <url>` — full performance audit with baseline comparison
|
- `/web-performance-benchmark <url>` — full performance audit with baseline comparison
|
||||||
- `/gstack-benchmark <url> --baseline` — capture baseline (run before making changes)
|
- `/web-performance-benchmark <url> --baseline` — capture baseline (run before making changes)
|
||||||
- `/gstack-benchmark <url> --quick` — single-pass timing check (no baseline needed)
|
- `/web-performance-benchmark <url> --quick` — single-pass timing check (no baseline needed)
|
||||||
- `/gstack-benchmark <url> --pages /,/dashboard,/api/health` — specify pages
|
- `/web-performance-benchmark <url> --pages /,/dashboard,/api/health` — specify pages
|
||||||
- `/gstack-benchmark --diff` — benchmark only pages affected by current branch
|
- `/web-performance-benchmark --diff` — benchmark only pages affected by current branch
|
||||||
- `/gstack-benchmark --trend` — show performance trends from historical data
|
- `/web-performance-benchmark --trend` — show performance trends from historical data
|
||||||
|
|
||||||
## Instructions
|
## Instructions
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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. |
|
| [`/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." |
|
| [`/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. |
|
| [`/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. |
|
| [`/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-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. |
|
| [`/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**.
|
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.
|
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)...
|
Claude: Benchmarking 5 pages (3 runs each)...
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ Conventions:
|
||||||
## Skills
|
## 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.
|
- [/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.
|
- [/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.
|
- [/browse](browse/SKILL.md): Fast headless browser for QA testing and site dogfooding.
|
||||||
- [/canary](canary/SKILL.md): Post-deploy canary monitoring.
|
- [/canary](canary/SKILL.md): Post-deploy canary monitoring.
|
||||||
|
|
@ -65,6 +64,7 @@ Conventions:
|
||||||
- [/spec](spec/SKILL.md): Turn vague intent into a precise, executable spec in five phases.
|
- [/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.
|
- [/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.
|
- [/unfreeze](unfreeze/SKILL.md): Clear the freeze boundary set by /freeze, allowing edits to all directories again.
|
||||||
|
- [/web-performance-benchmark](web-performance-benchmark/SKILL.md): Web page performance regression detection using the browse daemon.
|
||||||
|
|
||||||
## Browse Commands
|
## Browse Commands
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1898,7 +1898,7 @@ If verdict is REVERTED: Tell the user "The merge was reverted. Your changes are
|
||||||
|
|
||||||
Then suggest relevant follow-ups:
|
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 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."
|
- "Need to update docs? Run `/document-release` to sync README, CHANGELOG, and other docs with what you just shipped."
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -988,7 +988,7 @@ If verdict is REVERTED: Tell the user "The merge was reverted. Your changes are
|
||||||
|
|
||||||
Then suggest relevant follow-ups:
|
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 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."
|
- "Need to update docs? Run `/document-release` to sync README, CHANGELOG, and other docs with what you just shipped."
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -992,7 +992,7 @@ Tell the user:
|
||||||
> You're all set! Here's what you can do with the connected Chrome:
|
> You're all set! Here's what you can do with the connected Chrome:
|
||||||
>
|
>
|
||||||
> **Watch Claude work in real time:**
|
> **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
|
> every action happen in the visible Chrome window + Side Panel feed
|
||||||
> - No cookie import needed — the Playwright browser shares its own session
|
> - 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
|
> - `/qa` runs its full test suite in the visible browser — you see every page
|
||||||
> load, every click, every assertion
|
> load, every click, every assertion
|
||||||
> - `/design-review` takes screenshots in the real browser — same pixels you see
|
> - `/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,
|
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.
|
ask what they'd like to test or browse.
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ Tell the user:
|
||||||
> You're all set! Here's what you can do with the connected Chrome:
|
> You're all set! Here's what you can do with the connected Chrome:
|
||||||
>
|
>
|
||||||
> **Watch Claude work in real time:**
|
> **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
|
> every action happen in the visible Chrome window + Side Panel feed
|
||||||
> - No cookie import needed — the Playwright browser shares its own session
|
> - 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
|
> - `/qa` runs its full test suite in the visible browser — you see every page
|
||||||
> load, every click, every assertion
|
> load, every click, every assertion
|
||||||
> - `/design-review` takes screenshots in the real browser — same pixels you see
|
> - `/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,
|
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.
|
ask what they'd like to test or browse.
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ When asked for coding work, pick the dispatch tier:
|
||||||
|
|
||||||
**HEAVY:** needs a specific gstack methodology
|
**HEAVY:** needs a specific gstack methodology
|
||||||
→ sessions_spawn(runtime: "acp", prompt: "Load gstack. Run /qa https://...")
|
→ 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
|
**FULL:** build a complete feature, multi-day scope, needs planning + review
|
||||||
→ sessions_spawn(runtime: "acp", prompt: "<gstack-full content>\n\n<task>")
|
→ sessions_spawn(runtime: "acp", prompt: "<gstack-full content>\n\n<task>")
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,10 @@ import { COMMAND_DESCRIPTIONS as BROWSE_COMMANDS } from '../browse/src/commands'
|
||||||
|
|
||||||
const ROOT = path.resolve(import.meta.dir, '..');
|
const ROOT = path.resolve(import.meta.dir, '..');
|
||||||
const OUTPUT = path.join(ROOT, 'gstack', 'llms.txt');
|
const OUTPUT = path.join(ROOT, 'gstack', 'llms.txt');
|
||||||
const CLAUDE_SKIPPED_SKILL_DIRS = new Set(['claude']);
|
|
||||||
|
|
||||||
interface SkillEntry {
|
interface SkillEntry {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
href: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -115,11 +113,6 @@ function oneLine(text: string): string {
|
||||||
return first.replace(/\s+/g, ' ').trim();
|
return first.replace(/\s+/g, ' ').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function skillDirForOutput(output: string): string {
|
|
||||||
const dir = path.dirname(output);
|
|
||||||
return dir === '.' ? '.' : dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GenerateOptions {
|
interface GenerateOptions {
|
||||||
/** Override repo root (for tests). */
|
/** Override repo root (for tests). */
|
||||||
root?: string;
|
root?: string;
|
||||||
|
|
@ -142,9 +135,6 @@ export async function generateLlmsTxt(opts: GenerateOptions = {}): Promise<Gener
|
||||||
const templates = discoverTemplates(root);
|
const templates = discoverTemplates(root);
|
||||||
const skills: SkillEntry[] = [];
|
const skills: SkillEntry[] = [];
|
||||||
for (const t of templates) {
|
for (const t of templates) {
|
||||||
if (CLAUDE_SKIPPED_SKILL_DIRS.has(skillDirForOutput(t.output))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const filePath = path.join(root, t.tmpl);
|
const filePath = path.join(root, t.tmpl);
|
||||||
const entry = parseSkillFrontmatter(filePath);
|
const entry = parseSkillFrontmatter(filePath);
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
|
|
@ -154,7 +144,7 @@ export async function generateLlmsTxt(opts: GenerateOptions = {}): Promise<Gener
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
skills.push({ ...entry, href: t.output });
|
skills.push(entry);
|
||||||
}
|
}
|
||||||
skills.sort((a, b) => a.name.localeCompare(b.name));
|
skills.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
|
@ -177,7 +167,7 @@ export async function generateLlmsTxt(opts: GenerateOptions = {}): Promise<Gener
|
||||||
lines.push('');
|
lines.push('');
|
||||||
for (const skill of skills) {
|
for (const skill of skills) {
|
||||||
const summary = oneLine(skill.description);
|
const summary = oneLine(skill.description);
|
||||||
lines.push(`- [/${skill.name}](${skill.href}): ${summary}`);
|
lines.push(`- [/${skill.name}](${skill.name}/SKILL.md): ${summary}`);
|
||||||
}
|
}
|
||||||
lines.push('');
|
lines.push('');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
},
|
},
|
||||||
"benchmark-models": {
|
"benchmark-models": {
|
||||||
"lead": "Cross-model benchmark for gstack skills.",
|
"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\"."
|
"voice_line": "Voice triggers (speech-to-text aliases): \"compare models\", \"model shootout\", \"which model is best\"."
|
||||||
},
|
},
|
||||||
"browse": {
|
"browse": {
|
||||||
|
|
|
||||||
8
setup
8
setup
|
|
@ -1008,6 +1008,14 @@ if [ "$INSTALL_CLAUDE" -eq 1 ]; then
|
||||||
if [ -L "$_OGB_LINK" ] || [ ! -e "$_OGB_LINK" ]; then
|
if [ -L "$_OGB_LINK" ] || [ ! -e "$_OGB_LINK" ]; then
|
||||||
_link_or_copy "gstack/open-gstack-browser" "$_OGB_LINK"
|
_link_or_copy "gstack/open-gstack-browser" "$_OGB_LINK"
|
||||||
fi
|
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
|
if [ "$LOCAL_INSTALL" -eq 1 ]; then
|
||||||
log "gstack ready (project-local)."
|
log "gstack ready (project-local)."
|
||||||
log " skills: $INSTALL_SKILLS_DIR"
|
log " skills: $INSTALL_SKILLS_DIR"
|
||||||
|
|
|
||||||
|
|
@ -473,15 +473,22 @@ describe('gen-skill-docs', () => {
|
||||||
|
|
||||||
for (const content of [generated, template]) {
|
for (const content of [generated, template]) {
|
||||||
const frontmatter = content.match(/^---\n([\s\S]*?)\n---/)?.[1] ?? '';
|
const frontmatter = content.match(/^---\n([\s\S]*?)\n---/)?.[1] ?? '';
|
||||||
expect(frontmatter).toContain('name: gstack-benchmark');
|
expect(frontmatter).toContain('name: web-performance-benchmark');
|
||||||
expect(frontmatter).not.toContain('name: benchmark');
|
expect(frontmatter).not.toContain('name: benchmark\n');
|
||||||
expect(frontmatter).toContain('web performance benchmark');
|
expect(frontmatter).toContain('web performance benchmark');
|
||||||
expect(frontmatter).not.toMatch(/["']benchmark["']/);
|
expect(content).toContain('Use `/web-performance-benchmark` for new work.');
|
||||||
expect(content).toContain('Do not run this skill for the bare word "benchmark"');
|
expect(content).toContain('Compatibility aliases: `/benchmark` and `/gstack-benchmark` run this same workflow.');
|
||||||
expect(content).toContain('For cross-model gstack skill comparisons, use `/benchmark-models`.');
|
expect(content).toContain('/web-performance-benchmark <url>');
|
||||||
expect(content).toContain('/gstack-benchmark <url>');
|
|
||||||
expect(content).not.toContain('When the user types `/benchmark`, run this skill.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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('qa and qa-only templates use QA_METHODOLOGY placeholder', () => {
|
test('qa and qa-only templates use QA_METHODOLOGY placeholder', () => {
|
||||||
|
|
@ -1708,7 +1715,9 @@ describe('Codex generation (--host codex)', () => {
|
||||||
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
||||||
if (entry.name === 'codex') continue; // /codex is excluded from Codex output
|
if (entry.name === 'codex') continue; // /codex is excluded from Codex output
|
||||||
if (!fs.existsSync(path.join(ROOT, entry.name, 'SKILL.md.tmpl'))) continue;
|
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;
|
if (isSymlinkLoop(codexName)) continue;
|
||||||
skills.push({ dir: entry.name, codexName });
|
skills.push({ dir: entry.name, codexName });
|
||||||
}
|
}
|
||||||
|
|
@ -2027,7 +2036,9 @@ describe('Factory generation (--host factory)', () => {
|
||||||
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
||||||
if (entry.name === 'codex') continue;
|
if (entry.name === 'codex') continue;
|
||||||
if (!fs.existsSync(path.join(ROOT, entry.name, 'SKILL.md.tmpl'))) 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;
|
if (isSymlinkLoop(factoryName)) continue;
|
||||||
skills.push({ dir: entry.name, factoryName });
|
skills.push({ dir: entry.name, factoryName });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -367,6 +367,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||||
'journey-retro': ['*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
'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-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-visual-qa': ['*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts'],
|
||||||
|
'journey-web-performance-benchmark': ['benchmark/**', 'scripts/gen-skill-docs.ts'],
|
||||||
|
|
||||||
// Opus 4.7 behavior evals — keys match testName: values in the test file.
|
// Opus 4.7 behavior evals — keys match testName: values in the test file.
|
||||||
// Routing sub-tests use template literal `routing-${c.name}` testNames,
|
// Routing sub-tests use template literal `routing-${c.name}` testNames,
|
||||||
|
|
@ -735,6 +736,7 @@ export const E2E_TIERS: Record<string, 'gate' | 'periodic'> = {
|
||||||
'journey-retro': 'periodic',
|
'journey-retro': 'periodic',
|
||||||
'journey-design-system': 'periodic',
|
'journey-design-system': 'periodic',
|
||||||
'journey-visual-qa': 'periodic',
|
'journey-visual-qa': 'periodic',
|
||||||
|
'journey-web-performance-benchmark': 'periodic',
|
||||||
|
|
||||||
// Opus 4.7 overlay evals — periodic (non-deterministic LLM behavior + Opus cost)
|
// Opus 4.7 overlay evals — periodic (non-deterministic LLM behavior + Opus cost)
|
||||||
'fanout-arm-overlay-on': 'periodic',
|
'fanout-arm-overlay-on': 'periodic',
|
||||||
|
|
|
||||||
|
|
@ -25,29 +25,17 @@ describe('gen-llms-txt — shape', () => {
|
||||||
expect(generated.content).toContain('auto-generated');
|
expect(generated.content).toContain('auto-generated');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('every generated Claude skill appears in the index', () => {
|
test('every skill .tmpl in the repo appears in the index', () => {
|
||||||
const templates = discoverTemplates(ROOT);
|
const templates = discoverTemplates(ROOT);
|
||||||
const claudeGeneratedTemplates = templates.filter((t) => path.dirname(t.output) !== 'claude');
|
|
||||||
// Filter to those that successfully parsed (have name + description).
|
// Filter to those that successfully parsed (have name + description).
|
||||||
expect(generated.skills.length).toBeGreaterThan(0);
|
expect(generated.skills.length).toBeGreaterThan(0);
|
||||||
expect(generated.skills.length).toBeLessThanOrEqual(claudeGeneratedTemplates.length);
|
expect(generated.skills.length).toBeLessThanOrEqual(templates.length);
|
||||||
expect(generated.content).not.toContain('[/claude]');
|
|
||||||
|
|
||||||
for (const skill of generated.skills) {
|
for (const skill of generated.skills) {
|
||||||
expect(generated.content).toMatch(new RegExp(`/${skill.name}\\b`));
|
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', () => {
|
test('every browse command in COMMAND_DESCRIPTIONS appears in the index', () => {
|
||||||
expect(generated.browseCommands.length).toBeGreaterThan(0);
|
expect(generated.browseCommands.length).toBeGreaterThan(0);
|
||||||
for (const cmd of generated.browseCommands) {
|
for (const cmd of generated.browseCommands) {
|
||||||
|
|
|
||||||
|
|
@ -328,7 +328,7 @@ describeIfSelected('Benchmark skill E2E', ['benchmark-workflow'], () => {
|
||||||
|
|
||||||
testConcurrentIfSelected('benchmark-workflow', async () => {
|
testConcurrentIfSelected('benchmark-workflow', async () => {
|
||||||
const result = await runSkillTest({
|
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.
|
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,
|
runId,
|
||||||
});
|
});
|
||||||
|
|
||||||
logCost('/benchmark', result);
|
logCost('/web-performance-benchmark', result);
|
||||||
recordE2E(evalCollector, '/benchmark workflow', 'Benchmark skill E2E', result);
|
recordE2E(evalCollector, '/web-performance-benchmark workflow', 'Web performance benchmark skill E2E', result);
|
||||||
expect(result.exitReason).toBe('success');
|
expect(result.exitReason).toBe('success');
|
||||||
|
|
||||||
expect(fs.existsSync(path.join(benchDir, '.gstack', 'benchmark-reports'))).toBe(true);
|
expect(fs.existsSync(path.join(benchDir, '.gstack', 'benchmark-reports'))).toBe(true);
|
||||||
|
|
|
||||||
|
|
@ -65,20 +65,23 @@ if (evalsEnabled && process.env.EVALS_TIER) {
|
||||||
* install that point to different worktrees or dangling targets. */
|
* install that point to different worktrees or dangling targets. */
|
||||||
function installSkills(tmpDir: string) {
|
function installSkills(tmpDir: string) {
|
||||||
const skillDirs = [
|
const skillDirs = [
|
||||||
'', // root gstack SKILL.md
|
{ source: '', command: 'gstack' },
|
||||||
'qa', 'qa-only', 'ship', 'review', 'plan-ceo-review', 'plan-eng-review',
|
{ source: 'qa' }, { source: 'qa-only' }, { source: 'ship' }, { source: 'review' },
|
||||||
'plan-design-review', 'design-review', 'design-consultation', 'retro',
|
{ source: 'plan-ceo-review' }, { source: 'plan-eng-review' },
|
||||||
'document-release', 'investigate', 'office-hours', 'browse', 'setup-browser-cookies',
|
{ source: 'plan-design-review' }, { source: 'design-review' },
|
||||||
'gstack-upgrade', 'humanizer',
|
{ source: 'design-consultation' }, { source: 'retro' },
|
||||||
|
{ source: 'document-release' }, { source: 'investigate' }, { source: 'office-hours' },
|
||||||
|
{ source: 'browse' }, { source: 'benchmark', command: 'web-performance-benchmark' },
|
||||||
|
{ source: 'setup-browser-cookies' }, { source: 'gstack-upgrade' }, { source: 'humanizer' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const targetBase = path.join(tmpDir, '.claude', 'skills');
|
const targetBase = path.join(tmpDir, '.claude', 'skills');
|
||||||
|
|
||||||
for (const skill of skillDirs) {
|
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;
|
if (!fs.existsSync(srcPath)) continue;
|
||||||
|
|
||||||
const skillName = skill || 'gstack';
|
const skillName = skill.command || skill.source;
|
||||||
const destDir = path.join(targetBase, skillName);
|
const destDir = path.join(targetBase, skillName);
|
||||||
fs.mkdirSync(destDir, { recursive: true });
|
fs.mkdirSync(destDir, { recursive: true });
|
||||||
fs.copyFileSync(srcPath, path.join(destDir, 'SKILL.md'));
|
fs.copyFileSync(srcPath, path.join(destDir, 'SKILL.md'));
|
||||||
|
|
@ -107,6 +110,7 @@ Key routing rules:
|
||||||
- Design system, brand → invoke design-consultation
|
- Design system, brand → invoke design-consultation
|
||||||
- Visual audit, design polish → invoke design-review
|
- Visual audit, design polish → invoke design-review
|
||||||
- Architecture review → invoke plan-eng-review
|
- Architecture review → invoke plan-eng-review
|
||||||
|
- Web page speed, Core Web Vitals, or performance regression → invoke web-performance-benchmark
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,6 +185,34 @@ describeE2E('Skill Routing E2E — Developer Journey', () => {
|
||||||
evalCollector?.finalize();
|
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-ideation', async () => {
|
testIfSelected('journey-ideation', async () => {
|
||||||
const tmpDir = createRoutingWorkDir('ideation');
|
const tmpDir = createRoutingWorkDir('ideation');
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue