From 30aa8043c98e2e118e7b5fa2141c41a89daf67f8 Mon Sep 17 00:00:00 2001 From: mrrajan <86094767+mrrajan@users.noreply.github.com.> Date: Thu, 16 Apr 2026 08:51:56 +0530 Subject: [PATCH 01/23] feat(performance): add performance-setup skill and configuration template - Create performance-setup skill with route/module discovery - Create generic performance-config.template.md with placeholders - Skill discovers routes from router configuration files - Skill discovers modules from lazy-loaded routes and build config - Template provides guidance for scenarios and module registry - Implements idempotent setup with update/skip options Implements TC-4130 Co-Authored-By: Claude Sonnet 4.5 Assisted-by: Claude Code --- .../skills/performance-setup/SKILL.md | 198 ++++++++++++++++++ .../performance-config.template.md | 70 +++++++ 2 files changed, 268 insertions(+) create mode 100644 plugins/sdlc-workflow/skills/performance-setup/SKILL.md create mode 100644 plugins/sdlc-workflow/skills/performance/performance-config.template.md diff --git a/plugins/sdlc-workflow/skills/performance-setup/SKILL.md b/plugins/sdlc-workflow/skills/performance-setup/SKILL.md new file mode 100644 index 000000000..f7fe4e0b8 --- /dev/null +++ b/plugins/sdlc-workflow/skills/performance-setup/SKILL.md @@ -0,0 +1,198 @@ +--- +name: performance-setup +description: | + Initialize Performance Analysis Configuration in a target repository by discovering routes and modules from the codebase. +argument-hint: "[target-repository-path]" +--- + +# performance-setup skill + +You are an AI performance setup assistant. You analyze a frontend application's codebase to discover user flows and module structure, then generate a Performance Analysis Configuration file that enables performance optimization workflows. + +## Guardrails + +- This skill creates ONE file: `.claude/performance-config.md` in the target repository +- This skill is **idempotent** — running it multiple times on an already-configured repository offers to update or skip +- This skill does NOT modify source code — only creates/updates the performance configuration file + +## Step 1 – Determine Target Repository + +If the user provided a repository path as an argument, use that as the target. Otherwise, use the current working directory. + +Verify the target directory exists and contains a frontend application (check for `package.json`, `src/`, or similar frontend indicators). + +## Step 2 – Detect Existing Configuration + +Check if `.claude/performance-config.md` already exists in the target repository. + +- **If exists:** Read the file and inform the user: + > "Performance Analysis Configuration already exists. Would you like to update it or skip setup?" + > + > Options: + > 1. Update - Re-discover routes/modules and merge with existing config + > 2. Skip - Keep existing configuration unchanged + > + > Choose (1/2): + + If user chooses "2. Skip", stop execution and inform them the existing config will be used. + +- **If not exists:** Proceed to Step 3. + +## Step 3 – Discover Routes and User Flows + +Analyze the frontend codebase to discover routes using Serena (if available) or Read/Grep/Glob. + +### Step 3.1 – Find Router Configuration + +Common router configuration file patterns: +- React Router: `src/routes.tsx`, `src/router/index.ts`, `src/App.tsx` (with `` components) +- Vue Router: `src/router/index.ts`, `src/router/routes.ts` +- Angular: `src/app-routing.module.ts`, `src/app/app-routing.module.ts` +- Next.js: `pages/` or `app/` directory structure (file-based routing) + +Use Glob to find likely router files: +``` +**/*routes*.{ts,tsx,js,jsx} +**/*router*.{ts,tsx,js,jsx} +**/App.{ts,tsx,js,jsx} +``` + +### Step 3.2 – Extract Route Definitions + +For each router configuration file found: + +**If Serena is available:** +- Use `get_symbols_overview` to list route definitions +- Use `find_symbol` with `include_body=true` to read route arrays or objects + +**If Serena is not available:** +- Use Read tool to examine router files +- Use Grep to search for route path patterns: + ``` + path:\s*['"]([^'"]+)['"] + "Performance setup complete! Next steps:" +> +> "1. Start your application locally (e.g., `npm run dev`)" +> "2. Run the baseline capture:" +> ``` +> /sdlc-workflow:performance-baseline +> ``` + +## Important Rules + +- Never modify source code — only create the `.claude/performance-config.md` file +- Always use actual discovered routes and modules — do not invent placeholder examples +- If route discovery finds no results, ask the user to provide route paths manually +- If module discovery finds no results, skip the module registry section (it's optional) +- Validate that discovered routes reference real components/files before including them +- Ensure all URL paths in scenarios are relative (no `http://` or `https://` — they should work with localhost) diff --git a/plugins/sdlc-workflow/skills/performance/performance-config.template.md b/plugins/sdlc-workflow/skills/performance/performance-config.template.md new file mode 100644 index 000000000..0a186dac5 --- /dev/null +++ b/plugins/sdlc-workflow/skills/performance/performance-config.template.md @@ -0,0 +1,70 @@ +# Performance Analysis Configuration + +This configuration defines performance scenarios, baseline capture settings, and optimization targets for the sdlc-workflow performance optimization workflow. + +## Performance Scenarios + +List the key user workflows and pages to measure. Each scenario should represent a distinct user journey. + +**Discovery guidance:** Use router configuration files (e.g., `routes.tsx`, `router/index.ts`, `App.tsx`) to identify primary routes. Focus on high-traffic pages and critical user flows. + +| Scenario Name | URL Path | Description | +|---|---|---| +| {{scenario-1-name}} | {{/path}} | {{Brief description of user journey}} | +| {{scenario-2-name}} | {{/path}} | {{Brief description of user journey}} | + +**Example scenarios to consider:** +- Home/landing page load (cold cache) +- List views with pagination/filtering +- Detail/item views with related data +- Search/query interfaces +- Form-heavy workflows +- Dashboard/summary pages + +## Baseline Capture Settings + +| Setting | Value | Description | +|---|---|---| +| Iterations | 5 | Number of times to run each scenario | +| Warmup Runs | 2 | Iterations to discard before collecting metrics | +| Metrics to Collect | LCP, FCP, TTI, Total Load Time, Resource Timing | Core Web Vitals and resource-level metrics | +| Browser | Chromium (headless) | Playwright browser for automation | + +## Target Directories + +| Directory | Purpose | +|---|---| +| `.claude/performance/baselines/` | Baseline performance reports | +| `.claude/performance/analysis/` | Module and application analysis reports | +| `.claude/performance/plans/` | Optimization plan documents | +| `.claude/performance/verification/` | Verification reports for optimization PRs | + +## Optimization Targets + +Core Web Vitals thresholds to achieve after optimization. + +| Metric | Current (Baseline) | Target | Unit | +|---|---|---|---| +| LCP (Largest Contentful Paint) | TBD | 2.5 | seconds | +| FCP (First Contentful Paint) | TBD | 1.8 | seconds | +| TTI (Time to Interactive) | TBD | 3.5 | seconds | +| Total Load Time | TBD | 4.0 | seconds | + +**Note:** Current (Baseline) values will be filled in after running `performance-baseline` skill. Target values follow Google's Core Web Vitals "Good" thresholds but can be adjusted based on application requirements. + +## Module Registry + +Frontend modules/pages to analyze individually. Each entry represents a distinct bundle or route that can be optimized separately. + +**Discovery guidance:** Identify lazy-loaded routes, code-split chunks, or major feature modules from build configuration (Webpack/Vite) or dynamic imports in route definitions. + +| Module Name | Entry Point | Description | +|---|---|---| +| {{module-1}} | {{src/path/to/entry.tsx}} | {{Module purpose}} | +| {{module-2}} | {{src/path/to/entry.tsx}} | {{Module purpose}} | + +**Example modules to consider:** +- Route-level code splits (each lazy-loaded page) +- Feature modules with distinct bundles +- Heavy UI libraries (e.g., chart/visualization components) +- Third-party integrations with separate chunks From d61cb16b832983129f80af6bdc173d74de12161a Mon Sep 17 00:00:00 2001 From: mrrajan <86094767+mrrajan@users.noreply.github.com.> Date: Thu, 16 Apr 2026 09:22:30 +0530 Subject: [PATCH 02/23] feat(performance): add shared templates and security-hardened baseline capture script - Create 6 performance workflow templates (baseline, module analysis, optimization task/plan, verification) - Implement Playwright-based capture-baseline.template.mjs with comprehensive security hardening - Security fixes: path traversal prevention, prototype pollution mitigation, token leakage prevention - Localhost validation (127.0.0.0/8, ::1, blocks 0.0.0.0 and IPv6-mapped) - Port validation (1-65535), iteration bounds (max 50/10), URL path validation - Correct LCP metric using largest-contentful-paint entries - Robust config table parsing (only actual table rows) - Prerequisites documentation and user consent requirements Implements TC-4131 Co-Authored-By: Claude Sonnet 4.5 --- .../performance/baseline-report.template.md | 98 ++++ .../performance/capture-baseline.template.mjs | 435 ++++++++++++++++++ .../module-analysis-report.template.md | 142 ++++++ .../performance/optimization-plan.template.md | 159 +++++++ .../performance/optimization-task.template.md | 97 ++++ .../verification-report.template.md | 143 ++++++ 6 files changed, 1074 insertions(+) create mode 100644 plugins/sdlc-workflow/skills/performance/baseline-report.template.md create mode 100644 plugins/sdlc-workflow/skills/performance/capture-baseline.template.mjs create mode 100644 plugins/sdlc-workflow/skills/performance/module-analysis-report.template.md create mode 100644 plugins/sdlc-workflow/skills/performance/optimization-plan.template.md create mode 100644 plugins/sdlc-workflow/skills/performance/optimization-task.template.md create mode 100644 plugins/sdlc-workflow/skills/performance/verification-report.template.md diff --git a/plugins/sdlc-workflow/skills/performance/baseline-report.template.md b/plugins/sdlc-workflow/skills/performance/baseline-report.template.md new file mode 100644 index 000000000..7bd2d40fe --- /dev/null +++ b/plugins/sdlc-workflow/skills/performance/baseline-report.template.md @@ -0,0 +1,98 @@ +--- +generated_by: {{skill-name}} +timestamp: {{iso-8601-timestamp}} +repository: {{repository-name}} +--- + +# Performance Baseline Report + +## Configuration Summary + +- **Capture Date:** {{capture-date}} +- **Iterations:** {{iterations}} +- **Warmup Runs:** {{warmup-runs}} +- **Scenarios Measured:** {{scenario-count}} + +## Aggregate Metrics + +Overall performance across all scenarios. + +| Metric | Mean | p50 (Median) | p95 | p99 | Unit | +|---|---|---|---|---|---| +| **LCP** (Largest Contentful Paint) | {{lcp-mean}} | {{lcp-p50}} | {{lcp-p95}} | {{lcp-p99}} | ms | +| **FCP** (First Contentful Paint) | {{fcp-mean}} | {{fcp-p50}} | {{fcp-p95}} | {{fcp-p99}} | ms | +| **TTI** (Time to Interactive) | {{tti-mean}} | {{tti-p50}} | {{tti-p95}} | {{tti-p99}} | ms | +| **Total Load Time** | {{total-mean}} | {{total-p50}} | {{total-p95}} | {{total-p99}} | ms | + +## Per-Scenario Metrics + +### {{scenario-1-name}} + +**URL:** `{{scenario-1-url}}` + +| Metric | Mean | p50 | p95 | p99 | Unit | +|---|---|---|---|---|---| +| LCP | {{scenario-1-lcp-mean}} | {{scenario-1-lcp-p50}} | {{scenario-1-lcp-p95}} | {{scenario-1-lcp-p99}} | ms | +| FCP | {{scenario-1-fcp-mean}} | {{scenario-1-fcp-p50}} | {{scenario-1-fcp-p95}} | {{scenario-1-fcp-p99}} | ms | +| TTI | {{scenario-1-tti-mean}} | {{scenario-1-tti-p50}} | {{scenario-1-tti-p95}} | {{scenario-1-tti-p99}} | ms | +| Total Load Time | {{scenario-1-total-mean}} | {{scenario-1-total-p50}} | {{scenario-1-total-p95}} | {{scenario-1-total-p99}} | ms | + +**Resource Summary:** +- Scripts: {{scenario-1-scripts-count}} +- Stylesheets: {{scenario-1-stylesheets-count}} +- Images: {{scenario-1-images-count}} +- Fetch/XHR: {{scenario-1-fetch-count}} +- Total Resources: {{scenario-1-total-resources}} + +### {{scenario-2-name}} + +**URL:** `{{scenario-2-url}}` + +| Metric | Mean | p50 | p95 | p99 | Unit | +|---|---|---|---|---|---| +| LCP | {{scenario-2-lcp-mean}} | {{scenario-2-lcp-p50}} | {{scenario-2-lcp-p95}} | {{scenario-2-lcp-p99}} | ms | +| FCP | {{scenario-2-fcp-mean}} | {{scenario-2-fcp-p50}} | {{scenario-2-fcp-p95}} | {{scenario-2-fcp-p99}} | ms | +| TTI | {{scenario-2-tti-mean}} | {{scenario-2-tti-p50}} | {{scenario-2-tti-p95}} | {{scenario-2-tti-p99}} | ms | +| Total Load Time | {{scenario-2-total-mean}} | {{scenario-2-total-p50}} | {{scenario-2-total-p95}} | {{scenario-2-total-p99}} | ms | + +**Resource Summary:** +- Scripts: {{scenario-2-scripts-count}} +- Stylesheets: {{scenario-2-stylesheets-count}} +- Images: {{scenario-2-images-count}} +- Fetch/XHR: {{scenario-2-fetch-count}} +- Total Resources: {{scenario-2-total-resources}} + +## Resource Timing Breakdown + +Top resources by load duration across all scenarios: + +| Resource | Type | Duration (ms) | Size (KB) | Scenario | +|---|---|---|---|---| +| {{resource-1-name}} | {{resource-1-type}} | {{resource-1-duration}} | {{resource-1-size}} | {{resource-1-scenario}} | +| {{resource-2-name}} | {{resource-2-type}} | {{resource-2-duration}} | {{resource-2-size}} | {{resource-2-scenario}} | +| {{resource-3-name}} | {{resource-3-type}} | {{resource-3-duration}} | {{resource-3-size}} | {{resource-3-scenario}} | + +## Waterfall Visualization + +Resource load timeline for {{waterfall-scenario-name}}: + +``` +{{waterfall-ascii-chart}} +``` + +**Legend:** +- `[====]` Script +- `[----]` Stylesheet +- `[****]` Image +- `[++++]` Fetch/XHR + +## Comparison with Previous Baseline + +{{comparison-section}} + +## Next Steps + +1. Review scenarios with LCP > 2.5s or TTI > 3.5s +2. Identify heavy resources (> 500KB scripts, > 200KB stylesheets) +3. Run application-wide analysis: `/sdlc-workflow:performance-analyze-app` +4. Run module-level analysis for specific pages: `/sdlc-workflow:performance-analyze-module` diff --git a/plugins/sdlc-workflow/skills/performance/capture-baseline.template.mjs b/plugins/sdlc-workflow/skills/performance/capture-baseline.template.mjs new file mode 100644 index 000000000..2db9458a3 --- /dev/null +++ b/plugins/sdlc-workflow/skills/performance/capture-baseline.template.mjs @@ -0,0 +1,435 @@ +#!/usr/bin/env node + +/** + * Performance Baseline Capture Script + * + * This script automates performance metric collection using Playwright browser automation. + * + * WHAT IT DOES: + * - Reads performance scenarios from a local configuration file (.claude/performance-config.md) + * - Launches a headless Chromium browser in your local environment + * - Navigates to localhost URLs (with port numbers) specified in the configuration + * - Collects standard browser performance metrics using Web APIs: + * - Navigation Timing API (LCP, FCP, TTI, Total Load Time) + * - Resource Timing API (scripts, stylesheets, images, fetch requests) + * - Runs multiple iterations per scenario (with warmup runs) + * - Outputs aggregated metrics as JSON to stdout + * + * PREREQUISITES: + * - Node.js >= 16 must be installed in your local environment + * - @playwright/test package must be installed (npm install -D @playwright/test) + * - Playwright browsers must be installed (npx playwright install chromium) + * - Your application must be running locally on the configured port + * + * SECURITY: + * - Only navigates to localhost URLs (127.0.0.0/8, ::1) + * - Config file must be within current directory (no path traversal) + * - Port numbers are required and validated (1-65535) + * - Iterations and warmup runs are bounded (max 50 iterations, 10 warmups) + * - No remote code execution or untrusted input execution + * - No credential storage or transmission + * - Runs entirely in your local Node.js environment + * - Query strings are stripped from resource URLs before output (prevents token leakage) + * + * USAGE: + * node capture-baseline.mjs --config path/to/performance-config.md [--port 3000] + * + * The --port argument is optional if URLs in the config already include port numbers. + */ + +import { chromium } from '@playwright/test'; +import { readFile } from 'fs/promises'; +import { URL } from 'url'; +import { resolve, relative, isAbsolute } from 'path'; + +// Parse command line arguments +const args = process.argv.slice(2); +const configIndex = args.indexOf('--config'); +if (configIndex === -1 || !args[configIndex + 1]) { + console.error('Usage: node capture-baseline.mjs --config [--port ]'); + console.error(''); + console.error('Prerequisites:'); + console.error(' - Node.js >= 16'); + console.error(' - @playwright/test installed (npm install -D @playwright/test)'); + console.error(' - Playwright browsers installed (npx playwright install chromium)'); + console.error(' - Application running locally on configured port'); + process.exit(1); +} + +// Validate config path (prevent path traversal) +const configPathInput = args[configIndex + 1]; +const configPath = resolve(configPathInput); +const relPath = relative(process.cwd(), configPath); + +if (relPath.startsWith('..') || isAbsolute(relPath)) { + console.error('Security Error: Config path must be within the current directory'); + console.error(` Provided: ${configPathInput}`); + console.error(` Resolved: ${configPath}`); + console.error(` Relative: ${relPath}`); + process.exit(1); +} + +// Optional port override with validation +const portIndex = args.indexOf('--port'); +let portOverride = null; +if (portIndex !== -1) { + const portValue = args[portIndex + 1]; + const portNum = parseInt(portValue, 10); + if (isNaN(portNum) || portNum < 1 || portNum > 65535) { + console.error(`Invalid port: ${portValue}. Must be between 1 and 65535.`); + process.exit(1); + } + portOverride = portNum; +} + +/** + * Parse performance configuration from markdown file + * Extracts scenarios table from the Performance Scenarios section + */ +async function parseConfig(configPath) { + const content = await readFile(configPath, 'utf-8'); + + // Extract Performance Scenarios section + const scenariosMatch = content.match(/## Performance Scenarios[\s\S]*?\n\n([\s\S]*?)(?=\n##|$)/); + if (!scenariosMatch) { + throw new Error('Performance Scenarios section not found in configuration'); + } + + const sectionContent = scenariosMatch[1]; + const lines = sectionContent.split('\n'); + + // Filter to only actual table rows: + // - First non-whitespace character is | + // - Contains at least 4 | characters (for | name | path | description | format) + const tableLines = lines.filter(line => { + const trimmed = line.trim(); + return trimmed.startsWith('|') && (trimmed.match(/\|/g) || []).length >= 4; + }); + + if (tableLines.length < 2) { + throw new Error('Performance Scenarios table not found or incomplete (need at least header + separator rows)'); + } + + // Skip header row (index 0) and separator row (index 1), parse data rows + const scenarios = []; + for (let i = 2; i < tableLines.length; i++) { + const cells = tableLines[i].split('|').map(cell => cell.trim()).filter(Boolean); + if (cells.length >= 3) { + scenarios.push({ + name: cells[0], + path: cells[1], + description: cells[2] + }); + } + } + + // Extract baseline capture settings with bounds + const settingsMatch = content.match(/## Baseline Capture Settings[\s\S]*?\| Iterations \| (\d+)/); + const warmupMatch = content.match(/## Baseline Capture Settings[\s\S]*?\| Warmup Runs \| (\d+)/); + + const iterationsRaw = settingsMatch ? parseInt(settingsMatch[1], 10) : 5; + const warmupRaw = warmupMatch ? parseInt(warmupMatch[1], 10) : 2; + + // Bound iterations to prevent DoS (max 50 iterations, 10 warmup runs) + const iterations = Math.min(Math.max(iterationsRaw, 1), 50); + const warmupRuns = Math.min(Math.max(warmupRaw, 0), 10); + + if (iterations !== iterationsRaw) { + console.error(`Warning: Iterations capped at 50 (config specified ${iterationsRaw})`); + } + if (warmupRuns !== warmupRaw) { + console.error(`Warning: Warmup runs capped at 10 (config specified ${warmupRaw})`); + } + + return { scenarios, iterations, warmupRuns }; +} + +/** + * Validate that URL is localhost only with port (security check) + */ +function validateLocalhostUrl(urlPath, defaultPort) { + let fullUrl; + + // Validate path before construction - only allow safe path characters to prevent URL injection + const pathPattern = /^[a-zA-Z0-9\/_\-.:?&=%]+$/; + if (!urlPath.startsWith('http://') && !urlPath.startsWith('https://') && !pathPattern.test(urlPath)) { + throw new Error(`Invalid characters in URL path: ${urlPath}. Only alphanumeric, /, _, -, ., :, ?, &, =, % allowed.`); + } + + // If URL is already complete (starts with http://), validate it + if (urlPath.startsWith('http://') || urlPath.startsWith('https://')) { + fullUrl = urlPath; + } else { + // Construct URL with localhost and port + if (!defaultPort) { + throw new Error(`URL "${urlPath}" does not include a port number. Please specify port in the URL (e.g., "http://localhost:3000/path") or use --port argument.`); + } + fullUrl = `http://localhost:${defaultPort}${urlPath.startsWith('/') ? '' : '/'}${urlPath}`; + } + + try { + const parsed = new URL(fullUrl); + const hostname = parsed.hostname.toLowerCase(); + + // Comprehensive localhost validation + // Allow: 'localhost', 127.0.0.0/8 CIDR, ::1 + // Block: 0.0.0.0 (all interfaces), IPv6-mapped IPv4 loopback, any other hostname + + const isLocalhost = hostname === 'localhost'; + const isIPv4Loopback = /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname); + const isIPv6Loopback = hostname === '[::1]' || hostname === '::1'; + + // Explicitly block dangerous patterns + const isZeroAddress = hostname === '0.0.0.0'; + const isIPv6Mapped = hostname.includes('::ffff:'); + + if (isZeroAddress) { + throw new Error(`Security: 0.0.0.0 is not allowed (binds to all interfaces). Use localhost or 127.0.0.1.`); + } + + if (isIPv6Mapped) { + throw new Error(`Security: IPv6-mapped IPv4 addresses are not allowed. Use localhost or 127.0.0.1.`); + } + + if (!isLocalhost && !isIPv4Loopback && !isIPv6Loopback) { + throw new Error(`Security: Only localhost URLs are allowed. Got: ${hostname}`); + } + + // Validate port exists + if (!parsed.port) { + throw new Error(`Port number is required in URL: ${fullUrl}. Use --port argument or include port in URL.`); + } + + return fullUrl; + } catch (error) { + if (error.message.includes('Security:') || error.message.includes('Port number is required') || error.message.includes('Invalid characters')) { + throw error; + } + throw new Error(`Invalid URL: ${urlPath} - ${error.message}`); + } +} + +/** + * Strip query strings from URLs to prevent token leakage + */ +function stripQueryString(url) { + try { + const parsed = new URL(url); + return `${parsed.origin}${parsed.pathname}`; + } catch { + // If URL parsing fails, return as-is (shouldn't happen with resource.name) + return url; + } +} + +/** + * Collect performance metrics from browser APIs + */ +async function collectMetrics(page) { + return await page.evaluate(() => { + const perfData = {}; + + // Navigation Timing API + const navigation = performance.getEntriesByType('navigation')[0]; + if (navigation) { + perfData.navigationTiming = { + dns: navigation.domainLookupEnd - navigation.domainLookupStart, + tcp: navigation.connectEnd - navigation.connectStart, + request: navigation.responseStart - navigation.requestStart, + response: navigation.responseEnd - navigation.responseStart, + domProcessing: navigation.domComplete - navigation.domInteractive, + loadComplete: navigation.loadEventEnd - navigation.loadEventStart, + totalTime: navigation.loadEventEnd - navigation.fetchStart + }; + } + + // Core Web Vitals + const paintEntries = performance.getEntriesByType('paint'); + perfData.fcp = paintEntries.find(e => e.name === 'first-contentful-paint')?.startTime || null; + + const lcpEntries = performance.getEntriesByType('largest-contentful-paint'); + perfData.lcp = lcpEntries.length > 0 + ? lcpEntries[lcpEntries.length - 1].startTime + : null; + + // TTI approximation (dom interactive + 50ms for user input readiness) + perfData.tti = navigation ? navigation.domInteractive + 50 : null; + + // Resource Timing API + const resources = performance.getEntriesByType('resource'); + perfData.resources = resources.map(resource => ({ + name: resource.name, // Will be sanitized by stripQueryString after collection + type: resource.initiatorType, + duration: resource.duration, + size: resource.transferSize || 0, + startTime: resource.startTime + })); + + // Categorize resources by type + perfData.resourceSummary = { + scripts: resources.filter(r => r.initiatorType === 'script').length, + stylesheets: resources.filter(r => r.initiatorType === 'link' || r.initiatorType === 'css').length, + images: resources.filter(r => r.initiatorType === 'img').length, + fetch: resources.filter(r => r.initiatorType === 'fetch' || r.initiatorType === 'xmlhttprequest').length, + total: resources.length + }; + + return perfData; + }); +} + +/** + * Run performance measurement for a single scenario + */ +async function measureScenario(browser, scenario, iterations, warmupRuns, defaultPort) { + const url = validateLocalhostUrl(scenario.path, defaultPort); + const allMetrics = []; + + for (let i = 0; i < iterations + warmupRuns; i++) { + const page = await browser.newPage(); + + try { + await page.goto(url, { waitUntil: 'load', timeout: 30000 }); + + // Wait for DOM content to be fully loaded + await page.waitForLoadState('domcontentloaded'); + + // Collect metrics + const metrics = await collectMetrics(page); + + // Strip query strings from resource URLs before storing (prevent token leakage) + if (metrics.resources) { + metrics.resources = metrics.resources.map(resource => ({ + ...resource, + name: stripQueryString(resource.name) + })); + } + + // Skip warmup runs + if (i >= warmupRuns) { + allMetrics.push(metrics); + } + } catch (error) { + console.error(`Error measuring ${scenario.name} (iteration ${i + 1}): ${error.message}`); + } finally { + await page.close(); + } + } + + return aggregateMetrics(allMetrics); +} + +/** + * Aggregate metrics across iterations (mean, p50, p95, p99) + */ +function aggregateMetrics(metricsArray) { + if (metricsArray.length === 0) { + return null; + } + + const aggregate = { + iterations: metricsArray.length, + fcp: calculateStats(metricsArray.map(m => m.fcp).filter(v => v !== null)), + lcp: calculateStats(metricsArray.map(m => m.lcp).filter(v => v !== null)), + tti: calculateStats(metricsArray.map(m => m.tti).filter(v => v !== null)), + totalTime: calculateStats(metricsArray.map(m => m.navigationTiming?.totalTime).filter(v => v !== null && v !== undefined)), + resourceCount: calculateStats(metricsArray.map(m => m.resources?.length || 0)) + }; + + // Average resource summary + const resourceSummaries = metricsArray.map(m => m.resourceSummary).filter(Boolean); + if (resourceSummaries.length > 0) { + aggregate.resourceSummary = { + scripts: Math.round(resourceSummaries.reduce((sum, r) => sum + r.scripts, 0) / resourceSummaries.length), + stylesheets: Math.round(resourceSummaries.reduce((sum, r) => sum + r.stylesheets, 0) / resourceSummaries.length), + images: Math.round(resourceSummaries.reduce((sum, r) => sum + r.images, 0) / resourceSummaries.length), + fetch: Math.round(resourceSummaries.reduce((sum, r) => sum + r.fetch, 0) / resourceSummaries.length), + total: Math.round(resourceSummaries.reduce((sum, r) => sum + r.total, 0) / resourceSummaries.length) + }; + } + + return aggregate; +} + +/** + * Calculate statistical metrics (mean, p50, p95, p99) + */ +function calculateStats(values) { + if (values.length === 0) return null; + + const sorted = values.slice().sort((a, b) => a - b); + const sum = values.reduce((a, b) => a + b, 0); + + return { + mean: Math.round(sum / values.length * 100) / 100, + p50: sorted[Math.floor(sorted.length * 0.5)], + p95: sorted[Math.floor(sorted.length * 0.95)], + p99: sorted[Math.floor(sorted.length * 0.99)] + }; +} + +/** + * Main execution + */ +async function main() { + let browser; + + try { + // Parse configuration + console.error('Checking prerequisites...'); + const config = await parseConfig(configPath); + + if (config.scenarios.length === 0) { + console.error('No performance scenarios found in configuration'); + process.exit(1); + } + + // Launch browser + console.error('Launching Chromium browser (headless)...'); + browser = await chromium.launch({ headless: true }); + + // Prevent prototype pollution by using Object.create(null) + const results = Object.create(null); + + for (const scenario of config.scenarios) { + console.error(`Measuring: ${scenario.name}...`); + + // Sanitize scenario name to prevent prototype pollution + const safeName = String(scenario.name).replace(/[^a-zA-Z0-9_\- ]/g, '_'); + + if (safeName !== scenario.name) { + console.error(` Warning: Scenario name sanitized: "${scenario.name}" -> "${safeName}"`); + } + + results[safeName] = await measureScenario(browser, scenario, config.iterations, config.warmupRuns, portOverride); + } + + // Output JSON results to stdout + console.log(JSON.stringify({ + timestamp: new Date().toISOString(), + config: { + iterations: config.iterations, + warmupRuns: config.warmupRuns, + port: portOverride + }, + scenarios: results + }, null, 2)); + + } catch (error) { + console.error(`Fatal error: ${error.message}`); + console.error(''); + console.error('Prerequisites checklist:'); + console.error(' 1. Is your application running locally?'); + console.error(' 2. Is @playwright/test installed? (npm install -D @playwright/test)'); + console.error(' 3. Are Playwright browsers installed? (npx playwright install chromium)'); + console.error(' 4. Do URLs in your config include port numbers or did you use --port?'); + console.error(' 5. Is the config file within the current directory?'); + process.exit(1); + } finally { + if (browser) { + await browser.close(); + } + } +} + +main(); diff --git a/plugins/sdlc-workflow/skills/performance/module-analysis-report.template.md b/plugins/sdlc-workflow/skills/performance/module-analysis-report.template.md new file mode 100644 index 000000000..062ea665f --- /dev/null +++ b/plugins/sdlc-workflow/skills/performance/module-analysis-report.template.md @@ -0,0 +1,142 @@ +--- +generated_by: {{skill-name}} +timestamp: {{iso-8601-timestamp}} +repository: {{repository-name}} +module: {{module-name}} +--- + +# Module Analysis Report: {{module-name}} + +## Module Overview + +- **Module Name:** {{module-name}} +- **Entry Point:** `{{entry-point-path}}` +- **Analysis Date:** {{analysis-date}} +- **Baseline LCP:** {{baseline-lcp}} ms +- **Baseline Bundle Size:** {{baseline-bundle-size}} KB + +## Bundle Analysis + +### Bundle Composition + +| Component | Size (KB) | % of Total | Type | +|---|---|---|---| +| {{bundle-component-1-name}} | {{bundle-component-1-size}} | {{bundle-component-1-percent}} | {{bundle-component-1-type}} | +| {{bundle-component-2-name}} | {{bundle-component-2-size}} | {{bundle-component-2-percent}} | {{bundle-component-2-type}} | +| {{bundle-component-3-name}} | {{bundle-component-3-size}} | {{bundle-component-3-percent}} | {{bundle-component-3-type}} | + +**Total Bundle Size:** {{total-bundle-size}} KB + +### Third-Party Libraries + +| Library | Size (KB) | Version | Purpose | +|---|---|---|---| +| {{library-1-name}} | {{library-1-size}} | {{library-1-version}} | {{library-1-purpose}} | +| {{library-2-name}} | {{library-2-size}} | {{library-2-version}} | {{library-2-purpose}} | + +### Module-Specific vs. Shared Code + +- **Module-specific code:** {{module-specific-size}} KB ({{module-specific-percent}}%) +- **Shared code (from common chunks):** {{shared-code-size}} KB ({{shared-code-percent}}%) + +## Component Analysis + +### Component Hierarchy + +``` +{{component-tree-visualization}} +``` + +### Heavy Components + +Components with large dependency trees or frequent re-renders: + +| Component | Dependencies | Render Count | Issue | +|---|---|---|---| +| {{heavy-component-1-name}} | {{heavy-component-1-deps}} | {{heavy-component-1-renders}} | {{heavy-component-1-issue}} | +| {{heavy-component-2-name}} | {{heavy-component-2-deps}} | {{heavy-component-2-renders}} | {{heavy-component-2-issue}} | + +## Over-Fetching Analysis + +API calls made during module load with unused field analysis: + +### {{api-endpoint-1}} + +**Request:** `{{api-endpoint-1-method}} {{api-endpoint-1-path}}` + +| Field | Fetched | Used | Status | +|---|---|---|---| +| {{api-field-1-name}} | ✓ | {{api-field-1-used}} | {{api-field-1-status}} | +| {{api-field-2-name}} | ✓ | {{api-field-2-used}} | {{api-field-2-status}} | +| {{api-field-3-name}} | ✓ | {{api-field-3-used}} | {{api-field-3-status}} | + +**Over-fetching:** {{api-endpoint-1-unused-percent}}% of fields unused + +### {{api-endpoint-2}} + +**Request:** `{{api-endpoint-2-method}} {{api-endpoint-2-path}}` + +| Field | Fetched | Used | Status | +|---|---|---|---| +| {{api-field-4-name}} | ✓ | {{api-field-4-used}} | {{api-field-4-status}} | +| {{api-field-5-name}} | ✓ | {{api-field-5-used}} | {{api-field-5-status}} | + +**Over-fetching:** {{api-endpoint-2-unused-percent}}% of fields unused + +## N+1 Query Detection + +Potential N+1 query patterns detected: + +### {{n-plus-1-pattern-1-name}} + +**Location:** `{{n-plus-1-pattern-1-file}}:{{n-plus-1-pattern-1-line}}` + +**Pattern:** {{n-plus-1-pattern-1-description}} + +**Impact:** {{n-plus-1-pattern-1-impact}} sequential requests + +**Recommendation:** {{n-plus-1-pattern-1-recommendation}} + +## Performance Anti-Patterns + +| Anti-Pattern | Severity | Location | Description | +|---|---|---|---| +| **Waterfall Loading** | {{waterfall-severity}} | {{waterfall-location}} | {{waterfall-description}} | +| **Render-Blocking Resources** | {{render-blocking-severity}} | {{render-blocking-location}} | {{render-blocking-description}} | +| **Unused Code** | {{unused-code-severity}} | {{unused-code-location}} | {{unused-code-description}} | +| **Expensive Re-renders** | {{expensive-rerender-severity}} | {{expensive-rerender-location}} | {{expensive-rerender-description}} | +| **Long Tasks** | {{long-task-severity}} | {{long-task-location}} | {{long-task-description}} | +| **Layout Thrashing** | {{layout-thrashing-severity}} | {{layout-thrashing-location}} | {{layout-thrashing-description}} | + +**Severity Levels:** +- **CRITICAL:** LCP > 4s, bundle > 1MB, API > 2s (p95) +- **HIGH:** LCP > 2.5s, bundle > 500KB, API > 1s (p95) +- **MEDIUM:** LCP > 1.8s, bundle > 300KB, API > 500ms (p95) +- **LOW:** Below medium thresholds but above baseline targets + +## Recommendations + +### High-Priority Optimizations + +1. **{{recommendation-1-title}}** + - Impact: {{recommendation-1-impact}} + - Effort: {{recommendation-1-effort}} + - Approach: {{recommendation-1-approach}} + +2. **{{recommendation-2-title}}** + - Impact: {{recommendation-2-impact}} + - Effort: {{recommendation-2-effort}} + - Approach: {{recommendation-2-approach}} + +### Medium-Priority Optimizations + +3. **{{recommendation-3-title}}** + - Impact: {{recommendation-3-impact}} + - Effort: {{recommendation-3-effort}} + - Approach: {{recommendation-3-approach}} + +## Next Steps + +1. Review high-priority recommendations with team +2. Create optimization plan: `/sdlc-workflow:performance-plan-optimization` +3. Prioritize by impact vs. effort trade-off diff --git a/plugins/sdlc-workflow/skills/performance/optimization-plan.template.md b/plugins/sdlc-workflow/skills/performance/optimization-plan.template.md new file mode 100644 index 000000000..0072331f3 --- /dev/null +++ b/plugins/sdlc-workflow/skills/performance/optimization-plan.template.md @@ -0,0 +1,159 @@ +--- +generated_by: {{skill-name}} +timestamp: {{iso-8601-timestamp}} +repository: {{repository-name}} +module: {{module-name}} +--- + +# Performance Optimization Plan: {{module-name}} + +## Executive Summary + +**Optimization Goal:** {{optimization-goal}} + +**Expected Impact:** +- LCP improvement: {{expected-lcp-improvement}}% ({{baseline-lcp}}ms → {{target-lcp}}ms) +- FCP improvement: {{expected-fcp-improvement}}% ({{baseline-fcp}}ms → {{target-fcp}}ms) +- Bundle size reduction: {{expected-bundle-reduction}}% ({{baseline-bundle}}KB → {{target-bundle}}KB) + +**Total Effort Estimate:** {{total-effort-estimate}} engineering days + +**Risk Level:** {{risk-level}} (LOW/MEDIUM/HIGH) + +## Task Sequence + +Optimizations listed in recommended implementation order: + +| # | Task | Impact | Effort | Dependencies | +|---|---|---|---|---| +| 1 | {{task-1-title}} | {{task-1-impact}} | {{task-1-effort}} | None | +| 2 | {{task-2-title}} | {{task-2-impact}} | {{task-2-effort}} | Task 1 | +| 3 | {{task-3-title}} | {{task-3-impact}} | {{task-3-effort}} | Task 1 | +| 4 | {{task-4-title}} | {{task-4-impact}} | {{task-4-effort}} | Task 2, Task 3 | + +**Sequencing Rationale:** {{sequencing-rationale}} + +## Per-Task Details + +### Task 1: {{task-1-title}} + +**Jira Issue:** {{task-1-jira-key}} + +**Baseline Metrics:** +- LCP: {{task-1-baseline-lcp}}ms +- Bundle Size: {{task-1-baseline-bundle}}KB + +**Target Metrics:** +- LCP: {{task-1-target-lcp}}ms ({{task-1-improvement}}% improvement) +- Bundle Size: {{task-1-target-bundle}}KB + +**Optimization Strategy:** {{task-1-strategy}} + +**Files to Modify:** +- `{{task-1-file-1}}` +- `{{task-1-file-2}}` + +**Risk Assessment:** {{task-1-risk}} + +**Rollback Plan:** {{task-1-rollback}} + +### Task 2: {{task-2-title}} + +**Jira Issue:** {{task-2-jira-key}} + +**Baseline Metrics:** +- API Latency (p95): {{task-2-baseline-latency}}ms +- Over-fetching: {{task-2-baseline-overfetch}}% unused fields + +**Target Metrics:** +- API Latency (p95): {{task-2-target-latency}}ms ({{task-2-improvement}}% improvement) +- Over-fetching: {{task-2-target-overfetch}}% + +**Optimization Strategy:** {{task-2-strategy}} + +**Files to Modify:** +- `{{task-2-file-1}}` +- `{{task-2-file-2}}` + +**Risk Assessment:** {{task-2-risk}} + +**Rollback Plan:** {{task-2-rollback}} + +### Task 3: {{task-3-title}} + +**Jira Issue:** {{task-3-jira-key}} + +**Baseline Metrics:** +- Render-blocking resources: {{task-3-baseline-blocking}} resources +- Critical path duration: {{task-3-baseline-critical-path}}ms + +**Target Metrics:** +- Render-blocking resources: {{task-3-target-blocking}} resources +- Critical path duration: {{task-3-target-critical-path}}ms ({{task-3-improvement}}% improvement) + +**Optimization Strategy:** {{task-3-strategy}} + +**Files to Modify:** +- `{{task-3-file-1}}` +- `{{task-3-file-2}}` + +**Risk Assessment:** {{task-3-risk}} + +**Rollback Plan:** {{task-3-rollback}} + +## Risk Assessment + +### Overall Risks + +| Risk | Likelihood | Impact | Mitigation | +|---|---|---|---| +| {{risk-1-name}} | {{risk-1-likelihood}} | {{risk-1-impact}} | {{risk-1-mitigation}} | +| {{risk-2-name}} | {{risk-2-likelihood}} | {{risk-2-impact}} | {{risk-2-mitigation}} | +| {{risk-3-name}} | {{risk-3-likelihood}} | {{risk-3-impact}} | {{risk-3-mitigation}} | + +**Risk Levels:** +- **LOW:** Well-understood optimization, minimal code changes, easy rollback +- **MEDIUM:** Moderate code changes, requires thorough testing, rollback plan required +- **HIGH:** Architectural changes, affects multiple modules, requires staged rollout + +## Rollback Strategy + +### Per-Task Rollback + +Each task includes a specific rollback plan in the task details above. + +### Global Rollback Strategy + +If multiple optimizations are deployed and regressions are detected: + +1. **Immediate rollback:** Revert the most recent deployment +2. **Identify culprit:** Use baseline comparison to identify which optimization caused regression +3. **Selective revert:** Revert only the problematic optimization, keep successful ones +4. **Root cause analysis:** Investigate why regression occurred (missed test scenario, incorrect metric interpretation, etc.) +5. **Re-test and re-deploy:** Fix issue, re-run baseline, re-deploy + +### Rollback Trigger Criteria + +Automatic rollback if: +- Any Core Web Vital degrades > 10% from baseline in production +- Error rate increases > 2% from baseline +- User-reported performance complaints increase significantly + +## Jira Epic and Tasks + +**Epic:** {{epic-jira-key}} - {{epic-title}} + +**Tasks:** +- {{task-1-jira-key}}: {{task-1-title}} +- {{task-2-jira-key}}: {{task-2-title}} +- {{task-3-jira-key}}: {{task-3-title}} +- {{task-4-jira-key}}: {{task-4-title}} + +All tasks linked to Epic via "Incorporates" relationship. + +## Next Steps + +1. Review this plan with team +2. Confirm task priorities and sequencing +3. Begin implementation with Task 1: {{task-1-jira-key}} +4. Run `/sdlc-workflow:performance-implement-optimization {{task-1-jira-key}}` to start diff --git a/plugins/sdlc-workflow/skills/performance/optimization-task.template.md b/plugins/sdlc-workflow/skills/performance/optimization-task.template.md new file mode 100644 index 000000000..c5322a3fa --- /dev/null +++ b/plugins/sdlc-workflow/skills/performance/optimization-task.template.md @@ -0,0 +1,97 @@ +# Performance Optimization Task Template + +This template extends the base `task-description-template.md` with performance-specific sections. + +## Template + +``` +## Repository + + +## Description + + +## Baseline Metrics + +Current performance metrics before optimization (from baseline-report.md): + +| Metric | Current (Baseline) | Unit | +|---|---|---| +| LCP (Largest Contentful Paint) | {{baseline-lcp}} | ms | +| FCP (First Contentful Paint) | {{baseline-fcp}} | ms | +| TTI (Time to Interactive) | {{baseline-tti}} | ms | +| Total Load Time | {{baseline-total}} | ms | +| Bundle Size | {{baseline-bundle-size}} | KB | + +## Target Metrics + +Performance targets to achieve with this optimization: + +| Metric | Target | Improvement | Unit | +|---|---|---|---| +| LCP (Largest Contentful Paint) | {{target-lcp}} | {{lcp-improvement}}% | ms | +| FCP (First Contentful Paint) | {{target-fcp}} | {{fcp-improvement}}% | ms | +| TTI (Time to Interactive) | {{target-tti}} | {{tti-improvement}}% | ms | +| Total Load Time | {{target-total}} | {{total-improvement}}% | ms | +| Bundle Size | {{target-bundle-size}} | {{bundle-improvement}}% | KB | + +## Files to Modify +- `path/to/file.ext` — + +## Files to Create +- `path/to/new_file.ext` — + +## API Changes +- `GET /v2/endpoint` — NEW: +- `PUT /v2/endpoint/{id}` — MODIFY: + +## Implementation Notes + + +## Reuse Candidates +- `path/to/file.ext::symbol_name` — + +## Acceptance Criteria +- [ ] Criterion 1 +- [ ] Criterion 2 +- [ ] Target metrics achieved (LCP ≤ {{target-lcp}}ms, FCP ≤ {{target-fcp}}ms) +- [ ] No performance regressions in non-target scenarios (< 5% degradation) + +## Test Requirements +- [ ] Functional test description 1 +- [ ] Functional test description 2 + +## Performance Test Requirements + +- [ ] Re-run baseline capture for ALL scenarios (not just affected ones) +- [ ] Verify target scenario meets target metrics +- [ ] Verify non-target scenarios have no regressions (< 5% degradation in LCP, FCP, TTI, Total Load Time) +- [ ] Generate before/after comparison report +- [ ] If regression detected in non-target scenarios, prompt user for approval before committing + +## Verification Commands +- `` — +- `node capture-baseline.mjs --config .claude/performance-config.md` — Should show improved metrics for target scenario + +## Documentation Updates +- `path/to/doc.md` — + +## Dependencies +- Depends on: Task N — (if any) +``` + +## Rules + +Follows all base template rules (from `shared/task-description-template.md`), plus: + +- **Baseline Metrics** section is required — must reference actual baseline data +- **Target Metrics** section is required — targets must be realistic and measurable +- **Performance Test Requirements** section is required — defines regression testing criteria +- Omit non-applicable sections (API Changes, Files to Create, Documentation Updates, etc.) as per base template rules +- Target metrics should follow Google's Core Web Vitals "Good" thresholds unless application-specific requirements dictate otherwise: + - LCP: ≤ 2.5s + - FCP: ≤ 1.8s + - TTI: ≤ 3.5s +- Improvement percentages help communicate expected impact clearly diff --git a/plugins/sdlc-workflow/skills/performance/verification-report.template.md b/plugins/sdlc-workflow/skills/performance/verification-report.template.md new file mode 100644 index 000000000..3494b7adc --- /dev/null +++ b/plugins/sdlc-workflow/skills/performance/verification-report.template.md @@ -0,0 +1,143 @@ +--- +generated_by: {{skill-name}} +timestamp: {{iso-8601-timestamp}} +repository: {{repository-name}} +task: {{task-jira-key}} +pr: {{pr-url}} +--- + +# Performance Verification Report + +## Verification Summary + +**Task:** {{task-jira-key}} - {{task-title}} + +**PR:** {{pr-url}} + +**Verification Date:** {{verification-date}} + +**Result:** {{result}} (PASS / PARTIAL SUCCESS / FAIL) + +**Decision:** {{decision}} (APPROVE / REQUEST CHANGES / NEEDS INVESTIGATION) + +## Before/After Comparison + +### Target Scenario: {{target-scenario-name}} + +| Metric | Before (Baseline) | After (Current) | Target | Status | +|---|---|---|---|---| +| **LCP** | {{before-lcp}}ms | {{after-lcp}}ms | ≤ {{target-lcp}}ms | {{lcp-status}} | +| **FCP** | {{before-fcp}}ms | {{after-fcp}}ms | ≤ {{target-fcp}}ms | {{fcp-status}} | +| **TTI** | {{before-tti}}ms | {{after-tti}}ms | ≤ {{target-tti}}ms | {{tti-status}} | +| **Total Load Time** | {{before-total}}ms | {{after-total}}ms | ≤ {{target-total}}ms | {{total-status}} | +| **Bundle Size** | {{before-bundle}}KB | {{after-bundle}}KB | ≤ {{target-bundle}}KB | {{bundle-status}} | + +**Improvement Summary:** +- LCP: {{lcp-improvement}}% improvement +- FCP: {{fcp-improvement}}% improvement +- TTI: {{tti-improvement}}% improvement +- Total Load Time: {{total-improvement}}% improvement +- Bundle Size: {{bundle-improvement}}% reduction + +**Status Legend:** +- ✅ PASS: Target achieved +- ⚠️ PARTIAL: Significant improvement (> 20%) but target not fully met +- ❌ FAIL: Target not met and improvement < 20% + +## Regression Check + +Performance validation for non-target scenarios to ensure no regressions introduced: + +| Scenario | Metric | Before | After | Change | Status | +|---|---|---|---|---|---| +| {{scenario-1-name}} | LCP | {{scenario-1-before-lcp}}ms | {{scenario-1-after-lcp}}ms | {{scenario-1-lcp-change}}% | {{scenario-1-lcp-status}} | +| {{scenario-1-name}} | FCP | {{scenario-1-before-fcp}}ms | {{scenario-1-after-fcp}}ms | {{scenario-1-fcp-change}}% | {{scenario-1-fcp-status}} | +| {{scenario-2-name}} | LCP | {{scenario-2-before-lcp}}ms | {{scenario-2-after-lcp}}ms | {{scenario-2-lcp-change}}% | {{scenario-2-lcp-status}} | +| {{scenario-2-name}} | FCP | {{scenario-2-before-fcp}}ms | {{scenario-2-after-fcp}}ms | {{scenario-2-fcp-change}}% | {{scenario-2-fcp-status}} | + +**Regression Threshold:** > 5% degradation in any metric + +**Regressions Detected:** {{regressions-detected-count}} + +{{regressions-detail}} + +## Target Achievement + +### Overall Assessment + +**Acceptance Criteria Met:** {{acceptance-criteria-met}} / {{acceptance-criteria-total}} + +**Specific Criteria:** +- [ ] {{criterion-1}} — {{criterion-1-status}} +- [ ] {{criterion-2}} — {{criterion-2-status}} +- [ ] {{criterion-3}} — {{criterion-3-status}} +- [ ] Target metrics achieved (LCP ≤ {{target-lcp}}ms, FCP ≤ {{target-fcp}}ms) — {{target-metrics-status}} +- [ ] No performance regressions in non-target scenarios (< 5% degradation) — {{regression-check-status}} + +### Functional Testing + +**Functional Tests:** {{functional-tests-status}} (PASS / FAIL) + +**Test Results:** +- {{test-1-name}}: {{test-1-status}} +- {{test-2-name}}: {{test-2-status}} +- {{test-3-name}}: {{test-3-status}} + +## Review Feedback + +### PR Review Comments + +{{pr-review-comments}} + +### Code Review Issues + +{{code-review-issues}} + +## Recommendations + +### For This PR + +**Recommendation:** {{pr-recommendation}} + +**Justification:** {{pr-justification}} + +### Next Steps + +{{next-steps}} + +### Follow-Up Tasks + +{{follow-up-tasks}} + +## Verification Details + +**Verification Method:** {{verification-method}} + +**Baseline Used:** {{baseline-file-path}} + +**Verification Run:** {{verification-run-timestamp}} + +**Environment:** +- Node.js: {{nodejs-version}} +- Playwright: {{playwright-version}} +- Application Port: {{application-port}} + +## Appendix + +### Raw Metrics + +
+Full baseline metrics JSON + +```json +{{baseline-metrics-json}} +``` +
+ +
+Full current metrics JSON + +```json +{{current-metrics-json}} +``` +
From e0afe2cfeea62ac9366ee1ca1044f6bb41f20841 Mon Sep 17 00:00:00 2001 From: mrrajan <86094767+mrrajan@users.noreply.github.com.> Date: Thu, 16 Apr 2026 11:54:49 +0530 Subject: [PATCH 03/23] feat(performance): add workflow discovery skill Add performance-workflow-discovery skill that analyzes frontend source code to identify functional workflows (user journeys), presents discovered workflows to the user, and prompts for selection. This skill serves as the entry point for the workflow- based performance optimization approach. - Discovers workflows from router configuration, feature modules, and navigation structure - Estimates complexity based on components and API calls - Saves user selection to performance-config.md - Updated docs/workflow.md with performance optimization workflow section Implements TC-4132 Assisted-by: Claude Code --- docs/workflow.md | 76 ++++++ .../performance-workflow-discovery/SKILL.md | 229 ++++++++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 plugins/sdlc-workflow/skills/performance-workflow-discovery/SKILL.md diff --git a/docs/workflow.md b/docs/workflow.md index 6666ab58b..a338eb5f9 100644 --- a/docs/workflow.md +++ b/docs/workflow.md @@ -448,6 +448,82 @@ flowchart TD - **Task dependencies** — all intermediate tasks depend on the create-branch task; the merge-branch task depends on all intermediate tasks. **Jira state transitions:** New → In Progress → Done (create-branch bookend) | New → In Progress → In Review (intermediate + merge-branch tasks) → Done (human merge) +## Performance Optimization Workflow + +The performance optimization workflow is a specialized workflow for discovering, analyzing, and optimizing frontend application performance. It operates independently of the main SDLC workflow but follows a similar structured approach. + +### Performance Setup (One-time) + +**Skill:** `/sdlc-workflow:performance-setup` + +Initializes Performance Analysis Configuration in the target repository by discovering routes and modules from the codebase. + +**Invocation:** + +``` +/sdlc-workflow:performance-setup +/sdlc-workflow:performance-setup /path/to/target/repo +``` + +**Workflow:** +1. Determine target repository (argument or current directory) +2. Detect existing configuration (update or skip if exists) +3. Discover routes and user flows from router configuration +4. Discover module registry (lazy-loaded routes, code-split chunks) +5. Collect configuration values (baseline settings, optimization targets) +6. Generate `.claude/performance-config.md` configuration file +7. Create target directories (`.claude/performance/baselines/`, `/analysis/`, `/plans/`, `/verification/`) +8. Validate configuration and output summary + +**Output:** +- `.claude/performance-config.md` created in target repository +- Target directories created +- Configuration validated + +**Guardrails:** +- Idempotent — running multiple times offers to update or skip +- Does NOT modify source code — only creates configuration file +- All discovered routes/modules must reference actual files + +--- + +### Workflow Discovery + +**Skill:** `/sdlc-workflow:performance-workflow-discovery` + +Analyzes frontend source code to identify functional workflows (user journeys), presents discovered workflows to the user, and prompts the user to select which workflow to optimize. + +**Invocation:** + +``` +/sdlc-workflow:performance-workflow-discovery +/sdlc-workflow:performance-workflow-discovery /path/to/target/repo +``` + +**Workflow:** +1. Determine target repository (argument or current directory) +2. Verify Performance Analysis Configuration exists (created by performance-setup) +3. Discover workflows from codebase: + - Find router configuration files + - Extract routes and infer workflows by grouping (path prefixes, feature modules, navigation structure) + - Examine feature module directories + - Check for user flow documentation + - Synthesize discovered workflows with complexity estimates +4. Present discovered workflows in formatted table +5. Prompt user to select workflow by number +6. Save selection to `.claude/performance-config.md` (adds "Selected Workflow" section) +7. Output summary with next steps + +**Output:** +- Discovered workflows presented in formatted table +- User-selected workflow saved to performance configuration +- Guidance for next steps (load test data, start app, run baseline) + +**Guardrails:** +- Requires Performance Analysis Configuration to exist (prompts user to run performance-setup if missing) +- Does NOT modify source code — only updates configuration file +- Discovers workflows from actual source code — no placeholder examples +- If no workflows discovered, stops and informs user --- diff --git a/plugins/sdlc-workflow/skills/performance-workflow-discovery/SKILL.md b/plugins/sdlc-workflow/skills/performance-workflow-discovery/SKILL.md new file mode 100644 index 000000000..83b7116c2 --- /dev/null +++ b/plugins/sdlc-workflow/skills/performance-workflow-discovery/SKILL.md @@ -0,0 +1,229 @@ +--- +name: performance-workflow-discovery +description: | + Discover application workflows from frontend source code and prompt user to select which workflow to optimize. +argument-hint: "[target-repository-path]" +--- + +# performance-workflow-discovery skill + +You are an AI workflow discovery assistant. You analyze a frontend application's codebase to identify functional workflows (user journeys through the application), present discovered workflows to the user with descriptions, and prompt the user to select which workflow to optimize for performance. + +## Guardrails + +- This skill modifies ONE file: `.claude/performance-config.md` in the target repository (adds selected workflow) +- This skill does NOT modify source code files — only updates the performance configuration +- This skill requires Performance Analysis Configuration to exist (created by `performance-setup` skill) + +## Step 1 – Determine Target Repository + +If the user provided a repository path as an argument, use that as the target. Otherwise, use the current working directory. + +Verify the target directory exists and contains a frontend application (check for `package.json`, `src/`, or similar frontend indicators). + +## Step 2 – Verify Performance Configuration Exists + +Check if `.claude/performance-config.md` exists in the target repository. + +- **If not exists:** Inform the user: + > "Performance Analysis Configuration not found. Please run `/sdlc-workflow:performance-setup` first to initialize the configuration, then re-run this skill." + + Stop execution. + +- **If exists:** Read the configuration file and proceed to Step 3. + +## Step 3 – Discover Workflows from Codebase + +Analyze the frontend source code to identify functional workflows (user journeys). A workflow is a sequence of pages/screens that form a cohesive user task. + +### Step 3.1 – Find Router Configuration + +Use the same router discovery approach from `performance-setup` skill: + +Common router configuration file patterns: +- React Router: `src/routes.tsx`, `src/router/index.ts`, `src/App.tsx` (with `` components) +- Vue Router: `src/router/index.ts`, `src/router/routes.ts` +- Angular: `src/app-routing.module.ts`, `src/app/app-routing.module.ts` +- Next.js: `pages/` or `app/` directory structure (file-based routing) + +Use Glob to find likely router files: +``` +**/*routes*.{ts,tsx,js,jsx} +**/*router*.{ts,tsx,js,jsx} +**/App.{ts,tsx,js,jsx} +``` + +### Step 3.2 – Extract Routes and Infer Workflows + +For each router configuration file found, extract route paths using Read or Grep: + +``` +path:\s*['"]([^'"]+)['"] +`, ``, navigation config objects) + - Navigation items often represent primary workflows + +### Step 3.3 – Examine Feature Module Directories + +Use Glob to identify feature-based directory structures: +``` +src/features/*/ +src/modules/*/ +src/pages/*/ +``` + +For each feature directory found: +- Extract directory name as potential workflow name +- List key components/pages in that directory +- Identify the entry point (index file or main component) + +### Step 3.4 – Check for User Flow Documentation + +Look for existing workflow documentation: +- `docs/user-flows.md`, `docs/workflows.md`, `docs/user-journeys.md` +- README files in feature directories +- Comments in route configuration describing user flows + +If found, read these files to supplement discovered workflows. + +### Step 3.5 – Synthesize Discovered Workflows + +For each inferred workflow: + +**Extract:** +- **Workflow name** — descriptive name (e.g., "SBOM Upload and Analysis") +- **Entry point URL** — the first route in the workflow (e.g., `/sbom/upload`) +- **Key screens** — list of route paths that form the workflow +- **Component references** — file paths of key components in the workflow +- **Estimated complexity** — rough estimate based on: + - Number of routes in workflow (1-2 routes = Simple, 3-5 = Moderate, 6+ = Complex) + - Number of components referenced + - Presence of API calls (search for `fetch`, `axios`, `useQuery`, API client usage in components) + +**If no workflows discovered:** + +Inform the user: +> "No workflows could be auto-discovered from the codebase. This may happen if:" +> - The application uses a non-standard routing structure +> - Routes are dynamically generated +> - The router configuration uses complex patterns +> +> "You can manually define workflows by editing `.claude/performance-config.md` and adding entries to the Performance Scenarios table." + +Stop execution. + +## Step 4 – Present Discovered Workflows to User + +Display discovered workflows in a formatted table. + +**Example output:** + +``` +## Discovered Workflows + +| # | Workflow Name | Entry Point | Key Screens | Complexity | +|---|---|---|---|---| +| 1 | SBOM Upload and Analysis | /sbom/upload | /sbom/upload, /sbom/:id, /sbom/:id/analysis | Moderate (5 components, 3 API calls) | +| 2 | Advisory Browse and Detail | /advisory/list | /advisory/list, /advisory/:id | Simple (2 components, 2 API calls) | +| 3 | Risk Assessment | /assessment | /assessment, /assessment/new, /assessment/:id/report | Complex (8 components, 6 API calls) | +``` + +**Table columns:** +- **#** — selection number +- **Workflow Name** — descriptive name inferred from route paths or directory structure +- **Entry Point** — starting URL for the workflow +- **Key Screens** — list of route paths in the workflow +- **Complexity** — Simple/Moderate/Complex with breakdown (X components, Y API calls) + +**Guidance to user:** + +> "These workflows represent distinct user journeys through your application. Select one workflow to optimize for performance. The selected workflow will be used for baseline capture and analysis." +> +> "**Recommendation:** Start with a Moderate complexity workflow that is business-critical. Simple workflows may not reveal performance bottlenecks, while Complex workflows can be overwhelming to analyze." + +## Step 5 – Prompt User to Select Workflow + +Prompt the user: + +> "Enter the number of the workflow you want to optimize (1-N):" + +**Validation:** +- Verify user input is a valid number within the range +- If invalid, re-prompt with error message: "Invalid selection. Please enter a number between 1 and N." + +**Capture selection:** +- Store the selected workflow's details (name, entry point, key screens, complexity) + +## Step 6 – Save Selection to Performance Configuration + +Read the current `.claude/performance-config.md` file. + +**Add a new section at the end:** + +```markdown +## Selected Workflow + +The following workflow has been selected for performance optimization: + +| Property | Value | +|---|---| +| Workflow Name | {selected workflow name} | +| Entry Point | {entry point URL} | +| Key Screens | {comma-separated list of key screens} | +| Complexity | {complexity estimate} | +| Selected On | {current date in YYYY-MM-DD format} | + +**Next Steps:** +1. Ensure your application is running locally with test data loaded for this workflow +2. Run `/sdlc-workflow:performance-baseline` to capture baseline metrics for this workflow +3. Run `/sdlc-workflow:performance-analyze-module` to analyze the workflow for performance bottlenecks +``` + +Write the updated configuration back to `.claude/performance-config.md`. + +## Step 7 – Output Summary + +Report to the user: + +> ✅ **Workflow selected and saved to configuration!** +> +> **Selected Workflow:** {workflow name} +> **Entry Point:** {entry point URL} +> **Complexity:** {complexity} +> +> **Next Steps:** +> +> 1. **Load test data** — Ensure your application has test data for this workflow to ensure consistent measurements +> 2. **Start your application** — Run your dev server (e.g., `npm run dev`) +> 3. **Capture baseline:** +> ``` +> /sdlc-workflow:performance-baseline +> ``` + +## Important Rules + +- Never modify source code files — only update `.claude/performance-config.md` +- Always discover workflows from actual source code — do not invent placeholder examples +- If workflow discovery finds no results, stop and inform the user rather than proceeding with empty data +- Validate that discovered routes reference real components/files before presenting them +- Present workflows in order of estimated business value/traffic (if determinable from route names) +- Ensure all URL paths are relative (no `http://` or `https://` — they should work with localhost) +- If the user wants to analyze multiple workflows, they must run this skill again after completing optimization of the first workflow From bf6dd78ce30b4efd322647774e5685e9e3454933 Mon Sep 17 00:00:00 2001 From: mrrajan <86094767+mrrajan@users.noreply.github.com.> Date: Thu, 16 Apr 2026 12:07:45 +0530 Subject: [PATCH 04/23] feat(performance): add baseline capture skill with test data verification Add performance-baseline skill that captures performance metrics for user-selected workflows with test data verification before measurement. Key features: - Verifies Performance Analysis Configuration and selected workflow exist - Prompts user to confirm test data availability before capture - Handles existing baselines with replace/cancel prompt - Copies capture script template from plugin cache - Executes browser automation to measure LCP, FCP, TTI, Total Load Time - Filters scenarios to include only selected workflow's key screens - Generates baseline report with metrics, resource timing, waterfall - Comprehensive error handling for common failures (app not running, Playwright not installed, invalid URLs, missing performance marks) - Updated docs/workflow.md with baseline capture skill documentation Implements TC-4133 Assisted-by: Claude Code --- docs/workflow.md | 48 ++ .../skills/performance-baseline/SKILL.md | 411 ++++++++++++++++++ 2 files changed, 459 insertions(+) create mode 100644 plugins/sdlc-workflow/skills/performance-baseline/SKILL.md diff --git a/docs/workflow.md b/docs/workflow.md index a338eb5f9..507932719 100644 --- a/docs/workflow.md +++ b/docs/workflow.md @@ -527,6 +527,54 @@ Analyzes frontend source code to identify functional workflows (user journeys), --- +### Baseline Capture + +**Skill:** `/sdlc-workflow:performance-baseline` + +Captures performance baseline metrics for the user-selected workflow by verifying test data availability, executing browser automation to measure page load times and resource loading, and generating a baseline report. + +**Invocation:** + +``` +/sdlc-workflow:performance-baseline +/sdlc-workflow:performance-baseline /path/to/target/repo +``` + +**Workflow:** +1. Determine target repository (argument or current directory) +2. Verify Performance Analysis Configuration exists and contains selected workflow +3. Prompt user to confirm test data availability (yes/no) + - If no: display message and exit gracefully + - If yes: proceed to baseline capture +4. Check if baseline already exists (baseline-report.md in configured location) + - If exists: prompt user to replace or cancel +5. Copy capture-baseline.template.mjs from plugin cache to target directory +6. Execute script via `node capture-baseline.mjs --config ../path/to/performance-config.md` +7. Parse JSON output and generate baseline-report.md from template +8. Filter scenarios to include only those in selected workflow +9. Save report to configured location (`.claude/performance/baselines/baseline-report.md`) +10. Output summary with key metrics (LCP, FCP, TTI, Total Load Time) and threshold warnings + +**Output:** +- `baseline-report.md` created in configured baseline directory +- Baseline includes: timestamp, workflow name, per-scenario metrics, resource timing breakdown, waterfall visualization +- Summary output with aggregate metrics and warnings for exceeded thresholds + +**Guardrails:** +- Requires Performance Analysis Configuration with selected workflow (prompts user to run performance-setup and performance-workflow-discovery if missing) +- Verifies test data availability before capturing baseline +- Does NOT modify source code — only creates performance measurement artifacts +- Handles errors gracefully: application not running, Playwright not installed, invalid URLs, missing performance marks +- Filters scenarios to include only those in selected workflow + +**Error Handling:** +- **Application not running:** Detects ECONNREFUSED and prompts user to start application +- **Playwright not installed:** Detects missing dependency and provides installation commands +- **Invalid URLs:** Validates localhost URLs and prompts user to fix configuration +- **Missing performance marks:** Detects metric collection failures and suggests checking browser console + +--- + ## Jira Task Structure Tasks generated by `plan-feature` follow a structured template with these sections: diff --git a/plugins/sdlc-workflow/skills/performance-baseline/SKILL.md b/plugins/sdlc-workflow/skills/performance-baseline/SKILL.md new file mode 100644 index 000000000..a46ef8b4e --- /dev/null +++ b/plugins/sdlc-workflow/skills/performance-baseline/SKILL.md @@ -0,0 +1,411 @@ +--- +name: performance-baseline +description: | + Capture performance baseline metrics for user-selected workflow by executing browser automation and generating a baseline report. +argument-hint: "[target-repository-path]" +--- + +# performance-baseline skill + +You are an AI performance baseline assistant. You capture initial performance metrics for a user-selected workflow by verifying test data availability, executing browser automation to measure page load times and resource loading, and generating a baseline report for comparison during optimization. + +## Guardrails + +- This skill creates files in designated performance directories (`.claude/performance/baselines/`) +- This skill does NOT modify source code files — only creates performance measurement artifacts +- This skill requires Performance Analysis Configuration with a selected workflow + +## Step 1 – Determine Target Repository + +If the user provided a repository path as an argument, use that as the target. Otherwise, use the current working directory. + +Verify the target directory exists and contains a frontend application (check for `package.json`, `src/`, or similar frontend indicators). + +## Step 2 – Verify Performance Configuration and Selected Workflow + +Check if `.claude/performance-config.md` exists in the target repository. + +- **If not exists:** Inform the user: + > "Performance Analysis Configuration not found. Please run `/sdlc-workflow:performance-setup` first to initialize the configuration, then re-run this skill." + + Stop execution. + +- **If exists:** Read the configuration file. + +### Step 2.1 – Check for Selected Workflow + +Search for a `## Selected Workflow` section in the configuration file. + +- **If not found:** Inform the user: + > "No workflow selected for optimization. Please run `/sdlc-workflow:performance-workflow-discovery` first to select a workflow, then re-run this skill." + + Stop execution. + +- **If found:** Extract the workflow details: + - Workflow Name + - Entry Point URL + - Key Screens (list of route paths) + - Complexity estimate + +Store these details for use in later steps. + +## Step 3 – Verify Test Data Availability + +Prompt the user to confirm test data availability: + +> "Does the application have test data loaded for workflow **{workflow name}**? (yes/no)" +> +> "Test data ensures consistent baseline measurements and avoids noise from empty-state UI." + +**If user responds "no":** + +Display message and exit: + +> "Please load test data for this workflow before capturing baseline. Test data ensures consistent measurements." +> +> "Run this skill again after loading test data." + +Stop execution. + +**If user responds "yes":** + +Proceed to Step 4. + +## Step 4 – Check for Existing Baseline + +Determine the baseline report location from the configuration file: + +Look for the **Target Directories** section and extract the baseline directory path (e.g., `.claude/performance/baselines/`). + +Construct the baseline report filename: `baseline-report.md` + +Check if the file exists at `{baseline-directory}/baseline-report.md`. + +- **If baseline exists:** Prompt the user: + > "A baseline report already exists. Would you like to:" + > + > "1. Replace - Overwrite the existing baseline with new measurements" + > "2. Cancel - Keep the existing baseline and exit" + > + > "Choose (1/2):" + + **If user chooses "2. Cancel":** + + Inform the user: + > "Baseline capture cancelled. The existing baseline will be used for analysis." + + Stop execution. + + **If user chooses "1. Replace":** + + Proceed to Step 5. + +- **If baseline does not exist:** Proceed to Step 5. + +## Step 5 – Prepare Capture Script + +### Step 5.1 – Locate Plugin Cache Template + +The capture script template is located in the plugin cache: + +``` +{plugin-cache}/sdlc-workflow/{version}/skills/performance/capture-baseline.template.mjs +``` + +Use the Read tool to verify the template exists at this path. If not found, inform the user: + +> "Capture script template not found in plugin cache. This may indicate a corrupted plugin installation. Please reinstall the sdlc-workflow plugin." + +Stop execution. + +### Step 5.2 – Copy Template to Target Directory + +Determine the target location for the script from the configuration: + +Read the **Target Directories** section and extract the baseline directory path. + +Copy the template file to the target directory: + +``` +cp {plugin-cache}/.../capture-baseline.template.mjs {baseline-directory}/capture-baseline.mjs +``` + +Make the script executable: + +``` +chmod +x {baseline-directory}/capture-baseline.mjs +``` + +## Step 6 – Execute Baseline Capture + +### Step 6.1 – Construct Command + +Build the command to execute the capture script: + +``` +node {baseline-directory}/capture-baseline.mjs --config {path-to-performance-config.md} +``` + +Note: The script will read the Performance Scenarios table from the config and measure all configured scenarios. The workflow selection is used for filtering during report generation (Step 7). + +### Step 6.2 – Execute Script and Handle Errors + +Execute the command using the Bash tool. + +**Error handling:** + +1. **Application not running (connection refused):** + + If the script outputs an error containing "ECONNREFUSED", "connection refused", or "Failed to connect": + + Inform the user: + > "❌ **Application not running**" + > + > "The script could not connect to the application. Please ensure:" + > - Your application is running locally (e.g., `npm run dev`) + > - The URLs in performance-config.md are correct + > - The port numbers match your running application + > + > "Start your application and re-run this skill." + + Stop execution. + +2. **Playwright not installed:** + + If the script outputs an error containing "Cannot find module '@playwright/test'" or "Playwright": + + Inform the user: + > "❌ **Playwright not installed**" + > + > "The browser automation library is not installed. Please run:" + > + > ``` + > cd {target-repository} + > npm install -D @playwright/test + > npx playwright install chromium + > ``` + > + > "Then re-run this skill." + + Stop execution. + +3. **Invalid URLs in configuration:** + + If the script outputs an error containing "Invalid URL", "URL validation failed", or "not a localhost URL": + + Inform the user: + > "❌ **Invalid URLs in configuration**" + > + > "The URLs in performance-config.md are invalid or not localhost URLs. Please review the Performance Scenarios table and ensure all URLs:" + > - Start with `/` (relative paths) or `http://localhost` or `http://127.0.0.1` + > - Include port numbers if needed (e.g., `/products` → `http://localhost:3000/products`) + > + > "Edit `.claude/performance-config.md` and re-run this skill." + + Stop execution. + +4. **Missing performance marks:** + + If the script outputs an error containing "performance mark", "LCP not available", or "metric collection failed": + + Inform the user: + > "❌ **Performance metrics unavailable**" + > + > "The script could not collect all performance metrics. This may happen if:" + > - Pages load too quickly (metrics not captured before page unload) + > - Pages have client-side errors preventing metric collection + > - Browser security policies block metric access + > + > "Check browser console for errors and re-run this skill." + + Stop execution. + +5. **Other errors:** + + If the script fails with any other error, display the error message to the user and stop execution. + +### Step 6.3 – Parse JSON Output + +The script outputs JSON to stdout with the following structure: + +```json +{ + "scenarios": [ + { + "name": "Scenario Name", + "url": "/path", + "metrics": { + "lcp": { "mean": 1234, "p50": 1200, "p95": 1500, "p99": 1600 }, + "fcp": { ... }, + "tti": { ... }, + "totalLoadTime": { ... } + }, + "resources": { + "scripts": { "count": 10, "items": [...] }, + "stylesheets": { "count": 5, "items": [...] }, + "images": { "count": 20, "items": [...] }, + "fetch": { "count": 3, "items": [...] } + } + } + ], + "aggregate": { + "lcp": { "mean": 1500, "p50": 1450, "p95": 1800, "p99": 1900 }, + ... + }, + "config": { + "iterations": 5, + "warmupRuns": 2 + } +} +``` + +Parse this JSON output and store it for use in Step 7. + +## Step 7 – Generate Baseline Report + +### Step 7.1 – Read Report Template + +Read the baseline report template from the plugin cache: + +``` +{plugin-cache}/sdlc-workflow/{version}/skills/performance/baseline-report.template.md +``` + +### Step 7.2 – Filter Scenarios by Selected Workflow + +From the parsed JSON output (Step 6.3), filter scenarios to include only those in the selected workflow's **Key Screens** list. + +Match scenario URLs against the Key Screens list extracted in Step 2.1. A scenario matches if its URL path matches any of the Key Screens paths (exact match or wildcard match for dynamic segments like `:id`). + +If no scenarios match, inform the user: + +> "⚠️ **No scenarios found for selected workflow**" +> +> "The selected workflow's Key Screens do not match any configured Performance Scenarios. This may happen if:" +> - The workflow was selected before scenarios were configured +> - The scenario URLs in performance-config.md don't match the workflow's routes +> +> "Please review `.claude/performance-config.md` and ensure the Performance Scenarios table includes the workflow's Key Screens." + +Stop execution. + +### Step 7.3 – Replace Template Placeholders + +Replace placeholders in the baseline report template with actual values from the parsed JSON: + +**Metadata:** +- `{{skill-name}}` → `"performance-baseline"` +- `{{iso-8601-timestamp}}` → Current timestamp in ISO 8601 format (e.g., `"2026-04-16T12:00:00Z"`) +- `{{repository-name}}` → Target repository directory name +- `{{capture-date}}` → Current date in YYYY-MM-DD format +- `{{iterations}}` → From `config.iterations` +- `{{warmup-runs}}` → From `config.warmupRuns` +- `{{scenario-count}}` → Number of filtered scenarios + +**Aggregate Metrics:** + +Use the aggregate metrics from the JSON output: +- `{{lcp-mean}}`, `{{lcp-p50}}`, `{{lcp-p95}}`, `{{lcp-p99}}` → From `aggregate.lcp` +- `{{fcp-mean}}`, `{{fcp-p50}}`, `{{fcp-p95}}`, `{{fcp-p99}}` → From `aggregate.fcp` +- `{{tti-mean}}`, `{{tti-p50}}`, `{{tti-p95}}`, `{{tti-p99}}` → From `aggregate.tti` +- `{{total-mean}}`, `{{total-p50}}`, `{{total-p95}}`, `{{total-p99}}` → From `aggregate.totalLoadTime` + +**Per-Scenario Metrics:** + +For each filtered scenario, create a section using the template's per-scenario structure. Replace: +- `{{scenario-N-name}}` → Scenario name +- `{{scenario-N-url}}` → Scenario URL +- `{{scenario-N-lcp-mean}}`, etc. → Scenario metrics +- `{{scenario-N-scripts-count}}` → From `scenario.resources.scripts.count` +- `{{scenario-N-stylesheets-count}}` → From `scenario.resources.stylesheets.count` +- `{{scenario-N-images-count}}` → From `scenario.resources.images.count` +- `{{scenario-N-fetch-count}}` → From `scenario.resources.fetch.count` +- `{{scenario-N-total-resources}}` → Sum of all resource counts + +**Resource Timing Breakdown:** + +Extract the top 10 resources by duration across all filtered scenarios, sorted descending by duration. Replace: +- `{{resource-N-name}}` → Resource URL (strip query strings for privacy) +- `{{resource-N-type}}` → Resource type (script, stylesheet, image, fetch) +- `{{resource-N-duration}}` → Load duration in ms +- `{{resource-N-size}}` → Transfer size in KB +- `{{resource-N-scenario}}` → Scenario name where this resource was loaded + +**Waterfall Visualization:** + +Generate an ASCII waterfall chart for the first scenario in the filtered list. Create a visual timeline showing when each resource loaded relative to page start. + +Example format: + +``` +0ms 500ms 1000ms 1500ms +|-------------------|-------------------|-------------------| +[====main.js========] (650ms) + [--styles.css--] (320ms) + [***logo.png***] (180ms) + [++++api/data++++] (420ms) + [====vendor.js========] (780ms) +``` + +Replace `{{waterfall-ascii-chart}}` with the generated chart. + +**Comparison with Previous Baseline:** + +If this is a re-baseline (an existing baseline was replaced), include a comparison section showing the delta between old and new metrics. Otherwise, replace `{{comparison-section}}` with: + +```markdown +_This is the initial baseline. Future re-baselines will show comparison here._ +``` + +### Step 7.4 – Write Report to File + +Write the generated report to the baseline directory: + +``` +{baseline-directory}/baseline-report.md +``` + +## Step 8 – Output Summary + +Report to the user: + +> ✅ **Baseline captured successfully!** +> +> **Workflow:** {workflow name} +> **Scenarios measured:** {scenario count} +> **Report location:** `.claude/performance/baselines/baseline-report.md` +> +> **Key Metrics (aggregate across {scenario count} scenarios):** +> - **LCP (Largest Contentful Paint):** {lcp-mean} ms (p95: {lcp-p95} ms) +> - **FCP (First Contentful Paint):** {fcp-mean} ms (p95: {fcp-p95} ms) +> - **TTI (Time to Interactive):** {tti-mean} ms (p95: {tti-p95} ms) +> - **Total Load Time:** {total-mean} ms (p95: {total-p95} ms) +> +> {threshold-warnings} +> +> **Next Steps:** +> +> 1. Review the baseline report for performance bottlenecks +> 2. Run module-level analysis: +> ``` +> /sdlc-workflow:performance-analyze-module +> ``` + +Where `{threshold-warnings}` includes warnings for metrics exceeding targets (if any): + +- If LCP p95 > 2500ms: "⚠️ LCP exceeds target (2.5s)" +- If FCP p95 > 1800ms: "⚠️ FCP exceeds target (1.8s)" +- If TTI p95 > 3500ms: "⚠️ TTI exceeds target (3.5s)" +- If Total Load Time p95 > 4000ms: "⚠️ Total Load Time exceeds target (4.0s)" + +## Important Rules + +- Never modify source code files — only create performance measurement artifacts +- Always verify selected workflow exists before proceeding +- Always prompt for test data availability before capturing baseline +- If script execution fails, provide actionable error messages with clear remediation steps +- Ensure all URLs are localhost-only for security +- Filter scenarios to include only those in the selected workflow +- Generate waterfall visualization using ASCII art — no external dependencies +- If re-baselining (replacing existing baseline), include comparison section with deltas +- Capture script location must be in the baseline directory, not the repository root From b8d4364062cb6e1e6c770d07771de023f2c13ab9 Mon Sep 17 00:00:00 2001 From: mrrajan <86094767+mrrajan@users.noreply.github.com.> Date: Thu, 16 Apr 2026 12:20:10 +0530 Subject: [PATCH 05/23] feat(performance): add module-level analysis skill with anti-pattern detection Add performance-analyze-module skill that performs deep performance analysis of user-selected workflows by examining bundle composition and detecting nine common performance anti-patterns: over-fetching, N+1 queries, waterfall loading, render-blocking resources, unused code, expensive re-renders, long tasks, layout thrashing, and missing lazy loading. The skill generates comprehensive analysis reports with severity classification, quantified impact estimates, and prioritized optimization recommendations scoped to the selected workflow. Jira-Issue-Id: TC-4134 Assisted-by: Claude Sonnet 4.5 --- docs/workflow.md | 49 ++ .../performance-analyze-module/SKILL.md | 558 ++++++++++++++++++ 2 files changed, 607 insertions(+) create mode 100644 plugins/sdlc-workflow/skills/performance-analyze-module/SKILL.md diff --git a/docs/workflow.md b/docs/workflow.md index 507932719..d91bd71b1 100644 --- a/docs/workflow.md +++ b/docs/workflow.md @@ -575,6 +575,55 @@ Captures performance baseline metrics for the user-selected workflow by verifyin --- +### Module-Level Analysis + +**Skill:** `/sdlc-workflow:performance-analyze-module` + +Performs deep analysis of the selected workflow by examining bundle composition, component render times, and detecting common performance anti-patterns. + +**Invocation:** + +``` +/sdlc-workflow:performance-analyze-module +/sdlc-workflow:performance-analyze-module /path/to/target/repo +``` + +**Workflow:** +1. Determine target repository (argument or current directory) +2. Verify Performance Analysis Configuration exists and contains selected workflow +3. Verify baseline report exists for selected workflow +4. Read baseline data (per-scenario metrics, resource timing, aggregate metrics) +5. Analyze bundle composition: + - Locate bundle stats (webpack/vite) if available + - Identify third-party libraries vs application code + - Calculate module-specific vs shared code ratio +6. Detect performance anti-patterns: + - **Over-fetching:** API responses include unused fields + - **N+1 queries:** Sequential API calls in loops + - **Waterfall loading:** Sequential resource dependency chains + - **Render-blocking resources:** Synchronous scripts or non-async CSS + - **Unused code:** Imported modules never called + - **Expensive re-renders:** Components missing memoization + - **Long tasks:** JavaScript execution blocks > 50ms + - **Layout thrashing:** Interleaved DOM read-write operations + - **Missing lazy loading:** Large components loaded eagerly +7. Generate workflow-analysis-report.md with severity classification and quantified impact +8. Save report to configured location (`.claude/performance/analysis/workflow-analysis-report.md`) + +**Output:** +- `workflow-analysis-report.md` created in configured analysis directory +- Report includes: overall performance rating, bundle composition breakdown, anti-pattern analysis with code snippets, prioritized optimization recommendations +- Summary output with key findings and top optimization opportunities + +**Guardrails:** +- Requires Performance Analysis Configuration with selected workflow and existing baseline (prompts user to run prerequisite skills if missing) +- Does NOT modify source code — only creates analysis artifacts +- All anti-pattern detection based on actual code search results — no fabricated findings +- Scope limited to selected workflow only +- If anti-pattern detection fails, documents failed steps in report rather than halting + +--- + ## Jira Task Structure Tasks generated by `plan-feature` follow a structured template with these sections: diff --git a/plugins/sdlc-workflow/skills/performance-analyze-module/SKILL.md b/plugins/sdlc-workflow/skills/performance-analyze-module/SKILL.md new file mode 100644 index 000000000..ffa92353b --- /dev/null +++ b/plugins/sdlc-workflow/skills/performance-analyze-module/SKILL.md @@ -0,0 +1,558 @@ +--- +name: performance-analyze-module +description: | + Perform deep analysis of selected workflow by examining bundle composition, component render times, and detecting performance anti-patterns. +argument-hint: "[target-repository-path]" +--- + +# performance-analyze-module skill + +You are an AI performance analysis assistant. You perform deep analysis of a user-selected workflow by examining bundle composition, component render times, and detecting common performance anti-patterns including over-fetching, N+1 queries, waterfall loading, render-blocking resources, unused code, and expensive re-renders. + +## Guardrails + +- This skill creates files in designated performance directories (`.claude/performance/analysis/`) +- This skill does NOT modify source code files — only creates performance analysis artifacts +- This skill requires Performance Analysis Configuration with a selected workflow and an existing baseline report + +## Step 1 – Determine Target Repository + +If the user provided a repository path as an argument, use that as the target. Otherwise, use the current working directory. + +Verify the target directory exists and contains a frontend application (check for `package.json`, `src/`, or similar frontend indicators). + +## Step 2 – Verify Performance Configuration and Selected Workflow + +Check if `.claude/performance-config.md` exists in the target repository. + +- **If not exists:** Inform the user: + > "Performance Analysis Configuration not found. Please run `/sdlc-workflow:performance-setup` first to initialize the configuration, then re-run this skill." + + Stop execution. + +- **If exists:** Read the configuration file. + +### Step 2.1 – Check for Selected Workflow + +Search for a `## Selected Workflow` section in the configuration file. + +- **If not found:** Inform the user: + > "No workflow selected for optimization. Please run `/sdlc-workflow:performance-workflow-discovery` first to select a workflow, then re-run this skill." + + Stop execution. + +- **If found:** Extract the workflow details: + - Workflow Name + - Entry Point URL + - Key Screens (list of route paths) + - Complexity estimate + +Store these details for use in later steps. + +## Step 3 – Verify Baseline Report Exists + +Determine the baseline report location from the configuration file: + +Look for the **Target Directories** section and extract the baseline directory path (e.g., `.claude/performance/baselines/`). + +Construct the baseline report filename: `baseline-report.md` + +Check if the file exists at `{baseline-directory}/baseline-report.md`. + +- **If baseline does not exist:** Inform the user: + > "Baseline report not found. Please run `/sdlc-workflow:performance-baseline` first to capture baseline metrics, then re-run this skill." + + Stop execution. + +- **If baseline exists:** Proceed to Step 4. + +## Step 4 – Read Baseline Data + +Read the baseline report at `{baseline-directory}/baseline-report.md`. + +Extract the following data for use in anti-pattern detection: + +**Per-scenario metrics:** +- Scenario name and URL +- LCP (Largest Contentful Paint) — mean, p50, p95, p99 +- FCP (First Contentful Paint) — mean, p50, p95, p99 +- TTI (Time to Interactive) — mean, p50, p95, p99 +- Total Load Time — mean, p50, p95, p99 +- Resource counts (scripts, stylesheets, images, fetch calls) + +**Resource timing breakdown:** +- Resource URLs (scripts, stylesheets, images, fetch) +- Load duration per resource +- Transfer size per resource +- Scenario where resource was loaded + +**Aggregate metrics:** +- Overall LCP, FCP, TTI, Total Load Time across all scenarios + +Store this data for use in Steps 5 and 6. + +## Step 5 – Analyze Bundle Composition + +Analyze the JavaScript bundle composition for the selected workflow. + +### Step 5.1 – Locate Bundle Stats (Optional) + +Check if the target repository has webpack or vite bundle stats: + +Common locations: +- `dist/stats.json` (webpack) +- `build/stats.json` (webpack) +- `.vite/stats.json` (vite) +- `stats.json` in the repository root + +If bundle stats exist, parse them to extract: +- Module names and sizes +- Third-party library dependencies +- Code-split chunk boundaries +- Source map for module-to-file mapping + +### Step 5.2 – Identify Third-Party Libraries + +Extract the list of JavaScript files loaded in the baseline scenarios (from resource timing breakdown). + +For each JavaScript file, classify it as: +- **Third-party library** — matches pattern `/node_modules/`, `/vendor/`, or known CDN domains +- **Application code** — all other JavaScript files + +Calculate: +- Total size of third-party libraries (sum of transfer sizes) +- Total size of application code +- Ratio of third-party to application code + +List the top 10 third-party libraries by size. + +### Step 5.3 – Calculate Module-Specific vs Shared Code Ratio + +If bundle stats are available, calculate the ratio of: +- **Module-specific code** — code only used by the selected workflow +- **Shared code** — code shared across multiple workflows/routes + +If bundle stats are not available, estimate by examining import patterns in the workflow's route components (see Step 6.5 for unused code detection approach). + +## Step 6 – Detect Performance Anti-Patterns + +For each anti-pattern, search the codebase for indicators and report findings with severity classification and quantified impact. + +### Step 6.1 – Over-Fetching Detection + +**Definition:** API responses include fields that are never used in the UI. + +**Detection approach:** + +1. **Identify API calls in workflow components:** + - Use Grep to search for `fetch(`, `axios.get(`, `useQuery(` in workflow route components + - Extract API endpoint URLs from the search results + - For each endpoint, identify the response schema (check TypeScript interfaces, OpenAPI specs, or sample responses in baseline data) + +2. **Analyze field usage in components:** + - For each API endpoint response schema, identify the fields returned + - Use Grep to search for usage of each field in the component that consumes the response + - Flag fields that appear in the response schema but are never referenced in the consuming code + +**Severity classification:** +- **High:** > 50% of response fields unused, or response size > 100KB with > 30% unused +- **Medium:** 25-50% of response fields unused, or response size > 50KB with 25-30% unused +- **Low:** < 25% of response fields unused + +**Quantified impact:** +- Estimated size savings: `(unused_fields / total_fields) * response_size` +- Estimated time savings: `estimated_size_savings / average_bandwidth` (assume 5 Mbps average) + +### Step 6.2 – N+1 Query Detection + +**Definition:** Sequential API calls in loops, leading to many round-trips. + +**Detection approach:** + +1. **Search for loops with API calls:** + - Use Grep to search for patterns: + - `.forEach(` or `.map(` or `for (` followed by `fetch(` or `axios.` within 10 lines + - `await` inside loops (indicator of sequential execution) + - Extract code snippets showing the loop and API call pattern + +2. **Verify sequential execution:** + - Check if the loop uses `await` (sequential) vs `Promise.all` (parallel) + - Flag sequential loops with > 5 iterations as high severity + +**Severity classification:** +- **High:** Loop with > 10 iterations calling API sequentially +- **Medium:** Loop with 5-10 iterations calling API sequentially +- **Low:** Loop with < 5 iterations calling API sequentially + +**Quantified impact:** +- Estimated time savings: `(n_iterations - 1) * average_api_latency` (assume 100ms average latency) + +### Step 6.3 – Waterfall Loading Detection + +**Definition:** Resources loaded sequentially due to dependency chains, delaying page interactivity. + +**Detection approach:** + +1. **Analyze resource timing from baseline:** + - Extract resource timing data from baseline report + - For each resource, identify dependencies (resources that must load before this one) + - Build a dependency graph of resource loading + +2. **Detect sequential chains:** + - Identify chains of > 3 resources loading sequentially (each starting after the previous completes) + - Calculate the waterfall depth (longest sequential chain) + - Flag chains where total load time exceeds 500ms + +**Severity classification:** +- **High:** Waterfall depth > 5, or total chain time > 1000ms +- **Medium:** Waterfall depth 4-5, or total chain time 500-1000ms +- **Low:** Waterfall depth 3, or total chain time < 500ms + +**Quantified impact:** +- Estimated time savings: `total_chain_time - (slowest_resource_time * 1.2)` (assume 20% overhead for parallel loading) + +### Step 6.4 – Render-Blocking Resources Detection + +**Definition:** Synchronous scripts or non-async CSS in the critical rendering path, delaying FCP/LCP. + +**Detection approach:** + +1. **Search for synchronous script tags:** + - Use Grep to search for `