diff --git a/CHANGELOG.md b/CHANGELOG.md index 02ee78b58..15162a5d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [1.91.283.0] - 2026-10-27 + +**/benchmark's collection, comparison, budget and trend phases now agree with its vet-the-number section.** The 2026-10-27 flaky pass (GSTA-128) read the `benchmark/SKILL.md perf collection` judge's six samples from run 40558235014: every sample named the same contradictions, so the prose was repaired and no threshold moved. + +### Fixed +- **Phase 3 run count**: the script runs 5 times per page and later phases use each metric's median with its range, which is what the mandatory vet section already demanded; the old optional 3-run median contradicted it. `--quick` is the one-run ballpark and produces no comparison verdict. +- **Phase 5 against a stored baseline**: the baseline ran earlier, so question 5's alternation is unavailable; the comparison uses both sides' `range_ms`, names the limiter from Phase 6, and is inconclusive when either side is one run. A delta smaller than either spread is OK whatever threshold it crosses. The report leads with the verdict and names the run count. +- **Phase 4 schema** carries `runs` and `range_ms`; **Phase 7**'s grade example counts 3 PASS, 1 WARNING, 2 FAIL (4/6 below budget); **Phase 8** reads the benchmark and baseline JSON files by `timestamp` and says where Bundle and Grade come from; **Phase 9** defines the JSON as the Phase 4 object plus per-page `grade`, `verdict` and metric `status`. +- Nearby prose tightened so the skeleton stays under its 31,300-byte cap (31,290). Judge after the repair: clarity, completeness and actionability 4/4/4 on all three samples, three runs. + ## [1.91.282.0] - 2026-10-27 **A merge the pre-merge verify accepted no longer freezes the queue, and a slot stamp touches only the stamp files.** Train 24 (2026-10-27) merged #3434 and #3453, then froze on #3453's post-merge check and paid a $31 backstop: the pre-merge verify judged the lane's CHANGELOG as main's plus the pin's own entry and said MERGE_CONDITION, the post-merge check compared main to T2 section by section, and T2's one `[Unreleased]` (both stacked entries) had become two released sections. In both slots `gstack-restamp` also listed `lib/ship-policy.ts` as a fifth stamp file, because the lane's uncommitted base merge made #3511's `DOCS_CAPS_KEY_VERSION` look like the branch's own tentative key. And `mergeChangelogs` refused the stacked child outright, so the lane's hand resolve dropped #3453's two wording fixes to the released 1.91.276.0 entry. diff --git a/VERSION b/VERSION index 269b6e89c..0b68a0506 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.91.282.0 +1.91.283.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index 583844b7e..894898db8 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.91.282.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.91.283.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/benchmark/SKILL.md b/benchmark/SKILL.md index 7d2be1bd4..56c76e733 100644 --- a/benchmark/SKILL.md +++ b/benchmark/SKILL.md @@ -235,9 +235,9 @@ Label `$B` output with the same evidence lines (`URL=`, `CONSOLE_ERRORS=`, `DIFF # /benchmark — 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**. Performance dies by a thousand paper cuts. Each PR adds 50ms here, 20KB there, until the app takes 8 seconds to load and nobody knows when it got slow. -Your job is to measure, baseline, compare, and alert. You drive the Aside browser and read `performance.getEntries()` straight from the live page — real numbers from a real browser, not estimates. +You measure, baseline, compare and alert, reading `performance.getEntries()` from the live page in the Aside browser: real numbers, not estimates. ## User-invocable When the user types `/benchmark`, run this skill. @@ -245,7 +245,7 @@ When the user types `/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 --quick` — one-run ballpark, no baseline - `/benchmark --pages /,/dashboard,/api/health` — specify pages - `/benchmark --diff` — benchmark only pages affected by current branch - `/benchmark --trend` — show performance trends from historical data @@ -272,10 +272,9 @@ mkdir -p .gstack/benchmark-reports/baselines git update-index --refresh >/dev/null 2>&1 || true ``` -Run `git update-index --refresh` before timing anything that spawns git (a dev -server's version stamp, a build step, `--diff` discovery) in a benchmark or bisect -worktree: a stale index makes the first `git status`/`git diff` pay a full refresh, -and that cost lands in the measurement. +Run the refresh before timing anything that spawns git (a dev server's version stamp, +a build step, `--diff` discovery): in a benchmark or bisect worktree a stale index makes +the first git call pay a full refresh inside the timed region. ### Phase 2: Page Discovery @@ -307,7 +306,7 @@ await closeTab(pg); console.log("GSTACK_STEP_OK"); ' ``` -`NAV=` is the navigation timing entry, `PAINT=` the paint entries (FCP lives here), `LCP=` the largest-contentful-paint start time (`null` if the page emitted no LCP entry within 3s), `RESOURCES=` the 15 slowest resources, `SCRIPTS=` / `CSS=` the bundle inventory, `SUMMARY=` request count, total transfer, and requests by type. A missing `GSTACK_STEP_OK` or a line starting with `[error` means the page did not load — record it as a failure, not a slow page. +`NAV=` is the navigation timing entry, `PAINT=` the paint entries (FCP lives here), `LCP=` the largest-contentful-paint start time, `RESOURCES=` the 15 slowest resources, `SCRIPTS=` / `CSS=` the bundle inventory, `SUMMARY=` request count, total transfer, and requests by type. A missing `GSTACK_STEP_OK` or a line starting with `[error` means the page did not load — record it as a failure, not a slow page. Extract key metrics from the labelled lines (`NAV=` unless stated otherwise): - **TTFB** (Time to First Byte): `responseStart - requestStart` @@ -317,7 +316,7 @@ Extract key metrics from the labelled lines (`NAV=` unless stated otherwise): - **DOM Complete**: `domComplete - startTime` - **Full Load**: `loadEventEnd - startTime` -Load times jitter with the network. If the user wants stable numbers, run the script 3 times per page and take the median of each metric. +Run the script 5 times per page; later phases use each metric's median and report the run count and range. `--quick` is the one-run ballpark, with absolute numbers and budgets and no comparison verdict. ### Phase 4: Baseline Capture (--baseline mode) @@ -328,6 +327,7 @@ Save metrics to baseline file: "url": "", "timestamp": "", "branch": "", + "runs": 5, "pages": { "/": { "ttfb_ms": 120, @@ -340,6 +340,7 @@ Save metrics to baseline file: "total_transfer_bytes": 1250000, "js_bundle_bytes": 450000, "css_bundle_bytes": 85000, + "range_ms": {"lcp": [760, 840], "full_load": [1350, 1480]}, "largest_resources": [ {"name": "main.js", "size": 320000, "duration": 180}, {"name": "vendor.js", "size": 130000, "duration": 90} @@ -349,26 +350,26 @@ Save metrics to baseline file: } ``` -Write to `.gstack/benchmark-reports/baselines/baseline.json`. -Also retain an immutable `{UTC-timestamp}-baseline.json` beside it for trends. Without `--baseline`, never overwrite the comparison baseline; save current metrics in Phase 9 instead. +Each `_ms` value is the median of `runs` runs; `range_ms` holds each timing metric's min and max (two shown). Write to `.gstack/benchmark-reports/baselines/baseline.json`. +Also retain an immutable `{UTC-timestamp}-baseline.json` beside it for trends. Without `--baseline`, never overwrite it; Phase 9 saves the run. ### Phase 5: Comparison > **STOP.** Before reporting a comparison, speedup or regression (Phase 5), or any measured delta, Read `~/.claude/skills/gstack/benchmark/sections/vet-the-number.md` and execute it > in full. Do not work from memory — that section is the source of truth for this step. -If baseline exists, compare current metrics against it: -Without a baseline, report absolute measurements and budgets only, mark comparison unavailable, and recommend a `--baseline` run. Missing metrics remain N/A. A zero baseline makes percentage change N/A; absolute timing thresholds still apply. +If baseline exists, compare current metrics against it. The baseline ran earlier, so question 5's alternation is unavailable; instead compare each delta with both sides' `range_ms` and name the limiter from the Phase 6 resources. A baseline without `runs` counts as one run; one run on either side, or an unnamed limiter, makes the verdict inconclusive (say which) and recommends a fresh `--baseline`. +Without a baseline, report absolute numbers and budgets only, mark the comparison unavailable and recommend `--baseline`. Missing metrics remain N/A. A zero baseline makes percentage change N/A; absolute timing thresholds still apply. ``` PERFORMANCE REPORT — [url] ══════════════════════════ +VERDICT: slower — LCP 800ms → 1600ms at the median, outside both ranges, bound by the hero image decode Branch: [current-branch] vs baseline ([baseline-branch]) +Runs: 5 per side (medians; ranges in the JSON report) Page: / -───────────────────────────────────────────────────── Metric Baseline Current Delta Status -──────── ──────── ─────── ───── ────── TTFB 120ms 135ms +15ms OK FCP 450ms 480ms +30ms OK LCP 800ms 1600ms +800ms REGRESSION @@ -381,10 +382,10 @@ JS Bundle 450KB 720KB +270KB REGRESSION CSS Bundle 85KB 88KB +3KB OK REGRESSIONS DETECTED: 4 - [1] LCP doubled (800ms → 1600ms) — likely a large new image or blocking resource - [2] Total transfer +50% (1.2MB → 1.8MB) — check new JS bundles - [3] JS bundle +60% (450KB → 720KB) — new dependency or missing tree-shaking - [4] Full load +700ms (1400ms → 2100ms) — inspect the slowest resources + [1] LCP 800ms → 1600ms — a large new image or blocking resource + [2] Transfer 1.2MB → 1.8MB — new JS bundles + [3] JS 450KB → 720KB — new dependency or missing tree-shaking + [4] Full load 1400ms → 2100ms — see Phase 6 ``` **Regression thresholds:** @@ -393,7 +394,7 @@ REGRESSIONS DETECTED: 4 - Bundle size and total transfer: >25% increase = REGRESSION - Bundle size and total transfer: >10% increase = WARNING - Request count: >30% increase = WARNING (no separate regression threshold) -Apply REGRESSION before WARNING; otherwise OK. Negative deltas are improvements. +Apply REGRESSION before WARNING; otherwise OK. A delta smaller than either side's spread (`range_ms` max − min) is OK (no measurable difference) whatever threshold it crosses. Negative deltas are improvements. ### Phase 6: Slowest Resources @@ -409,11 +410,13 @@ TOP 10 SLOWEST RESOURCES ... RECOMMENDATIONS: -- vendor.chunk.js: Consider code-splitting — 320KB is large for initial load -- analytics.js: Load async/defer — blocks rendering for 250ms -- hero-image.webp: Add width/height to prevent CLS, consider lazy loading +- vendor.chunk.js: code-split — 320KB on initial load +- analytics.js: load async/defer — blocks rendering for 250ms +- hero-image.webp: add width/height (CLS), lazy-load ``` +Third-party = any host but the page's origin. Each recommendation names a resource here or in a Phase 5 regression, the metric it moves and one remedy (split, defer, compress, lazy-load, cache). + ### Phase 7: Performance Budget Check against industry budgets: @@ -423,7 +426,6 @@ For each available metric, FAIL at or above the budget, WARNING from 90% to belo PERFORMANCE BUDGET CHECK ════════════════════════ Metric Budget Actual Status -──────── ────── ────── ────── FCP < 1.8s 0.48s PASS LCP < 2.5s 1.6s PASS Total JS < 500KB 720KB FAIL @@ -431,12 +433,12 @@ Total CSS < 100KB 88KB PASS Total Transfer < 2MB 1.8MB WARNING (90%) HTTP Requests < 50 58 FAIL -Grade: B (4/6 passing) +Grade: B (4/6 below budget: 3 PASS, 1 WARNING, 2 FAIL) ``` ### Phase 8: Trend Analysis (--trend mode) -Load historical baseline files and show trends: +Load every `*-benchmark.json` and `baselines/*-baseline.json` under `.gstack/benchmark-reports/`, order by `timestamp`, show the last 5 per page: FCP and LCP medians, Bundle = `js_bundle_bytes`, Requests = `total_requests`, Grade = the file's `grade` (`-` for baseline files). ``` PERFORMANCE TRENDS (last 5 benchmarks) @@ -448,20 +450,19 @@ Date FCP LCP Bundle Requests Grade 2026-03-16 460ms 850ms 520KB 48 B 2026-03-18 480ms 1600ms 720KB 58 B -TREND: Performance degrading. LCP doubled in 8 days. - JS bundle growing 50KB/week. Investigate. +TREND: degrading. LCP doubled in 8 days; JS bundle +50KB/week. ``` ### Phase 9: Save Report -Write to `.gstack/benchmark-reports/{date}-benchmark.md` and `.gstack/benchmark-reports/{date}-benchmark.json`. +Write `.gstack/benchmark-reports/{UTC-timestamp}-benchmark.md` (the Phase 5–8 output) and `.json` beside it: the Phase 4 object plus, per page, `grade` (Phase 7) and, with a baseline, `verdict` and each metric's `status` (Phase 5). ## Important Rules - **Measure, don't guess.** Use actual performance.getEntries() data, not estimates. - **Vet every number before reporting it.** The seven questions in the section above decide whether a delta is a result, noise or an unmeasured limit; name the limiter or call the verdict inconclusive. -- **Baseline is essential.** Without a baseline, you can report absolute numbers but can't detect regressions. Always encourage baseline capture. -- **Relative thresholds, not absolute.** 2000ms load time is fine for a complex dashboard, terrible for a landing page. Compare against YOUR baseline. -- **Third-party scripts are context.** Flag them, but the user can't fix Google Analytics being slow. Focus recommendations on first-party resources. -- **Bundle size is the leading indicator.** Load time varies with network. Bundle size is deterministic. Track it religiously. +- **Baseline is essential.** Without one you report absolute numbers and detect no regressions. Encourage one. +- **Relative thresholds, not absolute.** 2000ms is fine for a complex dashboard, terrible for a landing page. Compare against YOUR baseline. +- **Third-party scripts are context.** Flag them; the user can't fix a slow Google Analytics. Recommend first-party fixes. +- **Bundle size is the leading indicator.** Load time varies with the network; bundle size is deterministic. Track it. - **Read-only.** Produce the report. Don't modify code unless explicitly asked. diff --git a/benchmark/SKILL.md.tmpl b/benchmark/SKILL.md.tmpl index 09f470930..12a805eec 100644 --- a/benchmark/SKILL.md.tmpl +++ b/benchmark/SKILL.md.tmpl @@ -32,9 +32,9 @@ allowed-tools: # /benchmark — 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**. Performance dies by a thousand paper cuts. Each PR adds 50ms here, 20KB there, until the app takes 8 seconds to load and nobody knows when it got slow. -Your job is to measure, baseline, compare, and alert. You drive the Aside browser and read `performance.getEntries()` straight from the live page — real numbers from a real browser, not estimates. +You measure, baseline, compare and alert, reading `performance.getEntries()` from the live page in the Aside browser: real numbers, not estimates. ## User-invocable When the user types `/benchmark`, run this skill. @@ -42,7 +42,7 @@ When the user types `/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 --quick` — one-run ballpark, no baseline - `/benchmark --pages /,/dashboard,/api/health` — specify pages - `/benchmark --diff` — benchmark only pages affected by current branch - `/benchmark --trend` — show performance trends from historical data @@ -62,10 +62,9 @@ mkdir -p .gstack/benchmark-reports/baselines git update-index --refresh >/dev/null 2>&1 || true ``` -Run `git update-index --refresh` before timing anything that spawns git (a dev -server's version stamp, a build step, `--diff` discovery) in a benchmark or bisect -worktree: a stale index makes the first `git status`/`git diff` pay a full refresh, -and that cost lands in the measurement. +Run the refresh before timing anything that spawns git (a dev server's version stamp, +a build step, `--diff` discovery): in a benchmark or bisect worktree a stale index makes +the first git call pay a full refresh inside the timed region. ### Phase 2: Page Discovery @@ -97,7 +96,7 @@ await closeTab(pg); console.log("GSTACK_STEP_OK"); ' ``` -`NAV=` is the navigation timing entry, `PAINT=` the paint entries (FCP lives here), `LCP=` the largest-contentful-paint start time (`null` if the page emitted no LCP entry within 3s), `RESOURCES=` the 15 slowest resources, `SCRIPTS=` / `CSS=` the bundle inventory, `SUMMARY=` request count, total transfer, and requests by type. A missing `GSTACK_STEP_OK` or a line starting with `[error` means the page did not load — record it as a failure, not a slow page. +`NAV=` is the navigation timing entry, `PAINT=` the paint entries (FCP lives here), `LCP=` the largest-contentful-paint start time, `RESOURCES=` the 15 slowest resources, `SCRIPTS=` / `CSS=` the bundle inventory, `SUMMARY=` request count, total transfer, and requests by type. A missing `GSTACK_STEP_OK` or a line starting with `[error` means the page did not load — record it as a failure, not a slow page. Extract key metrics from the labelled lines (`NAV=` unless stated otherwise): - **TTFB** (Time to First Byte): `responseStart - requestStart` @@ -107,7 +106,7 @@ Extract key metrics from the labelled lines (`NAV=` unless stated otherwise): - **DOM Complete**: `domComplete - startTime` - **Full Load**: `loadEventEnd - startTime` -Load times jitter with the network. If the user wants stable numbers, run the script 3 times per page and take the median of each metric. +Run the script 5 times per page; later phases use each metric's median and report the run count and range. `--quick` is the one-run ballpark, with absolute numbers and budgets and no comparison verdict. ### Phase 4: Baseline Capture (--baseline mode) @@ -118,6 +117,7 @@ Save metrics to baseline file: "url": "", "timestamp": "", "branch": "", + "runs": 5, "pages": { "/": { "ttfb_ms": 120, @@ -130,6 +130,7 @@ Save metrics to baseline file: "total_transfer_bytes": 1250000, "js_bundle_bytes": 450000, "css_bundle_bytes": 85000, + "range_ms": {"lcp": [760, 840], "full_load": [1350, 1480]}, "largest_resources": [ {"name": "main.js", "size": 320000, "duration": 180}, {"name": "vendor.js", "size": 130000, "duration": 90} @@ -139,25 +140,25 @@ Save metrics to baseline file: } ``` -Write to `.gstack/benchmark-reports/baselines/baseline.json`. -Also retain an immutable `{UTC-timestamp}-baseline.json` beside it for trends. Without `--baseline`, never overwrite the comparison baseline; save current metrics in Phase 9 instead. +Each `_ms` value is the median of `runs` runs; `range_ms` holds each timing metric's min and max (two shown). Write to `.gstack/benchmark-reports/baselines/baseline.json`. +Also retain an immutable `{UTC-timestamp}-baseline.json` beside it for trends. Without `--baseline`, never overwrite it; Phase 9 saves the run. ### Phase 5: Comparison {{SECTION:vet-the-number}} -If baseline exists, compare current metrics against it: -Without a baseline, report absolute measurements and budgets only, mark comparison unavailable, and recommend a `--baseline` run. Missing metrics remain N/A. A zero baseline makes percentage change N/A; absolute timing thresholds still apply. +If baseline exists, compare current metrics against it. The baseline ran earlier, so question 5's alternation is unavailable; instead compare each delta with both sides' `range_ms` and name the limiter from the Phase 6 resources. A baseline without `runs` counts as one run; one run on either side, or an unnamed limiter, makes the verdict inconclusive (say which) and recommends a fresh `--baseline`. +Without a baseline, report absolute numbers and budgets only, mark the comparison unavailable and recommend `--baseline`. Missing metrics remain N/A. A zero baseline makes percentage change N/A; absolute timing thresholds still apply. ``` PERFORMANCE REPORT — [url] ══════════════════════════ +VERDICT: slower — LCP 800ms → 1600ms at the median, outside both ranges, bound by the hero image decode Branch: [current-branch] vs baseline ([baseline-branch]) +Runs: 5 per side (medians; ranges in the JSON report) Page: / -───────────────────────────────────────────────────── Metric Baseline Current Delta Status -──────── ──────── ─────── ───── ────── TTFB 120ms 135ms +15ms OK FCP 450ms 480ms +30ms OK LCP 800ms 1600ms +800ms REGRESSION @@ -170,10 +171,10 @@ JS Bundle 450KB 720KB +270KB REGRESSION CSS Bundle 85KB 88KB +3KB OK REGRESSIONS DETECTED: 4 - [1] LCP doubled (800ms → 1600ms) — likely a large new image or blocking resource - [2] Total transfer +50% (1.2MB → 1.8MB) — check new JS bundles - [3] JS bundle +60% (450KB → 720KB) — new dependency or missing tree-shaking - [4] Full load +700ms (1400ms → 2100ms) — inspect the slowest resources + [1] LCP 800ms → 1600ms — a large new image or blocking resource + [2] Transfer 1.2MB → 1.8MB — new JS bundles + [3] JS 450KB → 720KB — new dependency or missing tree-shaking + [4] Full load 1400ms → 2100ms — see Phase 6 ``` **Regression thresholds:** @@ -182,7 +183,7 @@ REGRESSIONS DETECTED: 4 - Bundle size and total transfer: >25% increase = REGRESSION - Bundle size and total transfer: >10% increase = WARNING - Request count: >30% increase = WARNING (no separate regression threshold) -Apply REGRESSION before WARNING; otherwise OK. Negative deltas are improvements. +Apply REGRESSION before WARNING; otherwise OK. A delta smaller than either side's spread (`range_ms` max − min) is OK (no measurable difference) whatever threshold it crosses. Negative deltas are improvements. ### Phase 6: Slowest Resources @@ -198,11 +199,13 @@ TOP 10 SLOWEST RESOURCES ... RECOMMENDATIONS: -- vendor.chunk.js: Consider code-splitting — 320KB is large for initial load -- analytics.js: Load async/defer — blocks rendering for 250ms -- hero-image.webp: Add width/height to prevent CLS, consider lazy loading +- vendor.chunk.js: code-split — 320KB on initial load +- analytics.js: load async/defer — blocks rendering for 250ms +- hero-image.webp: add width/height (CLS), lazy-load ``` +Third-party = any host but the page's origin. Each recommendation names a resource here or in a Phase 5 regression, the metric it moves and one remedy (split, defer, compress, lazy-load, cache). + ### Phase 7: Performance Budget Check against industry budgets: @@ -212,7 +215,6 @@ For each available metric, FAIL at or above the budget, WARNING from 90% to belo PERFORMANCE BUDGET CHECK ════════════════════════ Metric Budget Actual Status -──────── ────── ────── ────── FCP < 1.8s 0.48s PASS LCP < 2.5s 1.6s PASS Total JS < 500KB 720KB FAIL @@ -220,12 +222,12 @@ Total CSS < 100KB 88KB PASS Total Transfer < 2MB 1.8MB WARNING (90%) HTTP Requests < 50 58 FAIL -Grade: B (4/6 passing) +Grade: B (4/6 below budget: 3 PASS, 1 WARNING, 2 FAIL) ``` ### Phase 8: Trend Analysis (--trend mode) -Load historical baseline files and show trends: +Load every `*-benchmark.json` and `baselines/*-baseline.json` under `.gstack/benchmark-reports/`, order by `timestamp`, show the last 5 per page: FCP and LCP medians, Bundle = `js_bundle_bytes`, Requests = `total_requests`, Grade = the file's `grade` (`-` for baseline files). ``` PERFORMANCE TRENDS (last 5 benchmarks) @@ -237,20 +239,19 @@ Date FCP LCP Bundle Requests Grade 2026-03-16 460ms 850ms 520KB 48 B 2026-03-18 480ms 1600ms 720KB 58 B -TREND: Performance degrading. LCP doubled in 8 days. - JS bundle growing 50KB/week. Investigate. +TREND: degrading. LCP doubled in 8 days; JS bundle +50KB/week. ``` ### Phase 9: Save Report -Write to `.gstack/benchmark-reports/{date}-benchmark.md` and `.gstack/benchmark-reports/{date}-benchmark.json`. +Write `.gstack/benchmark-reports/{UTC-timestamp}-benchmark.md` (the Phase 5–8 output) and `.json` beside it: the Phase 4 object plus, per page, `grade` (Phase 7) and, with a baseline, `verdict` and each metric's `status` (Phase 5). ## Important Rules - **Measure, don't guess.** Use actual performance.getEntries() data, not estimates. - **Vet every number before reporting it.** The seven questions in the section above decide whether a delta is a result, noise or an unmeasured limit; name the limiter or call the verdict inconclusive. -- **Baseline is essential.** Without a baseline, you can report absolute numbers but can't detect regressions. Always encourage baseline capture. -- **Relative thresholds, not absolute.** 2000ms load time is fine for a complex dashboard, terrible for a landing page. Compare against YOUR baseline. -- **Third-party scripts are context.** Flag them, but the user can't fix Google Analytics being slow. Focus recommendations on first-party resources. -- **Bundle size is the leading indicator.** Load time varies with network. Bundle size is deterministic. Track it religiously. +- **Baseline is essential.** Without one you report absolute numbers and detect no regressions. Encourage one. +- **Relative thresholds, not absolute.** 2000ms is fine for a complex dashboard, terrible for a landing page. Compare against YOUR baseline. +- **Third-party scripts are context.** Flag them; the user can't fix a slow Google Analytics. Recommend first-party fixes. +- **Bundle size is the leading indicator.** Load time varies with the network; bundle size is deterministic. Track it. - **Read-only.** Produce the report. Don't modify code unless explicitly asked. diff --git a/docs/skills.md b/docs/skills.md index 24ab4a175..d8ab32129 100644 --- a/docs/skills.md +++ b/docs/skills.md @@ -901,12 +901,12 @@ 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. -It measures in your Aside browser — the page's own `performance` navigation and resource entries from a real load, not synthetic estimates. Multiple runs averaged. Results persist so you can track trends across PRs. +It measures in your Aside browser — the page's own `performance` navigation and resource entries from a real load, not synthetic estimates. Each page runs five times and is reported as the median with its range (`--quick` is a single-run ballpark). Results persist so you can track trends across PRs. ``` You: /benchmark https://myapp.com -Claude: Benchmarking 5 pages (3 runs each)... +Claude: Benchmarking 5 pages (5 runs each)... / load: 1.2s LCP: 0.9s CLS: 0.01 resources: 24 (890KB) /dashboard load: 2.1s LCP: 1.8s CLS: 0.03 resources: 31 (1.4MB) diff --git a/package.json b/package.json index 66ec462ee..da0b87294 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.91.282", + "version": "1.91.283", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module",