Skip to content

Commit 81602a2

Browse files
committed
Merge personal repo history into master
# Conflicts: # README.md
2 parents 4dc3689 + 7386073 commit 81602a2

95 files changed

Lines changed: 10978 additions & 2 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude-plugin/marketplace.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "browsergnome",
3+
"description": "The autonomous JS web performance engineer, as a self-installable Claude Code plugin marketplace.",
4+
"owner": {
5+
"name": "Xavier",
6+
"email": "xavi-999@users.noreply.github.com"
7+
},
8+
"plugins": [
9+
{
10+
"name": "browsergnome",
11+
"source": "./",
12+
"version": "0.2.1",
13+
"description": "The autonomous JS web performance engineer — one propose -> measure -> keep/revert loop over chrome-devtools-mcp, with an LCP Attribution Map and a per-repo performance memory.",
14+
"author": {
15+
"name": "Xavier",
16+
"email": "xavi-999@users.noreply.github.com"
17+
},
18+
"license": "MIT",
19+
"keywords": [
20+
"web",
21+
"performance",
22+
"chrome-devtools",
23+
"lcp",
24+
"claude-code",
25+
"plugin"
26+
],
27+
"category": "performance",
28+
"tags": [
29+
"web",
30+
"performance",
31+
"mcp"
32+
]
33+
}
34+
]
35+
}

.claude-plugin/plugin.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3+
"name": "browsergnome",
4+
"version": "0.2.1",
5+
"description": "The autonomous JS web performance engineer. Orchestrates chrome-devtools-mcp into one propose → measure → keep/revert loop — with an LCP Attribution Map, a live HTML run report, configurable commit mode, and per-repo performance memory.",
6+
"author": {
7+
"name": "Xavier",
8+
"email": "xavi-999@users.noreply.github.com"
9+
},
10+
"license": "MIT",
11+
"keywords": [
12+
"claude-code",
13+
"chrome-devtools",
14+
"web-vitals",
15+
"lcp",
16+
"performance",
17+
"plugin"
18+
]
19+
}

.github/workflows/ci.yml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# CI covers only the offline chain:
2+
# - all self-tests (stats / playbook / doctor / trace / bundle / lcp-attribution)
3+
# - offline smoke chain: lcp_attribution → build_lcp_map, build_run_report from the sample fixture
4+
# - JSON validation of all plugin manifests
5+
# - official plugin manifest validation (claude plugin validate --strict)
6+
#
7+
# The live path (chrome-devtools-mcp / real browser / MCP runtime) cannot be CI-tested
8+
# because it requires a running Chrome instance and an active Claude Code session.
9+
10+
name: CI
11+
12+
on:
13+
pull_request:
14+
branches: [master]
15+
push:
16+
branches: [master]
17+
18+
jobs:
19+
ci:
20+
runs-on: ubuntu-latest
21+
22+
strategy:
23+
matrix:
24+
node-version: [18.x, 20.x]
25+
26+
steps:
27+
- name: Checkout
28+
uses: actions/checkout@v4
29+
30+
- name: Set up Node.js ${{ matrix.node-version }}
31+
uses: actions/setup-node@v4
32+
with:
33+
node-version: ${{ matrix.node-version }}
34+
35+
- name: Install dependencies
36+
run: npm ci
37+
38+
- name: All self-tests
39+
run: npm test
40+
41+
- name: Offline smoke chain — lcp_attribution
42+
run: node skills/browsergnome/scripts/lcp_attribution.mjs skills/browsergnome/assets/trace.render-blocking-sample.json.gz > /tmp/attribution.json
43+
44+
- name: Offline smoke chain — build_lcp_map
45+
run: node skills/browsergnome/scripts/build_lcp_map.mjs /tmp/attribution.json --out /tmp/lcp-map.html
46+
47+
- name: Offline smoke chain — build_run_report
48+
run: node skills/browsergnome/scripts/build_run_report.mjs skills/browsergnome/assets/run-state.sample.json --out /tmp/report.html
49+
50+
# Assert each output file exists and is non-empty.
51+
- name: Assert smoke-chain outputs are non-empty
52+
run: |
53+
test -s /tmp/attribution.json || (echo "attribution.json is missing or empty" && exit 1)
54+
test -s /tmp/lcp-map.html || (echo "lcp-map.html is missing or empty" && exit 1)
55+
test -s /tmp/report.html || (echo "report.html is missing or empty" && exit 1)
56+
57+
- name: Validate JSON files
58+
run: |
59+
node -e "
60+
const fs = require('fs');
61+
const files = [
62+
'.claude-plugin/plugin.json',
63+
'.claude-plugin/marketplace.json',
64+
'.mcp.json',
65+
'package.json',
66+
'skills/browsergnome/assets/run-state.sample.json',
67+
];
68+
let ok = true;
69+
for (const f of files) {
70+
try {
71+
JSON.parse(fs.readFileSync(f, 'utf8'));
72+
console.log(' ok ' + f);
73+
} catch (e) {
74+
console.error('FAIL ' + f + ': ' + e.message);
75+
ok = false;
76+
}
77+
}
78+
if (!ok) process.exit(1);
79+
"
80+
81+
# Static manifest validation via the official Claude Code CLI.
82+
# No auth needed — `plugin validate` is offline. --strict treats
83+
# warnings (e.g. a missing marketplace description) as errors.
84+
- name: Official plugin validation (strict)
85+
run: |
86+
npm install -g @anthropic-ai/claude-code@2.1.226
87+
claude plugin validate . --strict

