Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Generate realistic performance test scenarios with load profiles, ramp-up patterns, think times, and acceptance criteria derived from production traffic analysis
.claude/skills/pramoddutta-performance-test-scenario-generator/SKILL.md| Model | Eval pass | Runs |
|---|---|---|
| gemini-3.6-flash | 100% | 41 |
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 255% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 357% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 524% | 0% |
| case-19 | ✓→✗ | ▼ Worse | 332% | 0% |
| case-06 | ✓→✓ | = Same ✓ | 282% | 0% |
Performance testing validates that a system meets speed, scalability, and stability requirements under expected and extreme load conditions. The difference between a performance test that provides actionable insights and one that generates misleading data comes down to scenario design. Realistic scenarios mirror actual user behavior, incorporate proper think times, follow genuine navigation patterns, and simulate the mix of operations that production traffic exhibits. This skill guides AI coding agents through generating performance test scenarios that produce trustworthy, actionable results.
performance-tests/
├── src/
│ ├── scenarios/
│ │ ├── browse-catalog.ts
│ │ ├── search-and-filter.ts
│ │ ├── checkout-flow.ts
│ │ ├── api-crud-operations.ts
│ │ └── user-registration.ts
│ ├── profiles/
│ │ ├── load-test.ts
│ │ ├── stress-test.ts
│ │ ├── spike-test.ts
│ │ ├── soak-test.ts
│ │ └── breakpoint-test.ts
│ ├── helpers/
│ │ ├── auth.ts
│ │ ├── data-generators.ts
│ │ ├── correlation.ts
│ │ └── think-time.ts
│ ├── thresholds/
│ │ └── sla-definitions.ts
│ └── data/
│ ├── users.csv
│ ├── products.json
│ └── search-terms.csv
├── jmeter/
│ ├── test-plans/
│ │ ├── load-test.jmx
│ │ └── stress-test.jmx
│ ├── data/
│ │ └── users.csv
│ └── scripts/
│ └── run-test.sh
├── results/
│ └── .gitkeep
├── dashboards/
│ └── grafana-k6-dashboard.json
├── k6.config.ts
└── package.jsonA constant load profile maintains a fixed number of virtual users throughout the test duration. This is the simplest profile, useful for establishing baseline performance metrics.
typescript// src/profiles/constant-load.ts import http from 'k6/http'; import { check, sleep } from 'k6'; import { Options } from 'k6/options'; export const options: Options = { scenarios: { constant_load: { executor: 'constant-vus', vus: 50, duration: '10m', }, }, thresholds: { http_req_duration: ['p(95)<500', 'p(99)<1500'], http_req_failed: ['rate<0.01'], http_reqs: ['rate>100'], }, }; export default function () { const res = http.get('https://api.example.com/products'); check(res, { 'status is 200': (r) => r.status === 200, 'response time < 500ms': (r) => r.timings.duration < 500, }); sleep(Math.random() * 3 + 1); // 1-4 second think time }
Ramp-up profiles gradually increase load to identify the point at which performance degrades. This is the most common pattern for standard load tests.
typescript// src/profiles/load-test.ts import http from 'k6/http'; import { check, sleep } from 'k6'; import { Options } from 'k6/options'; import { browseCatalog } from '../scenarios/browse-catalog'; import { searchAndFilter } from '../scenarios/search-and-filter'; import { checkoutFlow } from '../scenarios/checkout-flow'; export const options: Options = { stages: [ { duration: '2m', target: 50 }, // Ramp up to 50 users { duration: '5m', target: 50 }, // Hold at 50 users { duration: '2m', target: 100 }, // Ramp up to 100 users { duration: '5m', target: 100 }, // Hold at 100 users { duration: '2m', target: 200 }, // Ramp up to 200 users { duration: '5m', target: 200 }, // Hold at 200 users (peak) { duration: '3m', target: 0 }, // Ramp down to 0 ], thresholds: { http_req_duration: ['p(95)<800', 'p(99)<2000'], http_req_failed: ['rate<0.02'], 'http_req_duration{scenario:browse}': ['p(95)<600'], 'http_req_duration{scenario:checkout}': ['p(95)<1200'], }, }; export default function () { const scenario = weightedScenario(); scenario(); } function weightedScenario(): () => void { const rand = Math.random() * 100; if (rand < 60) return browseCatalog; // 60% browse if (rand < 85) return searchAndFilter; // 25% search return checkoutFlow; // 15% checkout }
Spike tests simulate sudden, dramatic increases in load to verify system behavior under burst conditions, such as a flash sale or breaking news event.
typescript// src/profiles/spike-test.ts import { Options } from 'k6/options'; export const options: Options = { stages: [ { duration: '2m', target: 50 }, // Normal load { duration: '5m', target: 50 }, // Steady normal { duration: '30s', target: 500 }, // Spike to 10x { duration: '3m', target: 500 }, // Hold spike { duration: '30s', target: 50 }, // Drop back to normal { duration: '5m', target: 50 }, // Recovery observation { duration: '2m', target: 0 }, // Ramp down ], thresholds: { http_req_duration: ['p(95)<3000'], // Relaxed during spike http_req_failed: ['rate<0.05'], // Allow up to 5% errors during spike http_req_duration: ['p(50)<1000'], // Median should remain reasonable }, };
Stress tests push beyond expected peak load to find the system breaking point.
typescript// src/profiles/stress-test.ts import { Options } from 'k6/options'; export const options: Options = { scenarios: { stress: { executor: 'ramping-arrival-rate', startRate: 10, timeUnit: '1s', preAllocatedVUs: 500, maxVUs: 2000, stages: [ { duration: '2m', target: 10 }, // Warm up: 10 req/s { duration: '5m', target: 50 }, // Normal: 50 req/s { duration: '5m', target: 100 }, // High: 100 req/s { duration: '5m', target: 200 }, // Very high: 200 req/s { duration: '5m', target: 500 }, // Extreme: 500 req/s { duration: '5m', target: 1000 }, // Breaking point search { duration: '3m', target: 0 }, // Recovery ], }, }, thresholds: { http_req_failed: ['rate<0.10'], http_req_duration: ['p(95)<5000'], }, };
Soak tests run at moderate load for extended periods to detect memory leaks, connection pool exhaustion, and resource degradation.
typescript// src/profiles/soak-test.ts import { Options } from 'k6/options'; export const options: Options = { stages: [ { duration: '5m', target: 100 }, // Ramp up { duration: '8h', target: 100 }, // Sustained moderate load for 8 hours { duration: '5m', target: 0 }, // Ramp down ], thresholds: { http_req_duration: ['p(95)<800'], http_req_failed: ['rate<0.01'], // Track that performance does not degrade over time 'http_req_duration{window:last_30m}': ['p(95)<1000'], }, };
typescript// src/scenarios/browse-catalog.ts import http from 'k6/http'; import { check, group, sleep } from 'k6'; import { Trend, Counter } from 'k6/metrics'; import { thinkTime, shortPause, readingTime } from '../helpers/think-time'; const catalogBrowseTime = new Trend('catalog_browse_time'); const itemsViewed = new Counter('items_viewed'); export function browseCatalog(): void { group('Browse Catalog Flow', () => { // Step 1: Visit homepage group('01_Homepage', () => { const homeRes = http.get('https://store.example.com/', { tags: { scenario: 'browse', step: 'homepage' }, }); check(homeRes, { 'homepage loaded': (r) => r.status === 200, 'homepage size reasonable': (r) => r.body!.length > 1000, }); readingTime(2, 5); // User reads homepage for 2-5 seconds }); // Step 2: Browse a category group('02_Category', () => { const categories = ['electronics', 'clothing', 'home', 'books']; const category = categories[Math.floor(Math.random() * categories.length)]; const catRes = http.get(`https://store.example.com/category/${category}?page=1&limit=20`, { tags: { scenario: 'browse', step: 'category' }, }); check(catRes, { 'category loaded': (r) => r.status === 200, }); readingTime(3, 8); // User browses category listing }); // Step 3: View 2-4 product details group('03_Product_Details', () => { const numProducts = Math.floor(Math.random() * 3) + 2; for (let i = 0; i < numProducts; i++) { const productId = Math.floor(Math.random() * 1000) + 1; const prodRes = http.get(`https://store.example.com/api/products/${productId}`, { tags: { scenario: 'browse', step: 'product_detail' }, }); check(prodRes, { 'product loaded': (r) => r.status === 200 || r.status === 404, }); catalogBrowseTime.add(prodRes.timings.duration); itemsViewed.add(1); readingTime(5, 15); // User reads product description } }); // Step 4: Some users add to cart (30% probability) if (Math.random() < 0.3) { group('04_Add_to_Cart', () => { const addRes = http.post( 'https://store.example.com/api/cart', JSON.stringify({ productId: Math.floor(Math.random() * 1000) + 1, quantity: 1, }), { headers: { 'Content-Type': 'application/json' }, tags: { scenario: 'browse', step: 'add_to_cart' }, } ); check(addRes, { 'item added to cart': (r) => r.status === 200 || r.status === 201, }); shortPause(); // Brief pause after action }); } }); }
typescript// src/helpers/think-time.ts import { sleep } from 'k6'; /** * Simulate user think time with normal distribution. * Real users don't pause for exact durations; they follow a distribution. */ export function thinkTime(minSeconds: number, maxSeconds: number): void { const mean = (minSeconds + maxSeconds) / 2; const stdDev = (maxSeconds - minSeconds) / 6; const duration = normalRandom(mean, stdDev); sleep(Math.max(minSeconds, Math.min(maxSeconds, duration))); } /** * Short pause for page transitions or button clicks (0.5-2 seconds). */ export function shortPause(): void { sleep(Math.random() * 1.5 + 0.5); } /** * Reading time: simulates user reading content on a page. * Duration varies based on content length. */ export function readingTime(minSeconds: number, maxSeconds: number): void { thinkTime(minSeconds, maxSeconds); } /** * Form fill time: simulates user filling out a form. * Typically 10-30 seconds depending on form complexity. */ export function formFillTime(fieldCount: number): void { const timePerField = Math.random() * 3 + 2; // 2-5 seconds per field sleep(fieldCount * timePerField); } function normalRandom(mean: number, stdDev: number): number { // Box-Muller transform for normal distribution const u1 = Math.random(); const u2 = Math.random(); const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2); return mean + z * stdDev; }
typescript// src/helpers/data-generators.ts import { SharedArray } from 'k6/data'; import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js'; // Load CSV data shared across all VUs (memory efficient) const users = new SharedArray('users', function () { return papaparse.parse(open('../data/users.csv'), { header: true }).data; }); const searchTerms = new SharedArray('search_terms', function () { return papaparse.parse(open('../data/search-terms.csv'), { header: true }).data; }); const products = new SharedArray('products', function () { return JSON.parse(open('../data/products.json')); }); export function getRandomUser(): { username: string; password: string } { return users[Math.floor(Math.random() * users.length)]; } export function getRandomSearchTerm(): string { return searchTerms[Math.floor(Math.random() * searchTerms.length)].term; } export function getRandomProduct(): { id: string; name: string; price: number } { return products[Math.floor(Math.random() * products.length)]; } /** * Returns a unique user per VU to avoid session conflicts. * Uses __VU (virtual user number) as the index. */ export function getUserForVU(): { username: string; password: string } { return users[(__VU - 1) % users.length]; }
typescript// src/helpers/correlation.ts import http from 'k6/http'; import { check } from 'k6'; /** * Extract dynamic values from responses for use in subsequent requests. * Common in web applications that use CSRF tokens, session IDs, or * pagination cursors. */ export function extractCsrfToken(response: any): string { const match = response.body.match(/name="csrf_token"\s+value="([^"]+)"/); if (!match) { console.error('CSRF token not found in response'); return ''; } return match[1]; } export function extractSessionId(response: any): string { const cookies = response.cookies; if (cookies && cookies['session_id'] && cookies['session_id'].length > 0) { return cookies['session_id'][0].value; } return ''; } export function extractPaginationCursor(response: any): string | null { try { const body = JSON.parse(response.body); return body.pagination?.nextCursor || null; } catch { return null; } } /** * Complete login flow with correlation: * 1. GET login page to extract CSRF token * 2. POST credentials with extracted token * 3. Return session cookies for subsequent requests */ export function authenticatedSession( baseUrl: string, username: string, password: string ): { headers: Record<string, string> } { // Step 1: Get login page and extract CSRF token const loginPage = http.get(`${baseUrl}/login`); const csrfToken = extractCsrfToken(loginPage); // Step 2: Submit login with correlated token const loginRes = http.post( `${baseUrl}/api/auth/login`, JSON.stringify({ username, password, csrf_token: csrfToken }), { headers: { 'Content-Type': 'application/json' } } ); check(loginRes, { 'login successful': (r) => r.status === 200, }); // Step 3: Extract auth token from response const authToken = JSON.parse(loginRes.body as string).token; return { headers: { Authorization: `Bearer ${authToken}`, 'Content-Type': 'application/json', }, }; }
typescript// src/thresholds/sla-definitions.ts import { Options } from 'k6/options'; /** * Thresholds derived from business SLA requirements. * Each threshold maps to a specific business metric. */ export const slaThresholds: Options['thresholds'] = { // Global response time SLAs http_req_duration: [ 'p(50)<300', // 50th percentile under 300ms 'p(90)<800', // 90th percentile under 800ms 'p(95)<1500', // 95th percentile under 1.5s 'p(99)<3000', // 99th percentile under 3s 'max<10000', // No request exceeds 10s ], // Error rate SLA http_req_failed: [ 'rate<0.01', // Less than 1% error rate ], // Throughput SLA http_reqs: [ 'rate>50', // Minimum 50 requests per second ], // Scenario-specific thresholds 'http_req_duration{scenario:browse}': ['p(95)<600'], 'http_req_duration{scenario:search}': ['p(95)<400'], 'http_req_duration{scenario:checkout}': ['p(95)<2000'], 'http_req_duration{scenario:api_crud}': ['p(95)<300'], // Custom metrics 'catalog_browse_time': ['avg<500', 'p(95)<1000'], 'checkout_completion_time': ['avg<3000', 'p(95)<5000'], };
typescript// src/profiles/realistic-mix.ts import { Options } from 'k6/options'; import { browseCatalog } from '../scenarios/browse-catalog'; import { searchAndFilter } from '../scenarios/search-and-filter'; import { checkoutFlow } from '../scenarios/checkout-flow'; import { apiCrudOperations } from '../scenarios/api-crud-operations'; import { userRegistration } from '../scenarios/user-registration'; /** * Realistic scenario mix based on production traffic analysis: * - 50% browse catalog (highest traffic) * - 25% search and filter * - 15% API CRUD operations (mobile app) * - 8% checkout flow * - 2% user registration */ export const options: Options = { scenarios: { browse: { executor: 'ramping-vus', startVUs: 0, stages: [ { duration: '2m', target: 50 }, { duration: '10m', target: 50 }, { duration: '2m', target: 0 }, ], exec: 'browseCatalogScenario', tags: { scenario: 'browse' }, }, search: { executor: 'ramping-vus', startVUs: 0, stages: [ { duration: '2m', target: 25 }, { duration: '10m', target: 25 }, { duration: '2m', target: 0 }, ], exec: 'searchScenario', tags: { scenario: 'search' }, }, api_crud: { executor: 'constant-arrival-rate', rate: 30, timeUnit: '1s', duration: '14m', preAllocatedVUs: 50, exec: 'apiCrudScenario', tags: { scenario: 'api_crud' }, }, checkout: { executor: 'ramping-vus', startVUs: 0, stages: [ { duration: '2m', target: 8 }, { duration: '10m', target: 8 }, { duration: '2m', target: 0 }, ], exec: 'checkoutScenario', tags: { scenario: 'checkout' }, }, registration: { executor: 'per-vu-iterations', vus: 2, iterations: 10, exec: 'registrationScenario', tags: { scenario: 'registration' }, }, }, }; export function browseCatalogScenario() { browseCatalog(); } export function searchScenario() { searchAndFilter(); } export function apiCrudScenario() { apiCrudOperations(); } export function checkoutScenario() { checkoutFlow(); } export function registrationScenario() { userRegistration(); }
typescript// src/profiles/geo-distributed.ts import { Options } from 'k6/options'; /** * Simulate traffic from multiple geographic regions. * In k6 Cloud, this maps to load zones. Locally, it uses * different scenario weights to approximate geographic patterns. */ export const options: Options = { scenarios: { us_east_traffic: { executor: 'ramping-vus', startVUs: 0, stages: [ { duration: '2m', target: 40 }, { duration: '10m', target: 40 }, { duration: '2m', target: 0 }, ], exec: 'usEastTraffic', env: { REGION: 'us-east-1' }, }, eu_west_traffic: { executor: 'ramping-vus', startVUs: 0, stages: [ { duration: '2m', target: 30 }, { duration: '10m', target: 30 }, { duration: '2m', target: 0 }, ], exec: 'euWestTraffic', env: { REGION: 'eu-west-1' }, }, ap_southeast_traffic: { executor: 'ramping-vus', startVUs: 0, stages: [ { duration: '2m', target: 20 }, { duration: '10m', target: 20 }, { duration: '2m', target: 0 }, ], exec: 'apSoutheastTraffic', env: { REGION: 'ap-southeast-1' }, }, }, };
typescript// src/helpers/monitoring.ts import http from 'k6/http'; import { Trend, Gauge } from 'k6/metrics'; const serverCpuUsage = new Gauge('server_cpu_usage'); const serverMemoryUsage = new Gauge('server_memory_usage'); const dbConnectionPool = new Gauge('db_connection_pool_active'); const cacheHitRate = new Gauge('cache_hit_rate'); /** * Periodically poll server metrics during the test. * This provides correlation between client-side response times * and server-side resource utilization. */ export function collectServerMetrics(metricsEndpoint: string): void { const res = http.get(metricsEndpoint, { tags: { purpose: 'monitoring' }, timeout: '5s', }); if (res.status === 200) { try { const metrics = JSON.parse(res.body as string); serverCpuUsage.add(metrics.cpu_percent || 0); serverMemoryUsage.add(metrics.memory_percent || 0); dbConnectionPool.add(metrics.db_connections_active || 0); cacheHitRate.add(metrics.cache_hit_rate || 0); } catch (e) { // Silently skip if metrics endpoint returns unexpected format } } }
bash#!/bin/bash # jmeter/scripts/run-test.sh JMETER_HOME=${JMETER_HOME:-/opt/jmeter} TEST_PLAN=$1 RESULTS_DIR="results/$(date +%Y%m%d_%H%M%S)" mkdir -p "$RESULTS_DIR" # Run JMeter in non-GUI mode $JMETER_HOME/bin/jmeter \ -n \ -t "jmeter/test-plans/${TEST_PLAN}.jmx" \ -l "$RESULTS_DIR/results.jtl" \ -e \ -o "$RESULTS_DIR/report" \ -Jthreads=100 \ -Jrampup=120 \ -Jduration=600 \ -Jbase_url=https://api.example.com \ -j "$RESULTS_DIR/jmeter.log" echo "Results saved to $RESULTS_DIR" echo "HTML report: $RESULTS_DIR/report/index.html"
java// JMeter BeanShell PostProcessor for dynamic value extraction import org.json.JSONObject; String responseBody = prev.getResponseDataAsString(); JSONObject json = new JSONObject(responseBody); // Extract and store for next request String authToken = json.getString("token"); vars.put("auth_token", authToken); String userId = json.getString("userId"); vars.put("user_id", userId); // Extract pagination cursor if (json.has("pagination")) { JSONObject pagination = json.getJSONObject("pagination"); if (pagination.has("nextCursor")) { vars.put("next_cursor", pagination.getString("nextCursor")); } } log.info("Extracted auth_token: " + authToken.substring(0, 10) + "...");
typescript// src/helpers/custom-summary.ts import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; export function handleSummary(data: any): { [key: string]: string } { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); return { // Console output stdout: textSummary(data, { indent: ' ', enableColors: true }), // JSON results for CI/CD parsing [`results/summary-${timestamp}.json`]: JSON.stringify(data, null, 2), // Custom threshold report [`results/threshold-report-${timestamp}.txt`]: generateThresholdReport(data), }; } function generateThresholdReport(data: any): string { const lines: string[] = ['PERFORMANCE TEST THRESHOLD REPORT', '='.repeat(50), '']; for (const [metric, thresholds] of Object.entries(data.metrics)) { const metricData = thresholds as any; if (metricData.thresholds) { for (const [threshold, passed] of Object.entries(metricData.thresholds)) { const status = (passed as any).ok ? 'PASS' : 'FAIL'; lines.push(`[${status}] ${metric}: ${threshold}`); } } } return lines.join('\n'); }
typescript// k6.config.ts import { Options } from 'k6/options'; const config: Options = { // Default thresholds applied to all tests thresholds: { http_req_duration: ['p(95)<1000'], http_req_failed: ['rate<0.02'], }, // Tags applied to all requests tags: { environment: __ENV.TEST_ENV || 'staging', testRun: __ENV.TEST_RUN_ID || 'local', }, // DNS caching to simulate browser behavior dns: { ttl: '5m', select: 'roundRobin', policy: 'preferIPv4', }, // TLS configuration tlsAuth: [], insecureSkipTLSVerify: __ENV.TEST_ENV === 'local', // Connection reuse (simulates keep-alive) noConnectionReuse: false, // User agent userAgent: 'k6-performance-test/1.0', }; export default config;
bash# Basic load test k6 run src/profiles/load-test.ts # With environment variables k6 run --env BASE_URL=https://staging.example.com \ --env TEST_ENV=staging \ src/profiles/load-test.ts # Output to multiple destinations k6 run --out json=results/output.json \ --out influxdb=http://localhost:8086/k6 \ src/profiles/load-test.ts # Cloud execution (k6 Cloud) k6 cloud src/profiles/load-test.ts
k6 run --vus 1 --iterations 1 to catch script errors, authentication issues, and URL problems before scaling up.--http-debug="full" to see complete request and response bodies during development. Remove this flag for actual test runs.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-06 | pass→pass | 23,226 | 20,439 | -12% | 1 | 1 | 0% | 3,224 | 12,314 | +282% | 0 | 0 | — |
case-01 | fail→pass | 29,630 | 29,074 | -2% | 1 | 1 | 0% | 3,713 | 13,172 | +255% | 0 | 0 | — |
case-02 | pass→pass | 22,139 | 19,967 | -10% | 1 | 1 | 0% | 3,146 | 11,607 | +269% | 0 | 0 | — |
case-03 | pass→pass | 22,016 | 23,536 | +7% | 1 | 1 | 0% | 3,172 | 12,335 | +289% | 0 | 0 | — |
case-04 | pass→pass | 21,143 | 27,528 | +30% | 1 | 1 | 0% | 2,615 | 11,292 | +332% | 0 | 0 | — |
case-05 | fail→pass | 19,749 | 18,249 | -8% | 1 | 1 | 0% | 2,418 | 11,058 | +357% | 0 | 0 | — |
case-07 | pass→pass | 14,805 | 15,041 | +2% | 1 | 1 | 0% | 1,785 | 10,549 | +491% | 0 | 0 | — |
case-08 | pass→pass | 18,899 | 21,330 | +13% | 1 | 1 | 0% | 2,921 | 12,960 | +344% | 0 | 0 | — |
case-09 | fail→fail | 32,068 | 28,271 | -12% | 1 | 1 | 0% | 4,580 | 12,814 | +180% | 0 | 0 | — |
case-10 | pass→pass | 22,719 | 32,332 | +42% | 1 | 1 | 0% | 2,967 | 11,855 | +300% | 0 | 0 | — |
case-11 | pass→pass | 20,224 | 25,399 | +26% | 1 | 1 | 0% | 2,754 | 12,551 | +356% | 0 | 0 | — |
case-12 | fail→fail | 27,650 | 20,653 | -25% | 1 | 1 | 0% | 3,463 | 11,561 | +234% | 0 | 0 | — |
case-13 | pass→pass | 12,000 | 17,153 | +43% | 1 | 1 | 0% | 2,010 | 11,111 | +453% | 0 | 0 | — |
case-14 | pass→pass | 24,715 | 21,136 | -14% | 1 | 1 | 0% | 2,893 | 11,427 | +295% | 0 | 0 | — |
case-15 | pass→pass | 13,918 | 10,096 | -27% | 1 | 1 | 0% | 1,379 | 9,459 | +586% | 0 | 0 | — |
case-16 | pass→pass | 24,500 | 15,720 | -36% | 1 | 1 | 0% | 2,779 | 11,598 | +317% | 0 | 0 | — |
case-17 | pass→pass | 20,550 | 17,906 | -13% | 1 | 1 | 0% | 2,738 | 11,891 | +334% | 0 | 0 | — |
case-18 | fail→pass | 15,758 | 13,979 | -11% | 1 | 1 | 0% | 1,653 | 10,316 | +524% | 0 | 0 | — |
case-19 | pass→fail | 20,397 | 21,025 | +3% | 1 | 1 | 0% | 2,732 | 11,798 | +332% | 0 | 0 | — |
case-20 | pass→pass | 13,867 | 13,899 | +0% | 1 | 1 | 0% | 1,007 | 11,528 | +1045% | 0 | 0 | — |
case-21 | pass→pass | 13,893 | 17,433 | +25% | 1 | 1 | 0% | 1,630 | 10,416 | +539% | 0 | 0 | — |
case-22 | fail→fail | 18,888 | 20,121 | +7% | 1 | 1 | 0% | 2,708 | 11,746 | +334% | 0 | 0 | — |
case-23 | pass→pass | 12,537 | 13,377 | +7% | 1 | 1 | 0% | 1,597 | 10,184 | +538% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +9 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.