@intentsolutionsio/performance-test-suite 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,140 @@
1
+ ---
2
+ name: running-performance-tests
3
+ description: |
4
+ Execute load testing, stress testing, and performance benchmarking.
5
+ Use when performing specialized testing.
6
+ Trigger with phrases like "run load tests", "test performance", or "benchmark the system".
7
+
8
+ allowed-tools: Read, Write, Edit, Grep, Glob, Bash(test:perf-*)
9
+ version: 1.0.0
10
+ author: Jeremy Longshore <jeremy@intentsolutions.io>
11
+ license: MIT
12
+ compatible-with: claude-code, codex, openclaw
13
+ tags: [testing, performance, performance-tests]
14
+ ---
15
+ # Performance Test Suite
16
+
17
+ ## Overview
18
+
19
+ Execute load testing, stress testing, and performance benchmarking to identify bottlenecks, establish baseline metrics, and verify SLA compliance. Supports k6 (recommended), Artillery, Apache JMeter, Locust (Python), and autocannon (Node.js).
20
+
21
+ ## Prerequisites
22
+
23
+ - Performance testing tool installed (`k6`, `artillery`, `locust`, `jmeter`, or `autocannon`)
24
+ - Target application deployed in a production-like environment (not local dev)
25
+ - Baseline performance metrics or SLA targets (e.g., p95 < 200ms, 99.9% availability)
26
+ - Monitoring stack accessible (Grafana, CloudWatch, Datadog) for resource metrics during tests
27
+ - Test data sufficient to avoid cache-only responses
28
+
29
+ ## Instructions
30
+
31
+ 1. Define performance test scenarios based on production traffic patterns:
32
+ - **Load test**: Simulate expected peak traffic (e.g., 500 concurrent users for 10 minutes).
33
+ - **Stress test**: Ramp beyond expected capacity to find the breaking point.
34
+ - **Spike test**: Sudden burst of traffic (0 to 1000 users in 10 seconds).
35
+ - **Soak test**: Sustained moderate load for extended duration (1-4 hours) to detect memory leaks.
36
+ 2. Create test scripts targeting critical endpoints:
37
+ - Identify the top 5-10 most-hit API endpoints from production access logs.
38
+ - Include both read (GET) and write (POST/PUT/DELETE) operations.
39
+ - Simulate realistic user behavior with think time between requests.
40
+ - Use parameterized data to avoid cache-only hits (randomize query parameters, user IDs).
41
+ 3. Configure load profiles:
42
+ - Define virtual user (VU) ramp-up stages (e.g., 10 VUs for 1 minute, then 50 VUs for 5 minutes).
43
+ - Set test duration appropriate to the scenario (load: 10-15 min, soak: 1-4 hours).
44
+ - Configure request timeouts matching production settings.
45
+ 4. Execute the performance test:
46
+ - Run from a machine with sufficient network bandwidth and CPU.
47
+ - Avoid running from the same host as the application under test.
48
+ - Monitor application metrics (CPU, memory, DB connections) during execution.
49
+ 5. Analyze results against SLA thresholds:
50
+ - p50, p90, p95, p99 response times.
51
+ - Requests per second (throughput).
52
+ - Error rate (target: < 0.1% for load test, higher tolerance for stress test).
53
+ - Resource utilization (CPU < 80%, memory < 85% at peak load).
54
+ 6. Identify and document bottlenecks:
55
+ - Slow database queries (check slow query logs).
56
+ - CPU-bound operations (profiling data).
57
+ - Memory leaks (growing RSS over soak test).
58
+ - Connection pool exhaustion (database or HTTP client).
59
+ 7. Generate a performance report with visualizations and recommendations.
60
+
61
+ ## Output
62
+
63
+ - Performance test scripts (k6 `.js`, Artillery `.yml`, or Locust `.py` files)
64
+ - Execution results with response time percentiles, throughput, and error rates
65
+ - Performance report comparing results against SLA thresholds
66
+ - Bottleneck analysis with specific recommendations
67
+ - CI integration configuration for automated performance regression detection
68
+
69
+ ## Error Handling
70
+
71
+ | Error | Cause | Solution |
72
+ |-------|-------|---------|
73
+ | Connection reset by peer | Server or load balancer dropping connections under load | Check max connections settings; increase connection pool size; verify keep-alive configuration |
74
+ | Timeouts spike at certain VU count | Application thread pool or database connection pool exhausted | Profile connection usage; increase pool size; add connection queuing; optimize slow queries |
75
+ | Inconsistent results between runs | Cache warming, garbage collection pauses, or noisy neighbor effects | Run a warm-up phase before measurement; use dedicated test infrastructure; average across 3 runs |
76
+ | Load generator CPU maxed out | Single machine cannot generate sufficient load | Distribute load generation across multiple machines; use cloud-based load generation services |
77
+ | All requests return cached responses | Test data not sufficiently varied | Randomize request parameters; use unique IDs per request; disable CDN caching for test environment |
78
+
79
+ ## Examples
80
+
81
+ **k6 load test script:**
82
+ ```javascript
83
+ import http from 'k6/http';
84
+ import { check, sleep } from 'k6';
85
+
86
+ export const options = {
87
+ stages: [
88
+ { duration: '2m', target: 50 }, // Ramp up
89
+ { duration: '5m', target: 50 }, // Sustained load
90
+ { duration: '2m', target: 200 }, // Stress # HTTP 200 OK
91
+ { duration: '1m', target: 0 }, // Ramp down
92
+ ],
93
+ thresholds: {
94
+ http_req_duration: ['p(95)<200', 'p(99)<500'], # 500: HTTP 200 OK
95
+ http_req_failed: ['rate<0.01'],
96
+ },
97
+ };
98
+
99
+ export default function () {
100
+ const res = http.get('https://api.test.com/products');
101
+ check(res, {
102
+ 'status is 200': (r) => r.status === 200, # HTTP 200 OK
103
+ 'response time OK': (r) => r.timings.duration < 300, # 300: timeout: 5 minutes
104
+ });
105
+ sleep(1); // Think time
106
+ }
107
+ ```
108
+
109
+ **Artillery test configuration:**
110
+ ```yaml
111
+ config:
112
+ target: "https://api.test.com"
113
+ phases:
114
+ - duration: 120
115
+ arrivalRate: 10
116
+ name: "Warm up"
117
+ - duration: 300 # 300: timeout: 5 minutes
118
+ arrivalRate: 50
119
+ name: "Sustained load"
120
+ ensure:
121
+ p95: 200 # HTTP 200 OK
122
+ maxErrorRate: 1
123
+ scenarios:
124
+ - flow:
125
+ - get:
126
+ url: "/api/products"
127
+ - think: 1
128
+ - post:
129
+ url: "/api/cart"
130
+ json: { productId: "{{ $randomString() }}" }
131
+ ```
132
+
133
+ ## Resources
134
+
135
+ - k6 documentation: https://grafana.com/docs/k6/latest/
136
+ - Artillery: https://www.artillery.io/docs
137
+ - Locust (Python): https://docs.locust.io/
138
+ - Apache JMeter: https://jmeter.apache.org/
139
+ - autocannon (Node.js): https://github.com/mcollina/autocannon
140
+ - Performance testing best practices: https://grafana.com/blog/2024/01/30/load-testing-best-practices/
@@ -0,0 +1,7 @@
1
+ # Assets
2
+
3
+ Bundled resources for performance-test-suite skill
4
+
5
+ - [ ] test_template.js: Template file for creating k6 performance tests.
6
+ - [ ] report_template.html: HTML template for generating performance test reports.
7
+ - [ ] example_test_configurations.json: Example configurations for different types of performance tests.
@@ -0,0 +1,121 @@
1
+ [
2
+ {
3
+ "_comment": "Example configuration for a basic load test with gradual ramp-up",
4
+ "test_name": "Basic Load Test - Ramp Up",
5
+ "test_type": "load",
6
+ "description": "Simulates a gradual increase in users to test system responsiveness under increasing load.",
7
+ "target_url": "https://example.com/api/users",
8
+ "http_method": "GET",
9
+ "num_users": 100,
10
+ "ramp_up_time": 60,
11
+ "duration": 300,
12
+ "request_body": null,
13
+ "headers": {
14
+ "Content-Type": "application/json",
15
+ "Accept": "application/json"
16
+ },
17
+ "assertions": [
18
+ {
19
+ "type": "status_code",
20
+ "value": 200
21
+ },
22
+ {
23
+ "type": "response_time",
24
+ "max_ms": 500
25
+ }
26
+ ],
27
+ "metrics": [
28
+ "response_time",
29
+ "requests_per_second",
30
+ "error_rate"
31
+ ]
32
+ },
33
+ {
34
+ "_comment": "Example configuration for a stress test to find the breaking point",
35
+ "test_name": "Stress Test - Breaking Point",
36
+ "test_type": "stress",
37
+ "description": "Gradually increases load until the system reaches its breaking point.",
38
+ "target_url": "https://example.com/api/products",
39
+ "http_method": "POST",
40
+ "num_users": 500,
41
+ "ramp_up_time": 120,
42
+ "duration": 600,
43
+ "request_body": {
44
+ "product_name": "Test Product",
45
+ "price": 99.99
46
+ },
47
+ "headers": {
48
+ "Content-Type": "application/json"
49
+ },
50
+ "assertions": [
51
+ {
52
+ "type": "status_code",
53
+ "value": 201
54
+ }
55
+ ],
56
+ "metrics": [
57
+ "cpu_usage",
58
+ "memory_usage",
59
+ "database_connections",
60
+ "error_rate"
61
+ ]
62
+ },
63
+ {
64
+ "_comment": "Example configuration for a spike test simulating a flash sale",
65
+ "test_name": "Spike Test - Flash Sale",
66
+ "test_type": "spike",
67
+ "description": "Simulates a sudden surge in traffic to test system resilience.",
68
+ "target_url": "https://example.com/api/orders",
69
+ "http_method": "POST",
70
+ "num_users": 2000,
71
+ "ramp_up_time": 10,
72
+ "duration": 60,
73
+ "request_body": {
74
+ "user_id": 123,
75
+ "product_id": 456,
76
+ "quantity": 1
77
+ },
78
+ "headers": {
79
+ "Content-Type": "application/json"
80
+ },
81
+ "assertions": [
82
+ {
83
+ "type": "status_code",
84
+ "value": 201
85
+ }
86
+ ],
87
+ "metrics": [
88
+ "response_time",
89
+ "requests_per_second",
90
+ "error_rate",
91
+ "database_queue_length"
92
+ ]
93
+ },
94
+ {
95
+ "_comment": "Example configuration for an endurance test to check for memory leaks",
96
+ "test_name": "Endurance Test - Memory Leak Check",
97
+ "test_type": "endurance",
98
+ "description": "Runs a sustained load for an extended period to detect memory leaks and performance degradation.",
99
+ "target_url": "https://example.com/api/data",
100
+ "http_method": "GET",
101
+ "num_users": 50,
102
+ "ramp_up_time": 30,
103
+ "duration": 3600,
104
+ "request_body": null,
105
+ "headers": {
106
+ "Authorization": "Bearer <your_api_key>"
107
+ },
108
+ "assertions": [
109
+ {
110
+ "type": "status_code",
111
+ "value": 200
112
+ }
113
+ ],
114
+ "metrics": [
115
+ "memory_usage",
116
+ "cpu_usage",
117
+ "response_time",
118
+ "garbage_collection_count"
119
+ ]
120
+ }
121
+ ]
@@ -0,0 +1,203 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Performance Test Report</title>
7
+ <style>
8
+ /* Inline CSS for styling */
9
+ body {
10
+ font-family: sans-serif;
11
+ margin: 0;
12
+ padding: 0;
13
+ background-color: #f4f4f4;
14
+ color: #333;
15
+ }
16
+
17
+ .container {
18
+ width: 90%;
19
+ max-width: 1200px;
20
+ margin: 20px auto;
21
+ background-color: #fff;
22
+ padding: 20px;
23
+ border-radius: 8px;
24
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
25
+ }
26
+
27
+ h1, h2, h3 {
28
+ color: #0056b3;
29
+ margin-bottom: 10px;
30
+ }
31
+
32
+ table {
33
+ width: 100%;
34
+ border-collapse: collapse;
35
+ margin-top: 20px;
36
+ }
37
+
38
+ th, td {
39
+ padding: 10px;
40
+ text-align: left;
41
+ border-bottom: 1px solid #ddd;
42
+ }
43
+
44
+ th {
45
+ background-color: #f2f2f2;
46
+ }
47
+
48
+ .summary {
49
+ margin-top: 20px;
50
+ padding: 15px;
51
+ background-color: #e9ecef;
52
+ border-radius: 5px;
53
+ }
54
+
55
+ .summary p {
56
+ margin: 5px 0;
57
+ }
58
+
59
+ .charts {
60
+ display: flex;
61
+ flex-wrap: wrap;
62
+ justify-content: space-around;
63
+ margin-top: 20px;
64
+ }
65
+
66
+ .chart {
67
+ width: 45%;
68
+ min-width: 300px;
69
+ margin-bottom: 20px;
70
+ border: 1px solid #ddd;
71
+ padding: 10px;
72
+ border-radius: 5px;
73
+ box-sizing: border-box; /* Important for padding */
74
+ }
75
+
76
+ .chart img {
77
+ max-width: 100%;
78
+ height: auto;
79
+ }
80
+
81
+ @media (max-width: 768px) {
82
+ .container {
83
+ width: 95%;
84
+ }
85
+
86
+ .chart {
87
+ width: 100%;
88
+ }
89
+ }
90
+
91
+ .bottlenecks {
92
+ margin-top: 20px;
93
+ padding: 15px;
94
+ background-color: #ffebee;
95
+ border-radius: 5px;
96
+ border: 1px solid #ef9a9a;
97
+ }
98
+
99
+ .bottlenecks h3 {
100
+ color: #d32f2f;
101
+ }
102
+
103
+ .recommendations {
104
+ margin-top: 20px;
105
+ padding: 15px;
106
+ background-color: #e8f5e9;
107
+ border-radius: 5px;
108
+ border: 1px solid #a5d6a7;
109
+ }
110
+
111
+ .recommendations h3 {
112
+ color: #388e3c;
113
+ }
114
+ </style>
115
+ </head>
116
+ <body>
117
+
118
+ <!-- Main Container -->
119
+ <div class="container">
120
+
121
+ <!-- Report Header -->
122
+ <h1>Performance Test Report</h1>
123
+ <p>Test Run: {{test_run_id}}</p>
124
+ <p>Date: {{report_date}}</p>
125
+
126
+ <!-- Test Summary -->
127
+ <div class="summary">
128
+ <h2>Test Summary</h2>
129
+ <p><strong>Test Type:</strong> {{test_type}}</p>
130
+ <p><strong>Target URL:</strong> {{target_url}}</p>
131
+ <p><strong>Number of Users:</strong> {{number_of_users}}</p>
132
+ <p><strong>Duration:</strong> {{duration}}</p>
133
+ <p><strong>Status:</strong> {{test_status}}</p>
134
+ </div>
135
+
136
+ <!-- Key Metrics Table -->
137
+ <h2>Key Metrics</h2>
138
+ <table>
139
+ <thead>
140
+ <tr>
141
+ <th>Metric</th>
142
+ <th>Value</th>
143
+ </tr>
144
+ </thead>
145
+ <tbody>
146
+ <tr>
147
+ <td>Average Response Time</td>
148
+ <td>{{average_response_time}} ms</td>
149
+ </tr>
150
+ <tr>
151
+ <td>Throughput</td>
152
+ <td>{{throughput}} requests/second</td>
153
+ </tr>
154
+ <tr>
155
+ <td>Error Rate</td>
156
+ <td>{{error_rate}} %</td>
157
+ </tr>
158
+ <tr>
159
+ <td>95th Percentile Response Time</td>
160
+ <td>{{percentile_95_response_time}} ms</td>
161
+ </tr>
162
+ </tbody>
163
+ </table>
164
+
165
+ <!-- Charts -->
166
+ <div class="charts">
167
+ <div class="chart">
168
+ <h3>Response Time Over Time</h3>
169
+ <img src="{{response_time_chart_url}}" alt="Response Time Chart">
170
+ </div>
171
+ <div class="chart">
172
+ <h3>Throughput Over Time</h3>
173
+ <img src="{{throughput_chart_url}}" alt="Throughput Chart">
174
+ </div>
175
+ <!-- Add more charts as needed -->
176
+ </div>
177
+
178
+ <!-- Bottleneck Identification -->
179
+ <div class="bottlenecks">
180
+ <h3>Bottleneck Identification</h3>
181
+ <p>{{bottlenecks_summary}}</p>
182
+ <ul>
183
+ {{#bottlenecks}}
184
+ <li>{{.}}</li>
185
+ {{/bottlenecks}}
186
+ </ul>
187
+ </div>
188
+
189
+ <!-- Recommendations -->
190
+ <div class="recommendations">
191
+ <h3>Recommendations</h3>
192
+ <p>{{recommendations_summary}}</p>
193
+ <ul>
194
+ {{#recommendations}}
195
+ <li>{{.}}</li>
196
+ {{/recommendations}}
197
+ </ul>
198
+ </div>
199
+
200
+ </div>
201
+
202
+ </body>
203
+ </html>
@@ -0,0 +1,65 @@
1
+ // test_template.js - Template for K6 performance tests
2
+
3
+ // Import necessary modules from K6
4
+ import http from 'k6/http';
5
+ import { check, sleep } from 'k6';
6
+
7
+ // Configuration options
8
+ export const options = {
9
+ // Stages define the load pattern
10
+ stages: [
11
+ // Example: Ramp-up to 10 virtual users (VUs) over 10 seconds
12
+ { duration: '10s', target: 10 },
13
+
14
+ // Example: Maintain 10 VUs for 30 seconds
15
+ { duration: '30s', target: 10 },
16
+
17
+ // Example: Ramp-down to 0 VUs over 10 seconds
18
+ { duration: '10s', target: 0 },
19
+ ],
20
+
21
+ // Thresholds define pass/fail criteria
22
+ thresholds: {
23
+ // Example: 95th percentile response time should be below 200ms
24
+ http_req_duration: ['p95<200'],
25
+
26
+ // Example: 99% of requests should be successful
27
+ http_req_failed: ['rate<0.01'], // <1% failure rate
28
+ },
29
+ };
30
+
31
+ // Define the virtual user (VU) function
32
+ export default function () {
33
+ // Replace with your target URL
34
+ const url = 'YOUR_TARGET_URL_HERE';
35
+
36
+ // Replace with your request parameters (optional)
37
+ const params = {
38
+ headers: {
39
+ 'Content-Type': 'application/json',
40
+ },
41
+ };
42
+
43
+ // Replace with your request body (optional)
44
+ const payload = JSON.stringify({
45
+ key1: 'value1',
46
+ key2: 'value2',
47
+ });
48
+
49
+ // Make an HTTP request
50
+ const res = http.get(url, params); // or use http.post, http.put, http.delete, etc.
51
+
52
+ // Check the response status code
53
+ check(res, {
54
+ 'status is 200': (r) => r.status === 200,
55
+ });
56
+
57
+ // Add more checks as needed
58
+ // Example: check(res, { 'response time < 500ms': (r) => r.timings.duration < 500 });
59
+
60
+ // Introduce a delay between requests (optional)
61
+ sleep(1); // Sleep for 1 second
62
+
63
+ // Log response data for debugging (optional - REMOVE IN PRODUCTION!)
64
+ // console.log(res.body);
65
+ }
@@ -0,0 +1,4 @@
1
+ # References
2
+
3
+ Bundled resources for performance-test-suite skill
4
+
@@ -0,0 +1,11 @@
1
+ # Scripts
2
+
3
+ Bundled resources for performance-test-suite skill
4
+
5
+ - [x] init_test.py: Script to initialize a performance test based on user input.
6
+ - [x] run_test.sh: Script to execute the performance test using k6 or other tools.
7
+ - [x] analyze_results.py: Script to analyze the test results and generate a report.
8
+
9
+
10
+ ## Auto-Generated
11
+ Scripts generated on 2025-12-10 03:48:17
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ performance-test-suite - Analysis Script
4
+ Script to analyze the test results and generate a report.
5
+ Generated: 2025-12-10 03:48:17
6
+ """
7
+
8
+ import os
9
+ import json
10
+ import argparse
11
+ from pathlib import Path
12
+ from typing import Dict, List
13
+ from datetime import datetime
14
+
15
+ class Analyzer:
16
+ def __init__(self, target_path: str):
17
+ self.target_path = Path(target_path)
18
+ self.stats = {
19
+ 'total_files': 0,
20
+ 'total_size': 0,
21
+ 'file_types': {},
22
+ 'issues': [],
23
+ 'recommendations': []
24
+ }
25
+
26
+ def analyze_directory(self) -> Dict:
27
+ """Analyze directory structure and contents."""
28
+ if not self.target_path.exists():
29
+ self.stats['issues'].append(f"Path does not exist: {self.target_path}")
30
+ return self.stats
31
+
32
+ for file_path in self.target_path.rglob('*'):
33
+ if file_path.is_file():
34
+ self.analyze_file(file_path)
35
+
36
+ return self.stats
37
+
38
+ def analyze_file(self, file_path: Path):
39
+ """Analyze individual file."""
40
+ self.stats['total_files'] += 1
41
+ self.stats['total_size'] += file_path.stat().st_size
42
+
43
+ # Track file types
44
+ ext = file_path.suffix.lower()
45
+ if ext:
46
+ self.stats['file_types'][ext] = self.stats['file_types'].get(ext, 0) + 1
47
+
48
+ # Check for potential issues
49
+ if file_path.stat().st_size > 100 * 1024 * 1024: # 100MB
50
+ self.stats['issues'].append(f"Large file: {file_path} ({file_path.stat().st_size // 1024 // 1024}MB)")
51
+
52
+ if file_path.stat().st_size == 0:
53
+ self.stats['issues'].append(f"Empty file: {file_path}")
54
+
55
+ def generate_recommendations(self):
56
+ """Generate recommendations based on analysis."""
57
+ if self.stats['total_files'] == 0:
58
+ self.stats['recommendations'].append("No files found - check target path")
59
+
60
+ if len(self.stats['file_types']) > 20:
61
+ self.stats['recommendations'].append("Many file types detected - consider organizing")
62
+
63
+ if self.stats['total_size'] > 1024 * 1024 * 1024: # 1GB
64
+ self.stats['recommendations'].append("Large total size - consider archiving old data")
65
+
66
+ def generate_report(self) -> str:
67
+ """Generate analysis report."""
68
+ report = []
69
+ report.append("\n" + "="*60)
70
+ report.append(f"ANALYSIS REPORT - performance-test-suite")
71
+ report.append("="*60)
72
+ report.append(f"Target: {self.target_path}")
73
+ report.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
74
+ report.append("")
75
+
76
+ # Statistics
77
+ report.append("šŸ“Š STATISTICS")
78
+ report.append(f" Total Files: {self.stats['total_files']:,}")
79
+ report.append(f" Total Size: {self.stats['total_size'] / 1024 / 1024:.2f} MB")
80
+ report.append(f" File Types: {len(self.stats['file_types'])}")
81
+
82
+ # Top file types
83
+ if self.stats['file_types']:
84
+ report.append("\nšŸ“ TOP FILE TYPES")
85
+ sorted_types = sorted(self.stats['file_types'].items(), key=lambda x: x[1], reverse=True)[:5]
86
+ for ext, count in sorted_types:
87
+ report.append(f" {ext or 'no extension'}: {count} files")
88
+
89
+ # Issues
90
+ if self.stats['issues']:
91
+ report.append(f"\nāš ļø ISSUES ({len(self.stats['issues'])})")
92
+ for issue in self.stats['issues'][:10]:
93
+ report.append(f" - {issue}")
94
+ if len(self.stats['issues']) > 10:
95
+ report.append(f" ... and {len(self.stats['issues']) - 10} more")
96
+
97
+ # Recommendations
98
+ if self.stats['recommendations']:
99
+ report.append("\nšŸ’” RECOMMENDATIONS")
100
+ for rec in self.stats['recommendations']:
101
+ report.append(f" - {rec}")
102
+
103
+ report.append("")
104
+ return "\n".join(report)
105
+
106
+ def main():
107
+ parser = argparse.ArgumentParser(description="Script to analyze the test results and generate a report.")
108
+ parser.add_argument('target', help='Target directory to analyze')
109
+ parser.add_argument('--output', '-o', help='Output report file')
110
+ parser.add_argument('--json', action='store_true', help='Output as JSON')
111
+
112
+ args = parser.parse_args()
113
+
114
+ print(f"šŸ” Analyzing {args.target}...")
115
+ analyzer = Analyzer(args.target)
116
+ stats = analyzer.analyze_directory()
117
+ analyzer.generate_recommendations()
118
+
119
+ if args.json:
120
+ output = json.dumps(stats, indent=2)
121
+ else:
122
+ output = analyzer.generate_report()
123
+
124
+ if args.output:
125
+ Path(args.output).write_text(output)
126
+ print(f"āœ“ Report saved to {args.output}")
127
+ else:
128
+ print(output)
129
+
130
+ return 0 if len(stats['issues']) == 0 else 1
131
+
132
+ if __name__ == "__main__":
133
+ import sys
134
+ sys.exit(main())