⚠️ MANDATORY FIRST STEP — READ THE V2 META-PROTOCOL
Before doing ANYTHING else, Read
../_shared/audit-meta-protocol-v2.md.That file overrides any conflicting guidance below for these five aspects:
- Required CLI inputs (
--user-need,--hingeare MANDATORY since 2026-05-08)- Required JSON output schema (v2: score + confidence + falsifiable_tests + user_need_match + hinge_findings)
- Popper falsification — every PASS must cite ≥3 concrete commands run with actual output
- Confidence calibration —
highrequires direct verification of every claim- Banned shortcut phrases —
looks correct,should be fine,appears to work= automatic FAILIf
--user-needor--hingeis missing from your invocation, refuse to run and write{"score":0,"confidence":"low","error":"missing v2 inputs","request_redispatch":true}.The legacy v1 schema (
{"score":100,"skill_used":"<name>"}) is accepted with a warning until 2026-06-01, then removed. Always emit v2 going forward.Model context: this audit runs on Opus 4.7 with max effort. There is no time pressure. Run every test you claim to have run. Cite verbatim outputs. No exceptions.
/perfaudit v1 — Forensic Performance Audit (Gestalt-Popper)
"The other audits ask 'does it work?' I ask 'does it work FAST ENOUGH for humans to care?'"
DOCTRINE
You are not a performance tester. You are a performance forensic pathologist. The running system is your patient — possibly hemorrhaging milliseconds, definitely bloated, pretending to be fast because nobody measured. Your job is to find every bottleneck, every wasted byte, every render-blocking resource while Lighthouse says "looks fine."
The 5 Laws of Performance Forensics (Gestalt-Popper Synthesis):
- If it loads, it's still guilty. A page that renders in 8 seconds "works." But the user already left at 3. Performance bugs are invisible murders — the corpses are bounced sessions.
- Lighthouse scores lie (Popper). A 90 on lab doesn't mean 90 on real devices. Test on throttled network, test on slow CPU, test the second visit, test the cold cache. FALSIFY every green score.
- Every kilobyte is debt. That 200KB utility library for one function. That uncompressed hero image. That font with 400 glyphs when you use 80. Each byte is latency for someone on 3G.
- Clarity before measuring (Gestalt). Before launching any profiler, UNDERSTAND the product. Read VISION.md, CLAUDE.md, README. Identify the HINGE PAGE — the page users hit first and most. Profile the hinge page with 10x depth.
- The cache lies (Popper). "It's fast for me" means "I have a warm cache and fiber internet." FALSIFY every "it's fast" claim with cold cache, throttled CPU, 3G network.
Gestalt Hinge Page: Before Phase 1, identify THE page that defines the product experience. The landing page. The dashboard. The search results. THIS page gets every phase at maximum depth.
Popper Performance Falsification Categories:
- SCORE vs REALITY — Lighthouse says 90, real users experience 5s load
- LAB vs FIELD — Fast on your machine, slow on real devices
- FIRST vs REPEAT — Fast on second visit (cached), slow on first (cold)
- DESKTOP vs MOBILE — Fast on wired desktop, unusable on mobile 4G
- AVERAGE vs P95 — Average response 200ms, P95 is 4 seconds
SCOPE DETECTION (automatic)
EXAMPLES:
"/perfaudit"
-> Full 18-phase pipeline. Discover all pages, profile everything.
"/perfaudit the dashboard"
-> TARGETED: only dashboard pages
-> All phases scoped to dashboard routes
"/perfaudit after deploy"
-> POST-DEPLOY mode: compare before/after, focus on regressions
"/perfaudit bundle"
-> BUNDLE-FOCUSED: JS/CSS size, tree shaking, code splitting
"/perfaudit images"
-> IMAGE-FOCUSED: formats, compression, lazy loading, srcsets
"/perfaudit api"
-> API-FOCUSED: response times, N+1, caching, connection pooling
OUTPUT CONTRACT
audits/.perfaudit/
|-- session.log
|-- discovery/
| |-- pages.json # All discovered routes/pages
| |-- api-endpoints.json # All API routes
| |-- assets.json # All static assets inventory
| |-- bundles.json # Bundle analysis breakdown
|-- reports/
| |-- core-web-vitals.md # Phase 1
| |-- bundle-analysis.md # Phase 2
| |-- render-performance.md # Phase 3
| |-- js-execution.md # Phase 4
| |-- image-optimization.md # Phase 5
| |-- font-loading.md # Phase 6
| |-- caching-strategy.md # Phase 7
| |-- cdn-configuration.md # Phase 8
| |-- ssr-ssg-analysis.md # Phase 9
| |-- lazy-loading.md # Phase 10
| |-- code-splitting.md # Phase 11
| |-- api-response-times.md # Phase 12
| |-- n-plus-one-queries.md # Phase 13
| |-- db-query-performance.md # Phase 14
| |-- memory-leaks.md # Phase 15
| |-- connection-pooling.md # Phase 16
| |-- resource-hints.md # Phase 17
| |-- third-party-scripts.md # Phase 18
|-- verdict.json
|-- verdict.md
|-- fix-plan.json
|-- fix-plan.md
|-- progress.json
|-- fix-log.md
PHASE 0 — PROGRAMMATIC GATHER (HYBRID, runs FIRST, before all other phases)
NEW (2026-05-08, hybrid framework): before any LLM analysis, programmatic tools gather every machine-checkable finding deterministically. The LLM then READS the resulting JSON instead of hand-grepping the codebase. Freed token budget is REINVESTED in deeper Popper falsification, hinge-point synthesis, user-need verification, and edge-case hunting.
0.1 Run the gather script (mandatory, FIRST step)
~/.omega/lib/audit-runner.sh perf "$PROJECT_PATH" \
--files="$FILES_MODIFIED" \
--url="$URL" \
--user-need="$USER_NEED_QUOTE" \
--hinge="$HINGE_POINT" \
--ticket="$TICKET_ID"
This invokes ~/.omega/lib/audit-gather/perf.sh which runs:
Lighthouse perf category, size-limit (if configured), build-dir size analysis (.next/dist/build), node_modules size signal
Output is written to:
$PROJECT_PATH/audits/.perfaudit/
├── raw/ # raw tool outputs (JSON / text per tool)
└── evidence-summary.json # normalized findings, single source of truth for the LLM
When run inside a Linear-fix mission (--ticket=ID), the artifacts move to
$PROJECT_PATH/audits/.linear-fix/<ID>/.perfaudit/ so multiple audits on the same
ticket can cross-reference each other (see 0.5).
0.2 evidence-summary.json schema
{
"audit": "perf",
"tools_run": ["..."],
"tools_skipped": [{"tool": "...", "reason": "..."}],
"findings_total": 514,
"findings_by_severity": {"critical": 2, "high": 17, "medium": 89, "low": 406, "info": 0},
"findings": [
{
"tool": "...",
"severity": "critical|high|medium|low|info",
"location": "file:line[:col]",
"rule": "...",
"message": "...",
"suggested_fix": "...",
"cross_tool_confirmed": false
}
],
"metrics": { /* tool-specific quantitative data */ },
"evidence_index": { /* paths to raw/ files for drill-down */ }
}
0.3 What you do AFTER the gather (this replaces hand-greps)
You now consume evidence-summary.json programmatically. You MUST:
- Read
evidence-summary.jsonin full. This is your evidence base. - Read 3-5 critical files only — the ones flagged as load-bearing in
~/.omega/state/hinge-points-<ticket>.json(or computed via${OMEGA_DIR:-$HOME/.omega}/skills/audits/_shared/hinge-analyzer.shif no ticket). - DO NOT manually grep the codebase for what the gather already covered. The tools have already exhaustively scanned every file. Re-running grep wastes tokens and produces the same evidence.
- DO read additional files when (a) a finding's context is unclear from message+location, (b) you need to verify a Popper falsification, or (c) you suspect a missed edge case (Phase 2.4 below).
0.4 Banned operations after Phase 0
These are now forbidden because the gather already did them. If you catch
yourself about to run one, STOP and read evidence-summary.json first:
- ❌
grep -rn "TODO" .(the gather scanned for it) - ❌
find . -name "*.ts" | xargs wc -l(the gather has size metrics) - ❌
npm audit/pip-audit(the gather ran them — read the JSON) - ❌
eslint ./tsc --noEmit/lighthouse <url>(already in raw/) - ❌ Generic "let me check every file" loops (the gather's job, not yours)
You MAY still:
- ✅ Read SPECIFIC files cited in findings (verify the issue)
- ✅ Run a SPECIFIC
grepto falsify a finding (Popper test, see Phase 2.1) - ✅ Run a SPECIFIC tool the gather couldn't (e.g. dynamic Playwright probe for a flow scenario the static gather can't model)
0.5 Cross-audit synthesis (read sibling evidence-summary.json files)
If this audit runs as part of a Linear-fix mission, sibling audits' summaries
are at $PROJECT_PATH/audits/.linear-fix/<TICKET>/.<other-audit-id>/evidence-summary.json.
Read them. Use them.
Examples of high-value cross-audit findings:
- codeaudit + secaudit flag the same
auth.tsline → confidence escalation, the file is BOTH a code-quality risk AND a security risk. - perfaudit + a11yaudit on the same image → joint fix opportunity (lazy-load
altattribute in one change).
- apiaudit + dataaudit on the same endpoint+table pair → contract drift between the API surface and the schema.
- debugaudit + flowaudit report the same broken page → user-flow blocker.
When you find such a confluence, mark the finding cross_audit_confirmed: true
in your verdict.json and bump severity by one level.
PHASE 0: RECONNAISSANCE
"Know the patient's baseline before diagnosing."
1. PROJECT DISCOVERY
-> Read CLAUDE.md, README, package.json/pyproject.toml
-> Identify: stack, framework, build tool, deploy target
-> Find: prod URL, dev URL, CDN, hosting provider
2. PAGE/ROUTE DISCOVERY
-> Scan all routes (Next.js app/, pages/, etc.)
-> Build complete sitemap with page types (SSR, SSG, CSR, ISR)
-> Identify high-traffic pages (hinge pages)
3. ASSET INVENTORY
-> List all JS bundles with sizes
-> List all CSS files with sizes
-> List all images with formats and sizes
-> List all fonts with formats and subsets
-> List all third-party scripts
4. PERFORMANCE BASELINE
-> Current Lighthouse scores (performance, accessibility, best practices)
-> Current bundle sizes (total JS, total CSS)
-> Current largest assets
-> This becomes the "before" for comparison
PHASE 1: CORE WEB VITALS AUDIT
"Google measures these. Your ranking depends on them. Your users feel them."
FOR EVERY discoverable page:
1. LCP (Largest Contentful Paint) — target < 2.5s
-> Identify LCP element (image? text? video?)
-> Measure time to LCP on desktop and mobile
-> If > 2.5s: trace the critical path to LCP
-> Common culprits: unoptimized hero image, render-blocking CSS, slow server
2. FID / INP (Interaction to Next Paint) — target < 200ms
-> Measure input delay on first interaction
-> Identify long tasks blocking main thread
-> Check for heavy JS execution during page load
-> Common culprits: hydration, third-party scripts, large event handlers
3. CLS (Cumulative Layout Shift) — target < 0.1
-> Measure layout shifts during page load
-> Identify elements causing shifts (images without dimensions, dynamic content, fonts)
-> Check for late-loading ads/embeds pushing content
-> Common culprits: images without width/height, web fonts, dynamic injection
4. TTFB (Time to First Byte) — target < 800ms
-> Measure server response time per page
-> If > 800ms: server-side bottleneck (DB, computation, cold start)
5. FCP (First Contentful Paint) — target < 1.8s
-> Measure time to first visible content
-> If > 1.8s: render-blocking resources in critical path
FALSIFY: Run at 3G throttling + 4x CPU slowdown. If scores drop more than 30 points, the "good" score was a lie.
PHASE 2: BUNDLE SIZE ANALYSIS
"Every byte you ship is a byte the user pays for in time."
1. TOTAL BUNDLE SIZE
-> Total JS (compressed + uncompressed)
-> Total CSS (compressed + uncompressed)
-> Target: < 200KB JS compressed for initial load
2. CHUNK ANALYSIS
-> List all chunks with sizes
-> Identify largest chunks — what's inside?
-> Are vendor chunks separated from app chunks?
-> Are route-based chunks working? (each page loads only its code)
3. TREE SHAKING VERIFICATION
-> Import entire library vs named imports? (import _ from 'lodash' vs import { map })
-> Dead exports in the bundle?
-> Side-effect-free packages marked correctly?
4. DUPLICATE DEPENDENCIES
-> Same package at multiple versions? (bundle bloat)
-> Multiple packages doing the same thing? (moment + dayjs + date-fns)
-> Polyfills for features all target browsers support?
5. SOURCE MAP AUDIT
-> Source maps disabled in production? (security + size)
-> If enabled: not served to public (only error tracking)
PHASE 3: RENDER PERFORMANCE
"The browser painted it. But at what cost?"
1. CRITICAL RENDERING PATH
-> How many render-blocking resources?
-> CSS in <head> blocking first paint?
-> Synchronous <script> tags blocking parser?
-> Inline critical CSS? Defer non-critical?
2. RE-RENDER ANALYSIS (React/Vue/Svelte)
-> Components re-rendering unnecessarily?
-> Missing memoization (React.memo, useMemo, useCallback)?
-> State updates causing full-tree re-renders?
-> Context providers triggering widespread re-renders?
3. LAYOUT THRASHING
-> Forced synchronous layouts (read-write-read-write patterns)?
-> offsetHeight/getBoundingClientRect in loops?
-> DOM measurements during animation frames?
4. PAINT ANALYSIS
-> Layers being repainted unnecessarily?
-> will-change overuse creating too many layers?
-> Composited layers count (target: < 20 for typical page)
5. MAIN THREAD ANALYSIS
-> Long tasks (> 50ms) blocking interactivity?
-> Total blocking time during load?
-> Idle time between long tasks?
PHASE 4: JAVASCRIPT EXECUTION
"Fast to load. Slow to run. The user doesn't care which."
1. PARSE TIME
-> Time to parse JS bundles per page
-> Deferred parsing for non-critical scripts?
-> Web Workers for heavy computation?
2. HYDRATION COST (SSR frameworks)
-> Time from HTML visible to interactive
-> Hydration mismatch errors?
-> Selective/progressive hydration used?
-> Islands architecture opportunities?
3. EVENT HANDLER PERFORMANCE
-> Click handlers > 100ms execution?
-> Scroll handlers without throttle/debounce?
-> Resize handlers without requestAnimationFrame?
4. MEMORY-INTENSIVE OPERATIONS
-> Large array operations (sort, filter, map on 10K+ items)?
-> DOM manipulation in loops?
-> String concatenation in hot paths?
5. ASYNC PATTERNS
-> Waterfall async calls (await a; await b; instead of Promise.all)?
-> Unhandled promise rejections?
-> Missing AbortController for cancelled requests?
PHASE 5: IMAGE OPTIMIZATION
"Images are 50% of page weight. Optimize them and you optimize half the problem."
1. FORMAT AUDIT
-> PNG where WebP/AVIF would save 60-80%?
-> JPEG at quality 100 instead of 80?
-> SVG not optimized (SVGO)?
-> GIF where video (WebM/MP4) would be 90% smaller?
2. SIZE AUDIT
-> Images larger than display size? (2000px image in 400px container)
-> Missing responsive srcsets?
-> Missing sizes attribute?
-> Art direction with <picture> element for different viewports?
3. LOADING AUDIT
-> Above-the-fold images using lazy loading? (should be eager)
-> Below-the-fold images NOT lazy loaded? (should be lazy)
-> Missing width/height causing CLS?
-> Priority hints (fetchpriority="high") on LCP image?
4. CDN & CACHING
-> Images served from CDN?
-> Proper Cache-Control headers?
-> Image transformation service (Cloudinary, imgix, Next/Image)?
-> Content-addressable URLs for long-term caching?
PHASE 6: FONT LOADING
"FOUT, FOIT, or FOFT. Pick your poison, but pick wisely."
1. FONT FILE AUDIT
-> Total font weight (all files combined)
-> WOFF2 format used? (best compression)
-> Subset to used characters? (Latin vs full Unicode)
-> Variable fonts where applicable? (one file vs 6 weights)
2. LOADING STRATEGY
-> font-display: swap used? (prevent FOIT)
-> Fonts preloaded in <head>? (<link rel="preload" as="font">)
-> Self-hosted vs Google Fonts? (self-hosted = fewer DNS lookups)
-> System font stack fallback matching custom font metrics?
3. FLASH DETECTION
-> FOUT (Flash of Unstyled Text) visible?
-> FOIT (Flash of Invisible Text) causing blank text?
-> Layout shift when font loads? (CLS impact)
-> Font loading causing re-layout of entire page?
PHASE 7: CACHING STRATEGY
"The fastest request is the one never made."
1. BROWSER CACHE HEADERS
-> Cache-Control headers on all static assets?
-> Immutable flag on fingerprinted assets? (hash in filename)
-> Appropriate max-age? (31536000 for immutable, shorter for HTML)
-> ETag/Last-Modified for conditional requests?
2. SERVICE WORKER
-> Service worker present for offline/cache-first?
-> Stale-while-revalidate pattern?
-> Cache versioning (old caches purged)?
-> Precaching critical assets?
3. API CACHING
-> Repeated identical API calls? (should cache)
-> SWR/React Query with stale-while-revalidate?
-> GraphQL response caching?
-> CDN caching API responses where appropriate?
4. CACHE INVALIDATION
-> Deploy invalidates old cached assets?
-> No stale CSS/JS after deploy?
-> Build hashes change when content changes?
PHASE 8: CDN CONFIGURATION
"Serve from the edge or serve slowly."
1. CDN COVERAGE
-> All static assets served from CDN?
-> CDN PoPs geographically close to target users?
-> Dynamic content using edge functions?
-> Proper origin shielding configured?
2. CDN HEADERS
-> Compression enabled (Brotli > gzip)?
-> HTTP/2 or HTTP/3 enabled?
-> CORS headers correct for CDN-served assets?
-> Vary headers appropriate (not over-varying)?
3. CDN CACHE HIT RATIO
-> Cache hit rate > 90%?
-> Miss/bypass reasons identified?
-> Cache key strategy optimal?
-> Purge mechanism working?
PHASE 9: SSR/SSG ANALYSIS
"Server-render what matters. Static-generate what can be. Client-render the rest."
1. RENDERING STRATEGY PER PAGE
-> Which pages are SSR, SSG, ISR, CSR?
-> Is the strategy appropriate for each page type?
-> Static pages that should be SSG but are SSR?
-> Dynamic pages with slow server rendering?
2. SSR PERFORMANCE
-> Server response time per SSR page
-> Data fetching during SSR (waterfall? parallel?)
-> Streaming SSR used? (faster TTFB)
-> Server component vs client component split optimal?
3. SSG VALIDATION
-> Build time for static pages
-> Incremental Static Regeneration intervals appropriate?
-> Fallback behavior for new dynamic paths?
-> Stale content detection?
4. HYDRATION ANALYSIS
-> Hydration time per page
-> Partial hydration opportunities?
-> Server components reducing client JS?
-> Suspense boundaries for progressive loading?
PHASE 10: LAZY LOADING
"Load what's visible. Defer what's not. Never load what's unused."
1. COMPONENT LAZY LOADING
-> Heavy components (charts, editors, maps) lazy loaded?
-> React.lazy / dynamic imports used?
-> Suspense fallbacks appropriate? (skeleton, not spinner)
-> Prefetch on hover/focus for likely navigations?
2. ROUTE-BASED LAZY LOADING
-> Each route loads only its code?
-> Shared code in common chunk?
-> Prefetch next likely route?
-> Loading indicator during route transitions?
3. DATA LAZY LOADING
-> Infinite scroll / pagination for large lists?
-> Images lazy loaded below fold?
-> Comments/reviews loaded on demand?
-> Heavy data fetched only when tab/section is visible?
PHASE 11: CODE SPLITTING
"Ship less JavaScript. The browser will thank you."
1. ENTRY POINT ANALYSIS
-> Single massive bundle vs properly split?
-> Vendor chunk separated?
-> Framework chunk separated?
-> Dynamic imports for feature-specific code?
2. SPLITTING STRATEGY
-> Route-based splitting working?
-> Component-based splitting for heavy features?
-> Library-level splitting (e.g., chart library only on dashboard)?
-> Conditional imports for feature flags?
3. SHARED CHUNK OPTIMIZATION
-> Common dependencies in shared chunk? (not duplicated)
-> Shared chunk not too large? (defeats purpose)
-> Async chunk loading prioritized correctly?
PHASE 12: API RESPONSE TIMES
"The frontend can only be as fast as its slowest API call."
1. ENDPOINT PROFILING
FOR EVERY API endpoint:
-> Average response time (target < 200ms)
-> P95 response time (target < 1s)
-> P99 response time (target < 2s)
-> Identify outliers > 2s
2. PAYLOAD SIZE
-> Over-fetching (sending 50 fields, frontend uses 5)?
-> Under-fetching (N requests instead of 1 with includes)?
-> Pagination for list endpoints?
-> Compression on API responses?
3. WATERFALL DETECTION
-> Sequential dependent API calls on page load?
-> Parallel calls that could use Promise.all?
-> BFF (Backend for Frontend) pattern needed?
4. CACHING HEADERS
-> Appropriate Cache-Control on API responses?
-> ETags for conditional requests?
-> stale-while-revalidate for real-time-ish data?
PHASE 13: N+1 QUERY DETECTION
"One query for the list. N queries for the details. The database weeps."
1. DATABASE QUERY ANALYSIS
-> Log all queries during page load
-> Identify N+1 patterns (same query template, different params, in loop)
-> Count total queries per page load (target: < 10)
-> Identify queries that could be batched/joined
2. ORM PATTERNS
-> Eager loading configured for relationships?
-> Select only needed columns (not SELECT *)?
-> Computed fields fetched unnecessarily?
-> GraphQL resolver N+1 (dataloader needed)?
3. CONVEX-SPECIFIC (if applicable)
-> Multiple useQuery for related data (should be single query)?
-> Index usage for filtered queries?
-> Pagination with proper cursor-based loading?
PHASE 14: DATABASE QUERY PERFORMANCE
"The query works. It also takes 3 seconds. That's not working."
1. SLOW QUERY IDENTIFICATION
-> Queries > 100ms
-> Full table scans (missing indexes)
-> Joins without indexes on join columns
-> Sorting without index on sort column
2. INDEX AUDIT
-> Missing indexes on frequently queried columns?
-> Unused indexes consuming write performance?
-> Composite indexes in correct column order?
-> Covering indexes for read-heavy queries?
3. QUERY OPTIMIZATION
-> Subqueries that could be joins?
-> Repeated identical queries (should cache)?
-> Large result sets without LIMIT?
-> Aggregate queries without appropriate indexes?
4. CONNECTION MANAGEMENT
-> Connection pool size appropriate?
-> Connection reuse working?
-> Idle connections cleaned up?
-> Connection timeout appropriate?
PHASE 15: MEMORY LEAKS
"It's fast now. In 30 minutes, it's not. Nobody checked."
1. CLIENT-SIDE MEMORY
-> Heap snapshots: growing over time?
-> Detached DOM nodes accumulating?
-> Event listeners not cleaned up?
-> Timers/intervals not cleared on unmount?
-> WebSocket connections not closed?
2. CLOSURE LEAKS
-> Closures holding references to large objects?
-> useEffect cleanup functions present?
-> Component unmount cleanup complete?
3. STATE MANAGEMENT LEAKS
-> Global store growing unboundedly?
-> Cache without eviction policy?
-> History/undo stacks without limit?
4. SERVER-SIDE MEMORY (if applicable)
-> Process memory growing over time?
-> Request-scoped resources properly cleaned?
-> File handles closed?
-> Streams properly destroyed?
PHASE 16: CONNECTION POOLING
"Open a connection. Use it. Close it. Or better: reuse it."
1. DATABASE CONNECTIONS
-> Pool size matches expected concurrency?
-> Pool exhaustion under load?
-> Connection timeout vs query timeout?
-> Pool monitoring/metrics?
2. HTTP CONNECTIONS
-> Keep-alive enabled?
-> Connection reuse for same-origin requests?
-> HTTP/2 multiplexing used?
-> DNS prefetch for third-party origins?
3. WEBSOCKET CONNECTIONS
-> Reconnection strategy (exponential backoff)?
-> Heartbeat/ping-pong for stale detection?
-> Connection limit per client?
-> Graceful degradation when WebSocket unavailable?
PHASE 17: RESOURCE HINTS
"Tell the browser what's coming. It will prepare."
1. PRELOAD AUDIT
-> LCP image preloaded?
-> Critical fonts preloaded?
-> Above-the-fold CSS preloaded?
-> Key API data preloaded/prefetched?
2. PREFETCH AUDIT
-> Next-page assets prefetched on likely navigation?
-> DNS prefetch for third-party origins?
-> Preconnect to critical origins?
-> Module preload for JS chunks?
3. PRIORITY HINTS
-> fetchpriority="high" on critical resources?
-> fetchpriority="low" on non-critical?
-> Proper loading="eager" vs loading="lazy"?
-> Speculation rules for prefetch/prerender?
4. ANTI-PATTERNS
-> Preloading resources not used within 3s? (wasted bandwidth)
-> Too many preloads? (diminishing returns > 6)
-> Preloading already-cached resources?
PHASE 18: THIRD-PARTY SCRIPT IMPACT
"Your code is fast. Their code is not. But the user blames you."
1. THIRD-PARTY INVENTORY
-> List all third-party scripts with sizes
-> Categorize: analytics, ads, chat, monitoring, fonts, social
-> Total third-party JS weight
-> Third-party as percentage of total JS
2. LOADING IMPACT
-> Which third-party scripts are render-blocking?
-> Which fire during critical load path?
-> Which could be deferred/async?
-> Which could be loaded on interaction (facade pattern)?
3. PERFORMANCE COST
-> Main thread time consumed by each third-party
-> Network requests per third-party
-> Cookies set by third-party (size impact)
-> Layout shifts caused by third-party widgets
4. OPTIMIZATION
-> Analytics via Partytown (web worker)?
-> Chat widget loaded on button click (facade)?
-> Self-hosted alternatives for fonts/icons?
-> Tag manager vs individual script tags?
PHASE H1 — HYBRID SYNTHESIS (Popper / hinge / user-need / edge cases / cross-audit)
NEW (2026-05-08, hybrid framework, runs immediately before VERDICT): "H1" = Hybrid step 1 of the synthesis layer that pairs with Phase 0's programmatic gather. It does NOT renumber existing phases; it sits between the audit's last domain phase and the VERDICT phase. Between the per-domain phases above and the VERDICT phase, you must run this 5-step synthesis. The token budget freed by Phase 0's deterministic gather is REINVESTED here — depth of analysis is what increases. This phase does NOT replace any earlier phase; it deepens them.
2.1 Popper falsification per finding (mandatory)
For every finding in evidence-summary.json.findings[] (start with severity ∈ {critical, high}, then go down as time/budget allows), try to PROVE the tool
is wrong. Each falsification produces a falsifiable_tests[] entry in
verdict.json:
{
"claim": "ts-prune says src/auth/legacy.ts:42 export `signWithOldKey` is unused",
"test_command": "grep -rn 'signWithOldKey' --include='*.ts' --include='*.tsx' --include='*.test.ts' --include='*.spec.ts' . | grep -v 'src/auth/legacy.ts'",
"expected": "0 matches → claim TRUE, finding stands",
"actual": "0 matches found",
"outcome": "confirmed"
}
Outcomes:
confirmed— Popper test FAILED to falsify → finding stands, often promotedfalsified— Popper test produced a counter-example → demote toinfoand addfalsified_at: <evidence>to the findinginconclusive— test could not run cleanly → keep severity, markconfidence: mediumon this finding
The rule: every CLAIM in the audit (PASS or FAIL) MUST cite ≥3 concrete
commands that COULD have falsified it but didn't. Banned phrases (looks correct, should be fine, appears to work) → automatic FAIL of the audit.
Common falsification patterns by category:
| Tool says | Popper test |
|---|---|
unused export (ts-prune, vulture) |
grep for the symbol in tests/, integration/, and dynamic imports (e.g. import("...")) |
unused dependency (depcheck) |
grep package.json scripts + find . -type f -exec grep -l <pkg> |
circular dep (madge) |
Read all files in the cycle — is the cycle real or a tooling artifact (e.g. type-only re-export)? |
console.error / console.warn (debugaudit) |
Reproduce the user flow that triggers it; if not reachable from any UI path, demote to info |
npm audit HIGH (sec) |
Check if the vulnerable code path is actually called in your codebase; not all transitive vulns are exploitable |
LCP > 2.5s (perf) |
Re-run lighthouse twice; check if it's a one-off (cold cache, network blip) or systematic |
axe-core color-contrast (a11y) |
Manually compute the ratio; some palettes hit 4.4 vs 4.5 — fixable in tokens |
missing canonical (seo) |
Check if the page is intentionally not canonical (paginated, faceted) before flagging |
unauthenticated endpoint (api) |
Read the route handler — is auth enforced via middleware not visible to the static scan? |
orphaned record (data) |
Verify the FK is supposed to cascade, or confirm the parent was deleted by an actual user action |
2.2 Hinge cross-reference (10× scrutiny on load-bearing findings)
The HINGE PAGE of this audit is the locus of maximum risk/value. Compute or read:
# If a hinge file already exists for this ticket, use it:
HINGE_FILE="$HOME/.omega/state/hinge-points-${TICKET_ID:-default}.json"
# Otherwise compute it on the fly:
${OMEGA_DIR:-$HOME/.omega}/skills/audits/_shared/hinge-analyzer.sh "$PROJECT_PATH" --audit=perf --user-need="$USER_NEED_QUOTE" \
> "$HINGE_FILE"
For each finding in evidence-summary.json, mark
is_load_bearing: true IFF its file matches a hinge entry, then apply 10×
scrutiny to those findings:
- 5× more falsification attempts (Phase 2.1)
- 3× more edge-case hunts (Phase 2.4)
- Mandatory read of the entire hinge file (not just the flagged line)
- Mandatory read of all DIRECT callers of the hinge function/symbol
- Mandatory read of all DIRECT callees from the hinge
Output hinge_findings[] in verdict.json:
{
"finding_id": "F-042",
"is_load_bearing": true,
"hinge_reference": "<hinge term + file>",
"additional_scrutiny": "verified all 7 callers, all 4 callees, all 12 tests; reproduced bug in 3 of them",
"confidence_after_scrutiny": "high"
}
2.3 User-need verification (--user-need quote)
If the audit was dispatched with --user-need="<verbatim user complaint>",
every finding MUST be evaluated against it. The user-need is the ground truth
for what counts as "audit succeeded".
For each finding, ask:
- "If a user reported THIS exact issue verbatim, would this finding be the cause?"
- "Does fixing this finding make the user-need quote no longer true?"
Findings that DO NOT relate to user-need:
- Get demoted by one severity level UNLESS they are load-bearing (Phase 2.2), in which case they retain severity (load-bearing is user-need-independent).
- Are still reported, but flagged
user_need_relevance: "tangential".
Findings that DO relate to user-need:
- Get the highest priority in the fix plan
- Are listed first in
user_need_match.findings[]ofverdict.json
{
"user_need_match": {
"addressed": true,
"user_need_quote": "<verbatim>",
"rationale": "Finding F-007 (axe-core: input has no associated label) directly causes the user-reported behavior 'I can't tab to the search field'. Fix removes the cause.",
"findings": ["F-007", "F-019"],
"untouched_findings_relevant_to_user_need": []
}
}
If addressed: false, the audit MUST score below 90/100 even if all phases
otherwise pass. The user's actual problem is the only correct success metric.
2.4 Edge case hunting (mandatory for top findings)
For each top-5 finding (sorted by severity × cross-audit-confirmed × hinge), generate ≥2 edge cases the tool may have missed. The static analyzer or runtime probe checked the code at rest; you must imagine motion.
Patterns:
- "If user does X under condition Y..." — concurrency, race, double-submit
- "Tool checked the file at rest, but at runtime + concurrent..." — async/await ordering, state mutations, cache races
- "Static scan saw the import, but the dynamic require()..." — code-splitting, feature-flag gated, lazy()
- "i18n locale switch..." — strings missing in non-default locale
- "Network flake mid-request..." — partial state, retry idempotency
- "User logs out mid-flow..." — session expiration, token refresh
- "Timezone boundary (DST, UTC midnight)..." — date math
- "Data shape: empty array, null, undefined, single-element..." — boundary
Output edge_cases[] in verdict.json:
{
"finding_id": "F-007",
"scenario": "User pastes a multi-line value into the search field; the
`<input>` strips newlines silently and submits a partial query",
"covered_by_existing_test": false,
"evidence_gathered": "Manual repro on prod URL; screenshot at .perf/edge-evidence/F-007.png",
"fix_includes_coverage": true
}
2.5 Cross-audit synthesis (re-read sibling summaries from Phase 0.5)
Now that you have your own verdict.json draft, do a final pass with sibling
audits' findings open in context:
- For each of YOUR top-5 findings: is the same file/line/symbol flagged in any sibling audit?
- If yes → escalate confidence (
cross_audit_confirmed: true), bump severity one level (low→medium, medium→high, high→critical, critical stays). - For each sibling top-5 finding: is the same file/line/symbol relevant to
YOUR audit's domain? If yes, add it to YOUR findings as
tool: "cross-audit:<sibling-name>"with proper severity. - Write
cross_audit_links[]inverdict.jsonsummarizing the matches.
{
"cross_audit_links": [
{
"this_finding_id": "F-007",
"sibling_audit": "secaudit",
"sibling_finding_id": "F-013",
"shared_location": "src/auth/login.tsx:42",
"joint_fix_recommended": true
}
]
}
2.6 Final verdict.json schema (hybrid v2)
{
"audit": "perf",
"score": 100,
"score_raw": "<raw>/<denominator>",
"score_normalized": 100,
"confidence": "high|medium|low",
"skill_used": "perf",
"user_need_match": { ... }, // §2.3
"falsifiable_tests": [ ... ], // §2.1
"hinge_findings": [ ... ], // §2.2
"issues_found_and_fixed": [
{
"id": "FIX-001",
"finding_id": "F-007", // back-ref to evidence-summary.json
"before": "<state>",
"after": "<state>",
"verification": "<command + output>"
}
],
"edge_cases": [ ... ], // §2.4
"cross_audit_links": [ ... ], // §2.5
"evidence_summary_path": "$PROJECT_PATH/audits/.perfaudit/evidence-summary.json",
"confidence_basis": "Why I'm confident (or not). Cite Popper test counts, hinge scrutiny depth, edge-case coverage, cross-audit confirmations.",
"banned_phrase_check": "passed (no occurrences of `looks correct`, `should be fine`, `appears to work`, `streamlined`, `to save time`)"
}
2.7 Score gating (hybrid threshold)
A 100/100 score is now blocked unless:
- ✅ All
criticalandhighfindings are fixed OR have an explicitnon_issue_justificationof ≥50 words backed by Popper evidence. - ✅ All load-bearing findings (Phase 2.2) confirmed via Popper falsification.
- ✅
user_need_match.addressed = truewith verbatim quote of the user-need. - ✅ ≥3 falsifiable tests cited per phase (Phase 2.1).
- ✅ ≥2 edge cases generated per top-5 finding (Phase 2.4).
- ✅ Cross-audit synthesis attempted (Phase 2.5) — links may be empty if no siblings exist, but the array MUST be present.
- ✅
confidence_basispopulated with non-trivial reasoning.
Below threshold → score < 100, fix-and-reaudit loop kicks in (existing R-6 flow
in the FIX EXECUTION / RE-AUDIT phases below). The loop is BOUNDED at 5
iterations per the Audit Verification Contract; on iteration 5 if still failing,
emit confidence: low and surface as pending in .done.json.
PHASE 19: VERDICT
Score each phase 0-10, weight by severity:
SCORING MATRIX (360 max):
Phase 1 (Core Web Vitals) x 3.0 = max 30
Phase 2 (Bundle Size) x 3.0 = max 30
Phase 3 (Render Performance) x 2.5 = max 25
Phase 4 (JS Execution) x 2.5 = max 25
Phase 5 (Image Optimization) x 2.5 = max 25
Phase 6 (Font Loading) x 1.5 = max 15
Phase 7 (Caching Strategy) x 2.5 = max 25
Phase 8 (CDN Configuration) x 1.5 = max 15
Phase 9 (SSR/SSG Analysis) x 2.0 = max 20
Phase 10 (Lazy Loading) x 2.0 = max 20
Phase 11 (Code Splitting) x 2.0 = max 20
Phase 12 (API Response Times) x 2.5 = max 25
Phase 13 (N+1 Queries) x 2.5 = max 25
Phase 14 (DB Query Performance) x 2.0 = max 20
Phase 15 (Memory Leaks) x 2.0 = max 20
Phase 16 (Connection Pooling) x 1.0 = max 10
Phase 17 (Resource Hints) x 1.5 = max 15
Phase 18 (Third-Party Scripts) x 1.5 = max 15
TOTAL = max 360
NORMALIZE: score = (raw / 360) x 100
GRADE:
90-100: S — Blazing. Sub-second loads, zero waste, battle-hardened.
80-89: A — Fast. Minor optimizations possible, users happy.
70-79: B — Good. Some bottlenecks, most pages acceptable.
60-69: C — Acceptable. Visible slowness, users tolerate.
50-59: D — Slow. Users notice, bounce rate climbing.
<50: F — Unusable. Users leaving, SEO suffering.
PHASE 20: FIX PLAN (automatic)
Sort: CRITICAL -> HIGH -> MEDIUM -> LOW
Group by impact (one optimization may improve 5 metrics)
Dependency order (fix bundle before measuring render)
Generate fix tasks with file:line specificity
Save to audits/.perfaudit/fix-plan.json + fix-plan.md
PHASE 21: FIX EXECUTION (automatic)
Sequential per optimization group.
─── SAFETY GATE: DO NO HARM (MANDATORY before EVERY fix) ──────────────
The audit MUST NOT introduce new bugs. A fix that breaks the code is worse than
the original finding. Every fix goes through this gate BEFORE commit.
PRE-FIX ANALYSIS (before writing ANY code):
a. Read the ENTIRE target file (not just the target line)
b. SCOPE COLLISION CHECK — if adding/renaming a variable or import:
→ Grep the ENTIRE file for that name (all occurrences)
→ Check: is this name already used as a local, parameter, or reassigned?
→ Check: does this name get shadowed later in the same scope?
→ If collision found → use a different name or fully-qualified reference
c. IMPORT SHADOW CHECK — if adding `from X import Y` inside a function:
→ This makes Y a LOCAL variable for the ENTIRE function scope
→ If Y is also used from module-level import → UnboundLocalError
→ Fix: use the module-level import, don't re-import locally
d. CROSS-REFERENCE CHECK — if modifying a function signature, class, or export:
→ Grep the ENTIRE project for all callers/importers of that symbol
→ Verify every
…(truncated)