diff --git a/benchmark/SKILL.md b/benchmark/SKILL.md index 9451d2d4f..21391c5d1 100644 --- a/benchmark/SKILL.md +++ b/benchmark/SKILL.md @@ -1,12 +1,12 @@ --- -name: benchmark +name: gstack-benchmark preamble-tier: 1 version: 1.0.0 -description: Performance regression detection using the browse daemon. (gstack) +description: Web page performance regression detection using the browse daemon. (gstack) triggers: - - performance benchmark + - web performance benchmark - check page speed - - detect performance regression + - detect page performance regression allowed-tools: - Bash - Read @@ -23,8 +23,9 @@ allowed-tools: Establishes baselines for page load times, Core Web Vitals, and resource sizes. Compares before/after on every PR. Tracks performance trends over time. -Use when: "performance", "benchmark", "page speed", "lighthouse", "web vitals", -"bundle size", "load time". +Use when: "web performance benchmark", "page speed", "lighthouse", +"web vitals", "bundle size", "load time". Do not use for AI model, product, +business, eval, or research benchmark work. Voice triggers (speech-to-text aliases): "speed test", "check performance". @@ -84,7 +85,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning echo "QUESTION_TUNING: $_QUESTION_TUNING" mkdir -p ~/.gstack/analytics if [ "$_TEL" != "off" ]; then -echo '{"skill":"benchmark","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +echo '{"skill":"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 fi for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do if [ -f "$_PF" ]; then @@ -106,7 +107,7 @@ if [ -f "$_LEARN_FILE" ]; then else echo "LEARNINGS: 0" fi -~/.claude/skills/gstack/bin/gstack-timeline-log '{"skill":"benchmark","event":"started","branch":"'"$_BRANCH"'","session":"'"$_SESSION_ID"'"}' 2>/dev/null & +~/.claude/skills/gstack/bin/gstack-timeline-log '{"skill":"gstack-benchmark","event":"started","branch":"'"$_BRANCH"'","session":"'"$_SESSION_ID"'"}' 2>/dev/null & _HAS_ROUTING="no" if [ -f CLAUDE.md ] && grep -q "## Skill routing" CLAUDE.md 2>/dev/null; then _HAS_ROUTING="yes" @@ -571,22 +572,29 @@ If `NEEDS_SETUP`: fi ``` -# /benchmark — Performance Regression Detection +# /gstack-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 + +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`. + ## User-invocable -When the user types `/benchmark`, run this skill. +When the user types `/gstack-benchmark`, run this skill. ## Arguments -- `/benchmark ` — full performance audit with baseline comparison -- `/benchmark --baseline` — capture baseline (run before making changes) -- `/benchmark --quick` — single-pass timing check (no baseline needed) -- `/benchmark --pages /,/dashboard,/api/health` — specify pages -- `/benchmark --diff` — benchmark only pages affected by current branch -- `/benchmark --trend` — show performance trends from historical data +- `/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 ## Instructions diff --git a/benchmark/SKILL.md.tmpl b/benchmark/SKILL.md.tmpl index 038f16f5f..939c905bd 100644 --- a/benchmark/SKILL.md.tmpl +++ b/benchmark/SKILL.md.tmpl @@ -1,20 +1,21 @@ --- -name: benchmark +name: gstack-benchmark preamble-tier: 1 version: 1.0.0 description: | - Performance regression detection using the browse daemon. Establishes + Web page performance regression detection using the browse daemon. Establishes baselines for page load times, Core Web Vitals, and resource sizes. Compares before/after on every PR. Tracks performance trends over time. - Use when: "performance", "benchmark", "page speed", "lighthouse", "web vitals", - "bundle size", "load time". (gstack) + Use when: "web performance benchmark", "page speed", "lighthouse", + "web vitals", "bundle size", "load time". Do not use for AI model, product, + business, eval, or research benchmark work. (gstack) voice-triggers: - "speed test" - "check performance" triggers: - - performance benchmark + - web performance benchmark - check page speed - - detect performance regression + - detect page performance regression allowed-tools: - Bash - Read @@ -27,22 +28,29 @@ allowed-tools: {{BROWSE_SETUP}} -# /benchmark — Performance Regression Detection +# /gstack-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 + +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`. + ## User-invocable -When the user types `/benchmark`, run this skill. +When the user types `/gstack-benchmark`, run this skill. ## Arguments -- `/benchmark ` — full performance audit with baseline comparison -- `/benchmark --baseline` — capture baseline (run before making changes) -- `/benchmark --quick` — single-pass timing check (no baseline needed) -- `/benchmark --pages /,/dashboard,/api/health` — specify pages -- `/benchmark --diff` — benchmark only pages affected by current branch -- `/benchmark --trend` — show performance trends from historical data +- `/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 ## Instructions diff --git a/scripts/gen-llms-txt.ts b/scripts/gen-llms-txt.ts index 02e55d12d..6ade865a3 100644 --- a/scripts/gen-llms-txt.ts +++ b/scripts/gen-llms-txt.ts @@ -24,10 +24,12 @@ 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; } /** @@ -113,6 +115,11 @@ function oneLine(text: string): string { return first.replace(/\s+/g, ' ').trim(); } +function skillDirForOutput(output: string): string { + const dir = path.dirname(output); + return dir === '.' ? '.' : dir; +} + interface GenerateOptions { /** Override repo root (for tests). */ root?: string; @@ -135,6 +142,9 @@ export async function generateLlmsTxt(opts: GenerateOptions = {}): Promise a.name.localeCompare(b.name)); @@ -167,7 +177,7 @@ export async function generateLlmsTxt(opts: GenerateOptions = {}): Promise { } }); + test('benchmark skill is scoped to web performance', () => { + const generated = fs.readFileSync(path.join(ROOT, 'benchmark', 'SKILL.md'), 'utf-8'); + const template = fs.readFileSync(path.join(ROOT, 'benchmark', 'SKILL.md.tmpl'), 'utf-8'); + + for (const content of [generated, template]) { + const frontmatter = content.match(/^---\n([\s\S]*?)\n---/)?.[1] ?? ''; + expect(frontmatter).toContain('name: gstack-benchmark'); + expect(frontmatter).not.toContain('name: benchmark'); + 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.'); + } + }); + test('qa and qa-only templates use QA_METHODOLOGY placeholder', () => { const qaTmpl = fs.readFileSync(path.join(ROOT, 'qa', 'SKILL.md.tmpl'), 'utf-8'); expect(qaTmpl).toContain('{{QA_METHODOLOGY}}'); diff --git a/test/llms-txt-shape.test.ts b/test/llms-txt-shape.test.ts index 3cbebb426..14de056c9 100644 --- a/test/llms-txt-shape.test.ts +++ b/test/llms-txt-shape.test.ts @@ -25,17 +25,29 @@ describe('gen-llms-txt — shape', () => { expect(generated.content).toContain('auto-generated'); }); - test('every skill .tmpl in the repo appears in the index', () => { + test('every generated Claude skill appears in the index', () => { const templates = discoverTemplates(ROOT); + const claudeGeneratedTemplates = templates.filter((t) => path.dirname(t.output) !== 'claude'); // Filter to those that successfully parsed (have name + description). expect(generated.skills.length).toBeGreaterThan(0); - expect(generated.skills.length).toBeLessThanOrEqual(templates.length); + expect(generated.skills.length).toBeLessThanOrEqual(claudeGeneratedTemplates.length); + expect(generated.content).not.toContain('[/claude]'); for (const skill of generated.skills) { expect(generated.content).toMatch(new RegExp(`/${skill.name}\\b`)); } }); + test('every skill link points at an existing generated file', () => { + const skillsSection = generated.content.split('## Skills')[1].split('## Browse Commands')[0]; + const links = [...skillsSection.matchAll(/- \[\/[^\]]+\]\(([^)]+)\):/g)].map((m) => m[1]); + expect(links.length).toBe(generated.skills.length); + + for (const href of links) { + expect(fs.existsSync(path.join(ROOT, href)), href).toBe(true); + } + }); + test('every browse command in COMMAND_DESCRIPTIONS appears in the index', () => { expect(generated.browseCommands.length).toBeGreaterThan(0); for (const cmd of generated.browseCommands) {