.github/workflows/pages.yml

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# GitHub Pages live demo.
2+
#
3+
# Builds the public demo at https://xavi-999.github.io/browsergnome/ :
4+
# - perf-map.html — a REAL static scan of excalidraw at a pinned SHA (the same commit
5+
# recorded in references/perf-map.md's calibration note — 527 modules, 16 hotspots).
6+
# - lcp-map.html — a REAL LCP attribution from the shipped trace fixture
7+
# (skills/browsergnome/assets/trace.render-blocking-sample.json.gz, a real nextjs.org capture).
8+
# - report.html — the live run report rendered from the bundled sample run-state
9+
# (skills/browsergnome/assets/run-state.sample.json).
10+
# - index.html — landing page (docs/pages/index.html) with real substituted stats.
11+
#
12+
# No generated HTML is committed to master — everything is built here and deployed as a
13+
# Pages artifact.
14+
#
15+
# Runs on every push to master, plus manual dispatch. On a free-tier private repo the deploy
16+
# step will fail (GitHub Pages requires a public repo, or a paid plan) — that's expected until
17+
# the repo goes public and Pages is enabled once in Settings > Pages. No other change needed
18+
# here after that; this workflow is otherwise ready to go.
19+
20+
name: Pages
21+
22+
on:
23+
push:
24+
branches: [master]
25+
workflow_dispatch:
26+
27+
permissions:
28+
contents: read
29+
pages: write
30+
id-token: write
31+
32+
concurrency:
33+
group: pages
34+
cancel-in-progress: true
35+
36+
env:
37+
# excalidraw main, resolved 2026-08-08 — the calibration commit in references/perf-map.md.
38+
# Bump deliberately; re-verify hotspot count against the calibration note if you do.
39+
EXCALIDRAW_SHA: 4872083c044491b6d5c96ae134a75464f96d6831
40+
41+
jobs:
42+
build:
43+
runs-on: ubuntu-latest
44+
steps:
45+
- name: Checkout
46+
uses: actions/checkout@v4
47+
48+
- name: Set up Node.js 20
49+
uses: actions/setup-node@v4
50+
with:
51+
node-version: 20
52+
53+
- name: Install dependencies
54+
run: npm ci
55+
56+
- name: Fetch excalidraw at pinned SHA
57+
run: |
58+
git init excalidraw
59+
git -C excalidraw remote add origin https://github.com/excalidraw/excalidraw
60+
git -C excalidraw fetch --depth 1 origin "$EXCALIDRAW_SHA"
61+
git -C excalidraw checkout FETCH_HEAD
62+
63+
# Alias resolution is make-or-break for fan-in/centrality (13 @excalidraw/* aliases).
64+
# If the scan prints "aliases (none found)" the graph is meaningless — fail loudly.
65+
- name: Scan excalidraw (perf_scan)
66+
run: |
67+
set -o pipefail
68+
node skills/browsergnome/scripts/perf_scan.mjs excalidraw --out /tmp/graph.json | tee /tmp/scan.log
69+
if grep -qE 'aliases +\(none found\)' /tmp/scan.log; then
70+
echo "::error::perf_scan resolved no import aliases on excalidraw — graph would be meaninglessly sparse"
71+
exit 1
72+
fi
73+
74+
- name: Build perf map
75+
run: |
76+
mkdir -p _site
77+
node skills/browsergnome/scripts/build_perf_map.mjs /tmp/graph.json --out _site/perf-map.html
78+
79+
- name: Build LCP attribution map (shipped fixture)
80+
run: |
81+
node skills/browsergnome/scripts/lcp_attribution.mjs skills/browsergnome/assets/trace.render-blocking-sample.json.gz > /tmp/attribution.json
82+
node skills/browsergnome/scripts/build_lcp_map.mjs /tmp/attribution.json --out _site/lcp-map.html
83+
84+
- name: Build run report (sample run-state)
85+
run: node skills/browsergnome/scripts/build_run_report.mjs skills/browsergnome/assets/run-state.sample.json --out _site/report.html
86+
87+
- name: Assemble landing page
88+
run: |
89+
cp docs/pages/index.html _site/
90+
cp docs/logo-badge.svg _site/
91+
# Pull the hero numbers from the real scan and the real trace — the page never
92+
# claims a figure the build didn't measure.
93+
node -e "
94+
const g = JSON.parse(require('fs').readFileSync('/tmp/graph.json', 'utf8'));
95+
const attr = JSON.parse(require('fs').readFileSync('/tmp/attribution.json', 'utf8'));
96+
const fmt = (n) => n.toLocaleString('en-US');
97+
const out = {
98+
MODULES: fmt(g.nodes.length),
99+
HOTSPOTS: fmt(g.nodes.filter((n) => n.isHotspot).length),
100+
FINDINGS: fmt(g.nodes.reduce((s, n) => s + (n.findings?.length || 0), 0)),
101+
LCP_MS: attr.lcpMs != null ? Math.round(attr.lcpMs) : '?',
102+
};
103+
require('fs').writeFileSync('/tmp/stats.env', Object.entries(out).map(([k, v]) => k + '=' + v).join('\n'));
104+
"
105+
. /tmp/stats.env
106+
sed -i \
107+
-e "s/__EXCALIDRAW_SHA__/${EXCALIDRAW_SHA:0:8}/g" \
108+
-e "s/__BUILD_DATE__/$(date -u +%Y-%m-%d)/g" \
109+
-e "s/__STAT_MODULES__/${MODULES}/g" \
110+
-e "s/__STAT_HOTSPOTS__/${HOTSPOTS}/g" \
111+
-e "s/__STAT_FINDINGS__/${FINDINGS}/g" \
112+
-e "s/__LCP_MS__/${LCP_MS}/g" \
113+
_site/index.html
114+
# nothing un-templated left behind
115+
if grep -q '__' _site/index.html; then
116+
echo "::error::unsubstituted placeholder left in index.html" && exit 1
117+
fi
118+
119+
- name: Upload Pages artifact
120+
uses: actions/upload-pages-artifact@v3
121+
with:
122+
path: _site
123+
124+
deploy:
125+
needs: build
126+
runs-on: ubuntu-latest
127+
environment:
128+
name: github-pages
129+
url: ${{ steps.deployment.outputs.page_url }}
130+
steps:
131+
- name: Deploy to GitHub Pages
132+
id: deployment
133+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
node_modules/
2+
.DS_Store
3+
*.log
4+
5+
# Runtime artifacts produced against a target web app (these live in the
6+
# target repo's own .bgn/, never in the plugin). Ignored here so a test run
7+
# inside this repo doesn't pollute it.
8+
.bgn/
9+
graph.json
10+
perf-map.html
11+
lcp-map.html

