From 6148354529401a1c775ed1041bade41eac8fbe86 Mon Sep 17 00:00:00 2001 From: Nader Helmy Date: Wed, 15 Jul 2026 19:06:17 -0500 Subject: [PATCH] fix: name web performance benchmark --- AGENTS.md | 2 +- BROWSER.md | 2 +- CLAUDE.md | 2 +- README.md | 6 ++-- SKILL.md | 2 +- SKILL.md.tmpl | 2 +- TODOS.md | 2 +- benchmark-models/SKILL.md | 4 +-- benchmark-models/SKILL.md.tmpl | 4 +-- benchmark/SKILL.md | 36 ++++++++++++----------- benchmark/SKILL.md.tmpl | 32 ++++++++++++--------- docs/skills.md | 8 +++--- gstack/llms.txt | 2 +- land-and-deploy/SKILL.md | 2 +- land-and-deploy/SKILL.md.tmpl | 2 +- open-gstack-browser/SKILL.md | 4 +-- open-gstack-browser/SKILL.md.tmpl | 4 +-- openclaw/agents-gstack-section.md | 2 +- scripts/gen-llms-txt.ts | 14 ++------- scripts/proactive-suggestions.json | 2 +- setup | 8 ++++++ test/gen-skill-docs.test.ts | 29 +++++++++++++------ test/helpers/touchfiles.ts | 2 ++ test/llms-txt-shape.test.ts | 16 ++--------- test/skill-e2e-deploy.test.ts | 6 ++-- test/skill-routing-e2e.test.ts | 46 +++++++++++++++++++++++++----- 26 files changed, 140 insertions(+), 101 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4df7eec35..c791ad450 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. | diff --git a/BROWSER.md b/BROWSER.md index affa0447d..71a5d4ed9 100644 --- a/BROWSER.md +++ b/BROWSER.md @@ -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 diff --git a/CLAUDE.md b/CLAUDE.md index 984844902..174b1613d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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) diff --git a/README.md b/README.md index af534d2c5..1f835c0de 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/SKILL.md b/SKILL.md index aaa5612dd..7c38ec635 100644 --- a/SKILL.md +++ b/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 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` diff --git a/SKILL.md.tmpl b/SKILL.md.tmpl index 402bd0d7b..5c769489d 100644 --- a/SKILL.md.tmpl +++ b/SKILL.md.tmpl @@ -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` diff --git a/TODOS.md b/TODOS.md index 29721e6e5..857014957 100644 --- a/TODOS.md +++ b/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) - /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) diff --git a/benchmark-models/SKILL.md b/benchmark-models/SKILL.md index 6a9d62616..f3e838086 100644 --- a/benchmark-models/SKILL.md +++ b/benchmark-models/SKILL.md @@ -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. --- diff --git a/benchmark-models/SKILL.md.tmpl b/benchmark-models/SKILL.md.tmpl index 034cda182..5494021ac 100644 --- a/benchmark-models/SKILL.md.tmpl +++ b/benchmark-models/SKILL.md.tmpl @@ -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. --- diff --git a/benchmark/SKILL.md b/benchmark/SKILL.md index 21391c5d1..7fa275f7a 100644 --- a/benchmark/SKILL.md +++ b/benchmark/SKILL.md @@ -1,5 +1,5 @@ --- -name: gstack-benchmark +name: web-performance-benchmark preamble-tier: 1 version: 1.0.0 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" mkdir -p ~/.gstack/analytics 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 for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -107,7 +107,7 @@ if [ -f "$_LEARN_FILE" ]; then else echo "LEARNINGS: 0" 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" if [ -f CLAUDE.md ] && grep -q "## Skill routing" CLAUDE.md 2>/dev/null; then _HAS_ROUTING="yes" @@ -572,29 +572,33 @@ If `NEEDS_SETUP`: 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. 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 -about web page performance. Skip this skill 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`. +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 `/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 -- `/gstack-benchmark ` — full performance audit with baseline comparison -- `/gstack-benchmark --baseline` — capture baseline (run before making changes) -- `/gstack-benchmark --quick` — single-pass timing check (no baseline needed) -- `/gstack-benchmark --pages /,/dashboard,/api/health` — specify pages -- `/gstack-benchmark --diff` — benchmark only pages affected by current branch -- `/gstack-benchmark --trend` — show performance trends from historical data +- `/web-performance-benchmark ` — full performance audit with baseline comparison +- `/web-performance-benchmark --baseline` — capture baseline (run before making changes) +- `/web-performance-benchmark --quick` — single-pass timing check (no baseline needed) +- `/web-performance-benchmark --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 diff --git a/benchmark/SKILL.md.tmpl b/benchmark/SKILL.md.tmpl index 939c905bd..116d070b6 100644 --- a/benchmark/SKILL.md.tmpl +++ b/benchmark/SKILL.md.tmpl @@ -1,5 +1,5 @@ --- -name: gstack-benchmark +name: web-performance-benchmark preamble-tier: 1 version: 1.0.0 description: | @@ -28,29 +28,33 @@ allowed-tools: {{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. 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 -about web page performance. Skip this skill 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`. +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 `/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 -- `/gstack-benchmark ` — full performance audit with baseline comparison -- `/gstack-benchmark --baseline` — capture baseline (run before making changes) -- `/gstack-benchmark --quick` — single-pass timing check (no baseline needed) -- `/gstack-benchmark --pages /,/dashboard,/api/health` — specify pages -- `/gstack-benchmark --diff` — benchmark only pages affected by current branch -- `/gstack-benchmark --trend` — show performance trends from historical data +- `/web-performance-benchmark ` — full performance audit with baseline comparison +- `/web-performance-benchmark --baseline` — capture baseline (run before making changes) +- `/web-performance-benchmark --quick` — single-pass timing check (no baseline needed) +- `/web-performance-benchmark --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 diff --git a/docs/skills.md b/docs/skills.md index 8e8cb7adc..5957321cd 100644 --- a/docs/skills.md +++ b/docs/skills.md @@ -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)... diff --git a/gstack/llms.txt b/gstack/llms.txt index efe522f90..50f3c5814 100644 --- a/gstack/llms.txt +++ b/gstack/llms.txt @@ -11,7 +11,6 @@ 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. @@ -65,6 +64,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](web-performance-benchmark/SKILL.md): Web page performance regression detection using the browse daemon. ## Browse Commands diff --git a/land-and-deploy/SKILL.md b/land-and-deploy/SKILL.md index 54ebf52c0..c8d6aa3f0 100644 --- a/land-and-deploy/SKILL.md +++ b/land-and-deploy/SKILL.md @@ -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 ` to watch the site for the next 10 minutes." -- If performance data was collected: "Want a deeper performance analysis? Run `/benchmark `." +- If performance data was collected: "Want a deeper performance analysis? Run `/web-performance-benchmark `." - "Need to update docs? Run `/document-release` to sync README, CHANGELOG, and other docs with what you just shipped." --- diff --git a/land-and-deploy/SKILL.md.tmpl b/land-and-deploy/SKILL.md.tmpl index 98976ad02..ccf9cad1f 100644 --- a/land-and-deploy/SKILL.md.tmpl +++ b/land-and-deploy/SKILL.md.tmpl @@ -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 ` to watch the site for the next 10 minutes." -- If performance data was collected: "Want a deeper performance analysis? Run `/benchmark `." +- If performance data was collected: "Want a deeper performance analysis? Run `/web-performance-benchmark `." - "Need to update docs? Run `/document-release` to sync README, CHANGELOG, and other docs with what you just shipped." --- diff --git a/open-gstack-browser/SKILL.md b/open-gstack-browser/SKILL.md index 28fb1ddb2..627852470 100644 --- a/open-gstack-browser/SKILL.md +++ b/open-gstack-browser/SKILL.md @@ -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. diff --git a/open-gstack-browser/SKILL.md.tmpl b/open-gstack-browser/SKILL.md.tmpl index ef91a5278..e35abeece 100644 --- a/open-gstack-browser/SKILL.md.tmpl +++ b/open-gstack-browser/SKILL.md.tmpl @@ -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. diff --git a/openclaw/agents-gstack-section.md b/openclaw/agents-gstack-section.md index 15d9b214b..f1d4fe9f1 100644 --- a/openclaw/agents-gstack-section.md +++ b/openclaw/agents-gstack-section.md @@ -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: "\n\n") diff --git a/scripts/gen-llms-txt.ts b/scripts/gen-llms-txt.ts index 6ade865a3..02e55d12d 100644 --- a/scripts/gen-llms-txt.ts +++ b/scripts/gen-llms-txt.ts @@ -24,12 +24,10 @@ import { COMMAND_DESCRIPTIONS as BROWSE_COMMANDS } from '../browse/src/commands' const ROOT = path.resolve(import.meta.dir, '..'); const OUTPUT = path.join(ROOT, 'gstack', 'llms.txt'); -const CLAUDE_SKIPPED_SKILL_DIRS = new Set(['claude']); interface SkillEntry { name: string; description: string; - href: string; } /** @@ -115,11 +113,6 @@ 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; @@ -142,9 +135,6 @@ export async function generateLlmsTxt(opts: GenerateOptions = {}): Promise a.name.localeCompare(b.name)); @@ -177,7 +167,7 @@ export async function generateLlmsTxt(opts: GenerateOptions = {}): Promise { for (const content of [generated, template]) { const frontmatter = content.match(/^---\n([\s\S]*?)\n---/)?.[1] ?? ''; - expect(frontmatter).toContain('name: gstack-benchmark'); - expect(frontmatter).not.toContain('name: benchmark'); + expect(frontmatter).toContain('name: web-performance-benchmark'); + expect(frontmatter).not.toContain('name: benchmark\n'); expect(frontmatter).toContain('web performance benchmark'); - expect(frontmatter).not.toMatch(/["']benchmark["']/); - expect(content).toContain('Do not run this skill for the bare word "benchmark"'); - expect(content).toContain('For cross-model gstack skill comparisons, use `/benchmark-models`.'); - expect(content).toContain('/gstack-benchmark '); - expect(content).not.toContain('When the user types `/benchmark`, run this skill.'); + 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 '); } + + 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', () => { @@ -1708,7 +1715,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 }); } @@ -2027,7 +2036,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 }); } diff --git a/test/helpers/touchfiles.ts b/test/helpers/touchfiles.ts index 33ba9ad97..dce600e2d 100644 --- a/test/helpers/touchfiles.ts +++ b/test/helpers/touchfiles.ts @@ -367,6 +367,7 @@ export const E2E_TOUCHFILES: Record = { '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'], // 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 +736,7 @@ export const E2E_TIERS: Record = { 'journey-retro': 'periodic', 'journey-design-system': 'periodic', 'journey-visual-qa': 'periodic', + 'journey-web-performance-benchmark': 'periodic', // Opus 4.7 overlay evals — periodic (non-deterministic LLM behavior + Opus cost) 'fanout-arm-overlay-on': 'periodic', diff --git a/test/llms-txt-shape.test.ts b/test/llms-txt-shape.test.ts index 14de056c9..3cbebb426 100644 --- a/test/llms-txt-shape.test.ts +++ b/test/llms-txt-shape.test.ts @@ -25,29 +25,17 @@ describe('gen-llms-txt — shape', () => { 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 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(claudeGeneratedTemplates.length); - expect(generated.content).not.toContain('[/claude]'); + expect(generated.skills.length).toBeLessThanOrEqual(templates.length); 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) { diff --git a/test/skill-e2e-deploy.test.ts b/test/skill-e2e-deploy.test.ts index e2496e7f9..1439261c1 100644 --- a/test/skill-e2e-deploy.test.ts +++ b/test/skill-e2e-deploy.test.ts @@ -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); diff --git a/test/skill-routing-e2e.test.ts b/test/skill-routing-e2e.test.ts index 301563560..9d510087c 100644 --- a/test/skill-routing-e2e.test.ts +++ b/test/skill-routing-e2e.test.ts @@ -65,20 +65,23 @@ 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: '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 +110,7 @@ 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 `); } @@ -181,6 +185,34 @@ 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-ideation', async () => { const tmpDir = createRoutingWorkDir('ideation'); try {