mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 14:34:42 +01:00
v1.91.283.0 fix(benchmark): reconcile the collection, comparison, budget and trend phases with the vet-the-number section (GSTA-128) (#3520)
* fix(benchmark): reconcile the collection, comparison, budget and trend phases with the vet-the-number section
The perf-collection judge (run 40558235014, both attempts, six samples) named
the same contradictions: Phase 3 offered an optional 3-run median while the
mandatory vet section demands 5 alternating runs per side and a stored
baseline cannot be interleaved; the budget example read '4/6 passing' against
a rule that counts WARNING as below budget; the trend table showed Grade and
Bundle columns no stored file defined; Phase 9 named two files with no
contents. Phase 3 now runs 5 per page with median and range and names
--quick as the one-run ballpark; the baseline schema carries runs and
range_ms; Phase 5 says how the seven questions apply against a stored
baseline (compare the delta with both spreads, name the limiter from Phase 6,
inconclusive when either side is one run); the grade example counts 3 PASS,
1 WARNING, 2 FAIL; Phase 8 reads the benchmark and baseline JSON files by
timestamp; Phase 9 defines the JSON as the Phase 4 object plus grade, verdict
and per-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, twice.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* changelog: /benchmark phases reconciled with the vet-the-number section (GSTA-128)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs: /benchmark runs five times per page in docs/skills.md
Step 14.5 of /ship on #3520: the /benchmark section said 'Multiple runs averaged' and
'3 runs each'; it now matches Phase 3 (five runs per page, median with range, --quick a
single-run ballpark). GSTA-128.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* RESTAMP: 1.91.282.0 -> 1.91.283.0 (stamped; queue_mode=stamp-at-merge base=origin/main@9ccb5233f309 policy=base origin/main@9ccb5233f309:.gstack/ship-policy.json)
ship, next in queue at 1.91.283.0
STAMP_VERSION: 1.91.283.0
claims: ignored (stamp-at-merge)
stage version: done (wrote 1.91.283.0)
stage release_tool: done (bun scripts/gen-agents-digest.ts)
stage changelog: done (Unreleased -> 1.91.283.0)
stage stamp_paths: skipped (no tentative mention newer than the fork version 1.91.281.0 and none of base 1.91.282.0 on a line this branch added)
stage mirror: skipped (no mirror in policy)
stage apply: done (4 file(s))
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 695e20bd5..15162a5d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
# Changelog
-## [Unreleased]
+## [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.
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/node_modules b/node_modules
new file mode 120000
index 000000000..858ae29ce
--- /dev/null
+++ b/node_modules
@@ -0,0 +1 @@
+/workspace/gstack/node_modules
\ No newline at end of file
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",
FILES: CHANGELOG.md VERSION agents-digest/gstack-AGENTS.md package.json
MERGE_CONDITION: sha=aa8c07b76935d91ec548fb5a32fd429392f88460
next: NEW_VERSION=1.91.283.0; commit and push only the stamp diff above (FILES), with no edit of your own: the train's verify accepts the CHANGELOG only as the base's plus this PR's entry re-headed; merge with the expected head aa8c07b769 and recompute on a stale queue.
GSTACK_RESULT: skill=restamp status=complete run=/workspace/gstack/.gstack/tmp/restamp-journal.json
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
9ccb5233f3
commit
215c76e334
7 files changed
+83
-71
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
+34
-33
@@ -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 <url>` — full performance audit with baseline comparison
|
||||
- `/benchmark <url> --baseline` — capture baseline (run before making changes)
|
||||
- `/benchmark <url> --quick` — single-pass timing check (no baseline needed)
|
||||
- `/benchmark <url> --quick` — one-run ballpark, no baseline
|
||||
- `/benchmark <url> --pages /,/dashboard,/api/health` — specify pages
|
||||
- `/benchmark --diff` — benchmark only pages affected by current branch
|
||||
- `/benchmark --trend` — show performance trends from historical data
|
||||
@@ -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": "<url>",
|
||||
"timestamp": "<ISO>",
|
||||
"branch": "<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.
|
||||
+34
-33
@@ -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 <url>` — full performance audit with baseline comparison
|
||||
- `/benchmark <url> --baseline` — capture baseline (run before making changes)
|
||||
- `/benchmark <url> --quick` — single-pass timing check (no baseline needed)
|
||||
- `/benchmark <url> --quick` — one-run ballpark, no baseline
|
||||
- `/benchmark <url> --pages /,/dashboard,/api/health` — specify pages
|
||||
- `/benchmark --diff` — benchmark only pages affected by current branch
|
||||
- `/benchmark --trend` — show performance trends from historical data
|
||||
@@ -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": "<url>",
|
||||
"timestamp": "<ISO>",
|
||||
"branch": "<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.
|
||||
+2
-2
@@ -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)
|
||||
|
||||
+1
-1
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user