.mcp.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"mcpServers": {
3+
"chrome-devtools": {
4+
"command": "npx",
5+
"args": ["chrome-devtools-mcp@1.6.0"]
6+
}
7+
}
8+
}

AGENTS.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# AGENTS.md
2+
3+
Vendor-neutral instructions for coding agents working in this repo. Claude Code should read
4+
`CLAUDE.md` instead — it has the same facts plus Claude-specific wiring (skills, hooks, MCP setup).
5+
6+
## What this repo is
7+
8+
browsergnome is a **Claude Code plugin**, not an application. Most of its behavior is Markdown read at
9+
runtime by an LLM agent; the `.mjs` files are the deterministic, offline half.
10+
11+
- `skills/browsergnome/SKILL.md` — the orchestrator spec: menu, propose→measure→keep/revert loop, gate
12+
math, presets, `.bgn/config.json` defaults, reference index.
13+
- `skills/browsergnome/references/` — read-on-demand context: `tools.md`, `presets.md`,
14+
`measurement.md`, `perf-map.md`, `senior-audit.md`, `what-if.md`, `knowledge/`, and three stack-axis
15+
catalogs (`frameworks/`, `bundlers/`, `hosts/`).
16+
- `skills/browsergnome/scripts/*.mjs` — pure, offline, self-testing tools. No framework, no runtime
17+
browser dependency, no shared util module. Each script is standalone except one ESM import edge
18+
(`lcp_attribution.mjs` imports parser functions from `trace_metrics.mjs`/`bundle_stats.mjs`, guarded
19+
by a `pathToFileURL(process.argv[1])` check so importing doesn't also fire the CLI).
20+
- `commands/`, `hooks/hooks.json`, `.mcp.json`, `.claude-plugin/` — Claude Code plugin wiring; not
21+
relevant to a non-Claude agent working on the `.mjs` scripts or docs.
22+
- `templates/ci/` — GitHub Actions templates for running Autoresearch unattended in a *target* repo.
23+
24+
**Two-repo model.** At runtime the working directory is the *target* web repo being optimized, not
25+
this one. Scripts resolve against `$CLAUDE_PLUGIN_ROOT`, and `.bgn/` (config, ledger, perf-memory,
26+
playbook) is written into the **target** repo, never this one.
27+
28+
## Commands
29+
30+
No build step, no linter, no test framework beyond the scripts' own self-tests.
31+
32+
```bash
33+
npm install # @babel/parser + @babel/traverse, needed only by perf_scan.mjs
34+
npm test # all six self-tests, chained
35+
node skills/browsergnome/scripts/stats.mjs --self-test # a single self-test, directly
36+
```
37+
38+
CI (`.github/workflows/ci.yml`) runs `npm test` plus an offline smoke chain (`lcp_attribution.mjs`
39+
`build_lcp_map.mjs`, `build_run_report.mjs`) and `claude plugin validate . --strict`.
40+
41+
## Cross-file invariants
42+
43+
These have no single source of truth; an edit to one side silently breaks the other.
44+
45+
- **chrome-devtools-mcp pin** appears in `.mcp.json`, `references/tools.md`, and `doctor.mjs`'s
46+
self-test fixtures. Bump all three together; never use `@latest`.
47+
- **Stack-catalog filenames** under `references/frameworks|bundlers|hosts/` must match
48+
`doctor.mjs`'s detector return strings verbatim.
49+
- **All product-facing docs** (`README.md`, `docs/pages/`, `SKILL.md`, `references/`) read as one
50+
confident, unhedged voice — no validation-status narration, no dev-diary voice, no local-machine
51+
specifics. Facts stay accurate; nothing editorializes about whether something was confirmed to work.
52+
- **Perf Map calibration**`.github/workflows/pages.yml`'s `EXCALIDRAW_SHA` is pinned to the commit
53+
`references/perf-map.md`'s calibration note (527 modules / 16 hotspots) was measured against.
54+
55+
## Project rules
56+
57+
- **Don't claim something works that doesn't.** `/bisect` is not built — say so plainly, don't guess
58+
its behavior. State what's built and what isn't as plain fact, same unhedged voice as the docs.
59+
- **Never tune `perf_scan.mjs`'s `CONFIG` against a fixture** — it's circular by construction. Use a
60+
real cloned OSS app.
61+
- **Never use `lighthouse_audit` for the gate** — it excludes performance. Route measurement through
62+
`performance_start_trace`.
63+
- **Never `git add -A`** in the propose→measure→gate loop; stage only the touched paths.
64+
- **Knowledge-base status tiers** (`proven` / `documented` / `ungated hypothesis` / `dead end`) in
65+
`references/knowledge/` require a real measured before/after to upgrade. A citation is not a
66+
measurement.
67+
- Node ≥ 18, ESM throughout. The only runtime deps are `@babel/parser` + `@babel/traverse`.
68+
69+
## Full detail
70+
71+
`CLAUDE.md` in this same directory carries everything above plus the script pipeline diagram and
72+
Claude-Code-specific wiring (skills, hooks, MCP). Read it for anything this file doesn't cover.

0 commit comments

Comments
 (0)