lhci-ai-assistant 0.1.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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +228 -0
  3. package/dist/ai/copilot.d.ts +40 -0
  4. package/dist/ai/copilot.d.ts.map +1 -0
  5. package/dist/ai/copilot.js +176 -0
  6. package/dist/ai/copilot.js.map +1 -0
  7. package/dist/ai/local.d.ts +11 -0
  8. package/dist/ai/local.d.ts.map +1 -0
  9. package/dist/ai/local.js +199 -0
  10. package/dist/ai/local.js.map +1 -0
  11. package/dist/ai/openai.d.ts +39 -0
  12. package/dist/ai/openai.d.ts.map +1 -0
  13. package/dist/ai/openai.js +113 -0
  14. package/dist/ai/openai.js.map +1 -0
  15. package/dist/ai/prompt.d.ts +36 -0
  16. package/dist/ai/prompt.d.ts.map +1 -0
  17. package/dist/ai/prompt.js +190 -0
  18. package/dist/ai/prompt.js.map +1 -0
  19. package/dist/analyzer.d.ts +19 -0
  20. package/dist/analyzer.d.ts.map +1 -0
  21. package/dist/analyzer.js +264 -0
  22. package/dist/analyzer.js.map +1 -0
  23. package/dist/autofix/generator.d.ts +27 -0
  24. package/dist/autofix/generator.d.ts.map +1 -0
  25. package/dist/autofix/generator.js +153 -0
  26. package/dist/autofix/generator.js.map +1 -0
  27. package/dist/autofix/patterns.d.ts +34 -0
  28. package/dist/autofix/patterns.d.ts.map +1 -0
  29. package/dist/autofix/patterns.js +247 -0
  30. package/dist/autofix/patterns.js.map +1 -0
  31. package/dist/cli.d.ts +3 -0
  32. package/dist/cli.d.ts.map +1 -0
  33. package/dist/cli.js +159 -0
  34. package/dist/cli.js.map +1 -0
  35. package/dist/config.d.ts +19 -0
  36. package/dist/config.d.ts.map +1 -0
  37. package/dist/config.js +109 -0
  38. package/dist/config.js.map +1 -0
  39. package/dist/github/diff-fetcher.d.ts +43 -0
  40. package/dist/github/diff-fetcher.d.ts.map +1 -0
  41. package/dist/github/diff-fetcher.js +183 -0
  42. package/dist/github/diff-fetcher.js.map +1 -0
  43. package/dist/github/pr-commenter.d.ts +18 -0
  44. package/dist/github/pr-commenter.d.ts.map +1 -0
  45. package/dist/github/pr-commenter.js +229 -0
  46. package/dist/github/pr-commenter.js.map +1 -0
  47. package/dist/index.d.ts +23 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +85 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/metrics/comparator.d.ts +10 -0
  52. package/dist/metrics/comparator.d.ts.map +1 -0
  53. package/dist/metrics/comparator.js +228 -0
  54. package/dist/metrics/comparator.js.map +1 -0
  55. package/dist/metrics/extractor.d.ts +26 -0
  56. package/dist/metrics/extractor.d.ts.map +1 -0
  57. package/dist/metrics/extractor.js +174 -0
  58. package/dist/metrics/extractor.js.map +1 -0
  59. package/dist/metrics/loader.d.ts +35 -0
  60. package/dist/metrics/loader.d.ts.map +1 -0
  61. package/dist/metrics/loader.js +94 -0
  62. package/dist/metrics/loader.js.map +1 -0
  63. package/dist/output/json.d.ts +23 -0
  64. package/dist/output/json.d.ts.map +1 -0
  65. package/dist/output/json.js +144 -0
  66. package/dist/output/json.js.map +1 -0
  67. package/dist/output/markdown.d.ts +10 -0
  68. package/dist/output/markdown.d.ts.map +1 -0
  69. package/dist/output/markdown.js +226 -0
  70. package/dist/output/markdown.js.map +1 -0
  71. package/dist/output/terminal.d.ts +22 -0
  72. package/dist/output/terminal.d.ts.map +1 -0
  73. package/dist/output/terminal.js +238 -0
  74. package/dist/output/terminal.js.map +1 -0
  75. package/dist/types.d.ts +156 -0
  76. package/dist/types.d.ts.map +1 -0
  77. package/dist/types.js +6 -0
  78. package/dist/types.js.map +1 -0
  79. package/package.json +80 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # LHCI AI Assistant
2
+
3
+ AI-powered companion tool for [Lighthouse CI](https://github.com/GoogleChrome/lighthouse-ci) that analyzes performance regressions, explains root causes, and suggests fixes.
4
+
5
+ ## Features
6
+
7
+ - **AI-Powered Analysis**: Uses GitHub Copilot, OpenAI, or local heuristics to analyze Lighthouse results
8
+ - **Regression Detection**: Automatically identifies performance regressions by comparing against baselines
9
+ - **Root Cause Analysis**: Explains why performance degraded based on code changes and Lighthouse audits
10
+ - **Auto-Fix Suggestions**: Generates specific code fixes for common performance issues
11
+ - **GitHub Integration**: Posts analysis as PR comments and fetches code diffs for context
12
+ - **Multiple Output Formats**: Terminal (colored), JSON, Markdown
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install -g lhci-ai-assistant
18
+ # or
19
+ pnpm add -g lhci-ai-assistant
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ 1. Run Lighthouse CI to collect reports:
25
+
26
+ ```bash
27
+ lhci collect --url https://your-site.com
28
+ ```
29
+
30
+ 2. Analyze with AI:
31
+
32
+ ```bash
33
+ # Using local heuristics (no API key required)
34
+ lhci-ai analyze
35
+
36
+ # Using GitHub Copilot (requires gh auth login or GITHUB_TOKEN)
37
+ gh auth login # One-time setup
38
+ lhci-ai analyze --provider copilot
39
+
40
+ # Using OpenAI
41
+ lhci-ai analyze --provider openai --openai-key $OPENAI_API_KEY
42
+ ```
43
+
44
+ ## Commands
45
+
46
+ ### `lhci-ai analyze`
47
+
48
+ Analyze Lighthouse results with AI assistance.
49
+
50
+ ```bash
51
+ lhci-ai analyze [options]
52
+
53
+ Options:
54
+ --provider <name> AI provider: copilot | openai | local (default: local)
55
+ --github-token <token> GitHub token for Copilot/PR access
56
+ --openai-key <key> OpenAI API key
57
+ --auto-fix Generate auto-fix suggestions
58
+ --post-comment Post analysis to GitHub PR
59
+ --pr-number <number> PR number for posting comments
60
+ --repo <repo> GitHub repository (owner/repo)
61
+ --output <format> Output format: terminal | json | markdown (default: terminal)
62
+ --config <path> Config file path
63
+ ```
64
+
65
+ ### `lhci-ai check`
66
+
67
+ Quick threshold check for CI/CD gates.
68
+
69
+ ```bash
70
+ lhci-ai check [options]
71
+
72
+ Options:
73
+ --performance <score> Minimum performance score (0-100)
74
+ --accessibility <score> Minimum accessibility score (0-100)
75
+ --best-practices <score> Minimum best practices score (0-100)
76
+ --seo <score> Minimum SEO score (0-100)
77
+ ```
78
+
79
+ Example:
80
+
81
+ ```bash
82
+ # Fail if performance drops below 90%
83
+ lhci-ai check --performance 90
84
+ ```
85
+
86
+ ### `lhci-ai init`
87
+
88
+ Display configuration setup instructions.
89
+
90
+ ## Configuration
91
+
92
+ Create a `.lhci-ai.js` or add to your existing `.lhcirc.js`:
93
+
94
+ ```javascript
95
+ module.exports = {
96
+ ai: {
97
+ provider: 'local', // 'copilot' | 'openai' | 'local'
98
+ autoFix: true,
99
+ outputFormat: 'terminal',
100
+ },
101
+ thresholds: {
102
+ performance: 0.9,
103
+ accessibility: 0.9,
104
+ bestPractices: 0.9,
105
+ seo: 0.9,
106
+ },
107
+ };
108
+ ```
109
+
110
+ ### Environment Variables
111
+
112
+ - `GITHUB_TOKEN` - GitHub token for PR comments (Copilot uses GitHub CLI auth)
113
+ - `OPENAI_API_KEY` - OpenAI API key
114
+
115
+ ### Copilot Authentication
116
+
117
+ The Copilot provider uses the official [GitHub Copilot SDK](https://github.com/github/copilot-sdk) which authenticates via:
118
+
119
+ 1. **GitHub CLI** (recommended): Run `gh auth login` once
120
+ 2. **Environment variable**: Set `GITHUB_TOKEN` with a token that has Copilot access
121
+
122
+ ## CI/CD Integration
123
+
124
+ ### GitHub Actions
125
+
126
+ ```yaml
127
+ name: Lighthouse CI
128
+
129
+ on: [push, pull_request]
130
+
131
+ jobs:
132
+ lighthouse:
133
+ runs-on: ubuntu-latest
134
+ steps:
135
+ - uses: actions/checkout@v4
136
+
137
+ - name: Setup Node
138
+ uses: actions/setup-node@v4
139
+ with:
140
+ node-version: '20'
141
+
142
+ - name: Install dependencies
143
+ run: npm ci
144
+
145
+ - name: Build
146
+ run: npm run build
147
+
148
+ - name: Run Lighthouse CI
149
+ run: |
150
+ npm install -g @lhci/cli lhci-ai-assistant
151
+ lhci collect --url http://localhost:3000
152
+ lhci-ai analyze --provider copilot --post-comment
153
+ env:
154
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
155
+ ```
156
+
157
+ ## Programmatic API
158
+
159
+ ```typescript
160
+ import { analyze, quickCheck, loadLighthouseReports } from 'lhci-ai-assistant';
161
+
162
+ // Run full analysis
163
+ const result = await analyze({
164
+ provider: 'local',
165
+ output: 'json',
166
+ autoFix: true,
167
+ });
168
+
169
+ console.log(result.summary);
170
+ console.log(result.regressions);
171
+ console.log(result.recommendations);
172
+
173
+ // Quick threshold check
174
+ const check = await quickCheck({
175
+ performance: 0.9,
176
+ accessibility: 0.9,
177
+ });
178
+
179
+ if (!check.passed) {
180
+ console.log('Threshold failures:', check.failures);
181
+ process.exit(1);
182
+ }
183
+ ```
184
+
185
+ ## How It Works
186
+
187
+ 1. **Load Reports**: Reads Lighthouse JSON reports from `.lighthouseci/` directory
188
+ 2. **Extract Metrics**: Parses Core Web Vitals (FCP, LCP, TBT, CLS) and category scores
189
+ 3. **Compare**: Identifies regressions and improvements vs baseline
190
+ 4. **Fetch Context**: Retrieves code changes from GitHub PR diff
191
+ 5. **Analyze**: Uses AI or heuristics to identify root causes
192
+ 6. **Recommend**: Generates prioritized, actionable recommendations
193
+ 7. **Output**: Formats results for terminal, JSON, or PR comments
194
+
195
+ ## Supported Metrics
196
+
197
+ - **Performance Score**: Overall Lighthouse performance score
198
+ - **FCP**: First Contentful Paint
199
+ - **LCP**: Largest Contentful Paint
200
+ - **TBT**: Total Blocking Time
201
+ - **CLS**: Cumulative Layout Shift
202
+ - **Speed Index**: Visual progress speed
203
+ - **TTI**: Time to Interactive
204
+
205
+ ## Auto-Fix Patterns
206
+
207
+ The tool can detect and suggest fixes for:
208
+
209
+ - Missing `loading="lazy"` on images
210
+ - Missing image dimensions (CLS fix)
211
+ - Scripts without `defer` or `async`
212
+ - Missing `preconnect` hints
213
+ - Missing `font-display: swap`
214
+ - Render-blocking resources
215
+
216
+ ## Contributing
217
+
218
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
219
+
220
+ ## License
221
+
222
+ MIT - see [LICENSE](LICENSE)
223
+
224
+ ## Related Projects
225
+
226
+ - [Lighthouse CI](https://github.com/GoogleChrome/lighthouse-ci)
227
+ - [Lighthouse](https://github.com/GoogleChrome/lighthouse)
228
+ - [web.dev](https://web.dev)
@@ -0,0 +1,40 @@
1
+ /**
2
+ * GitHub Copilot SDK client for performance analysis
3
+ * Uses the official @github/copilot-sdk for programmatic access to Copilot
4
+ */
5
+ export interface CopilotOptions {
6
+ token?: string;
7
+ model?: string;
8
+ maxTokens?: number;
9
+ timeout?: number;
10
+ }
11
+ /**
12
+ * Analyze performance data using GitHub Copilot SDK
13
+ */
14
+ export declare function analyzeWithCopilot(prompt: string, options?: CopilotOptions): Promise<string>;
15
+ /**
16
+ * Check if Copilot access is available
17
+ */
18
+ export declare function checkCopilotAccess(_token?: string): Promise<boolean>;
19
+ /**
20
+ * Stop the Copilot client and clean up resources
21
+ */
22
+ export declare function stopCopilotClient(): Promise<void>;
23
+ /**
24
+ * List available models
25
+ */
26
+ export declare function listCopilotModels(): Promise<string[]>;
27
+ /**
28
+ * Custom error class for Copilot API errors
29
+ */
30
+ export declare class CopilotError extends Error {
31
+ statusCode: number;
32
+ constructor(message: string, statusCode: number);
33
+ isAuthError(): boolean;
34
+ isRateLimit(): boolean;
35
+ }
36
+ /**
37
+ * Get a helpful error message for Copilot errors
38
+ */
39
+ export declare function getCopilotErrorMessage(error: unknown): string;
40
+ //# sourceMappingURL=copilot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copilot.d.ts","sourceRoot":"","sources":["../../src/ai/copilot.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA4CD;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAmDjB;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ1E;AAED;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAWvD;AAED;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAQ3D;AAED;;GAEG;AACH,qBAAa,YAAa,SAAQ,KAAK;IAG5B,UAAU,EAAE,MAAM;gBADzB,OAAO,EAAE,MAAM,EACR,UAAU,EAAE,MAAM;IAM3B,WAAW,IAAI,OAAO;IAItB,WAAW,IAAI,OAAO;CAGvB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAgB7D"}
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ /**
3
+ * GitHub Copilot SDK client for performance analysis
4
+ * Uses the official @github/copilot-sdk for programmatic access to Copilot
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.CopilotError = void 0;
8
+ exports.analyzeWithCopilot = analyzeWithCopilot;
9
+ exports.checkCopilotAccess = checkCopilotAccess;
10
+ exports.stopCopilotClient = stopCopilotClient;
11
+ exports.listCopilotModels = listCopilotModels;
12
+ exports.getCopilotErrorMessage = getCopilotErrorMessage;
13
+ // Singleton client instance for reuse across calls
14
+ let clientInstance = null;
15
+ let clientInitPromise = null;
16
+ /**
17
+ * Dynamically import the ESM SDK
18
+ * Uses Function to prevent TypeScript from converting to require()
19
+ */
20
+ async function importSDK() {
21
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
22
+ const dynamicImport = new Function('specifier', 'return import(specifier)');
23
+ return dynamicImport('@github/copilot-sdk');
24
+ }
25
+ /**
26
+ * Get or create the Copilot client singleton
27
+ */
28
+ async function getClient() {
29
+ if (clientInstance && clientInstance.getState() === 'connected') {
30
+ return clientInstance;
31
+ }
32
+ if (clientInitPromise) {
33
+ return clientInitPromise;
34
+ }
35
+ clientInitPromise = (async () => {
36
+ const { CopilotClient } = await importSDK();
37
+ const client = new CopilotClient({
38
+ autoStart: true,
39
+ autoRestart: true,
40
+ logLevel: 'error',
41
+ });
42
+ await client.start();
43
+ clientInstance = client;
44
+ return client;
45
+ })();
46
+ return clientInitPromise;
47
+ }
48
+ /**
49
+ * Analyze performance data using GitHub Copilot SDK
50
+ */
51
+ async function analyzeWithCopilot(prompt, options = {}) {
52
+ const client = await getClient();
53
+ let session = null;
54
+ try {
55
+ // Create a session with the performance expert system message
56
+ session = await client.createSession({
57
+ model: options.model || 'gpt-4o',
58
+ systemMessage: {
59
+ mode: 'append',
60
+ content: 'You are a web performance expert specializing in Lighthouse optimization. You provide specific, actionable advice for improving Core Web Vitals and overall performance scores.',
61
+ },
62
+ streaming: false,
63
+ });
64
+ // Send the prompt and wait for the response
65
+ const timeout = options.timeout || 60000;
66
+ const response = await session.sendAndWait({ prompt }, timeout);
67
+ if (!response) {
68
+ throw new CopilotError('No response received from Copilot', 500);
69
+ }
70
+ return response.data.content || 'No analysis available.';
71
+ }
72
+ catch (error) {
73
+ if (error instanceof CopilotError) {
74
+ throw error;
75
+ }
76
+ // Map SDK errors to CopilotError
77
+ const message = error instanceof Error ? error.message : String(error);
78
+ if (message.includes('not authenticated') || message.includes('auth')) {
79
+ throw new CopilotError('GitHub Copilot authentication failed. Please run `gh auth login` or ensure GITHUB_TOKEN is set.', 401);
80
+ }
81
+ if (message.includes('rate limit') || message.includes('429')) {
82
+ throw new CopilotError('GitHub Copilot rate limit exceeded. Please try again later.', 429);
83
+ }
84
+ throw new CopilotError(`Copilot SDK error: ${message}`, 500);
85
+ }
86
+ finally {
87
+ // Clean up the session
88
+ if (session) {
89
+ try {
90
+ await session.destroy();
91
+ }
92
+ catch {
93
+ // Ignore cleanup errors
94
+ }
95
+ }
96
+ }
97
+ }
98
+ /**
99
+ * Check if Copilot access is available
100
+ */
101
+ async function checkCopilotAccess(_token) {
102
+ try {
103
+ const client = await getClient();
104
+ const authStatus = await client.getAuthStatus();
105
+ return authStatus.isAuthenticated;
106
+ }
107
+ catch {
108
+ return false;
109
+ }
110
+ }
111
+ /**
112
+ * Stop the Copilot client and clean up resources
113
+ */
114
+ async function stopCopilotClient() {
115
+ if (clientInstance) {
116
+ try {
117
+ await clientInstance.stop();
118
+ }
119
+ catch {
120
+ await clientInstance.forceStop();
121
+ }
122
+ finally {
123
+ clientInstance = null;
124
+ clientInitPromise = null;
125
+ }
126
+ }
127
+ }
128
+ /**
129
+ * List available models
130
+ */
131
+ async function listCopilotModels() {
132
+ try {
133
+ const client = await getClient();
134
+ const models = await client.listModels();
135
+ return models.map(m => m.id);
136
+ }
137
+ catch {
138
+ return [];
139
+ }
140
+ }
141
+ /**
142
+ * Custom error class for Copilot API errors
143
+ */
144
+ class CopilotError extends Error {
145
+ constructor(message, statusCode) {
146
+ super(message);
147
+ this.statusCode = statusCode;
148
+ this.name = 'CopilotError';
149
+ }
150
+ isAuthError() {
151
+ return this.statusCode === 401 || this.statusCode === 403;
152
+ }
153
+ isRateLimit() {
154
+ return this.statusCode === 429;
155
+ }
156
+ }
157
+ exports.CopilotError = CopilotError;
158
+ /**
159
+ * Get a helpful error message for Copilot errors
160
+ */
161
+ function getCopilotErrorMessage(error) {
162
+ if (error instanceof CopilotError) {
163
+ if (error.isAuthError()) {
164
+ return 'GitHub Copilot access denied. Please run `gh auth login` or ensure you have an active Copilot subscription.';
165
+ }
166
+ if (error.isRateLimit()) {
167
+ return 'GitHub Copilot rate limit exceeded. Please try again later.';
168
+ }
169
+ return error.message;
170
+ }
171
+ if (error instanceof Error) {
172
+ return error.message;
173
+ }
174
+ return 'Unknown error occurred while calling Copilot SDK';
175
+ }
176
+ //# sourceMappingURL=copilot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copilot.js","sourceRoot":"","sources":["../../src/ai/copilot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { MetricComparison, Opportunity, AnalysisResult } from '../types';
2
+ /**
3
+ * Local heuristic-based analysis when no AI provider is available
4
+ * This provides basic analysis without external API calls
5
+ */
6
+ export declare function analyzeLocally(data: {
7
+ regressions: MetricComparison[];
8
+ improvements: MetricComparison[];
9
+ opportunities: Opportunity[];
10
+ }): AnalysisResult;
11
+ //# sourceMappingURL=local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/ai/local.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,cAAc,EAA6B,MAAM,UAAU,CAAC;AAEpG;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,WAAW,EAAE,gBAAgB,EAAE,CAAC;IAChC,YAAY,EAAE,gBAAgB,EAAE,CAAC;IACjC,aAAa,EAAE,WAAW,EAAE,CAAC;CAC9B,GAAG,cAAc,CAWjB"}
@@ -0,0 +1,199 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzeLocally = analyzeLocally;
4
+ /**
5
+ * Local heuristic-based analysis when no AI provider is available
6
+ * This provides basic analysis without external API calls
7
+ */
8
+ function analyzeLocally(data) {
9
+ const rootCauses = identifyRootCauses(data.regressions, data.opportunities);
10
+ const recommendations = generateRecommendations(data.regressions, data.opportunities);
11
+ return {
12
+ summary: generateSummary(data),
13
+ regressions: data.regressions,
14
+ improvements: data.improvements,
15
+ rootCauses,
16
+ recommendations,
17
+ };
18
+ }
19
+ /**
20
+ * Generate a text summary of the analysis
21
+ */
22
+ function generateSummary(data) {
23
+ const parts = [];
24
+ if (data.regressions.length > 0) {
25
+ const critical = data.regressions.filter((r) => r.severity === 'critical');
26
+ const high = data.regressions.filter((r) => r.severity === 'high');
27
+ parts.push(`Found ${data.regressions.length} performance regression(s).`);
28
+ if (critical.length > 0) {
29
+ parts.push(`${critical.length} critical issue(s) require immediate attention.`);
30
+ }
31
+ if (high.length > 0) {
32
+ parts.push(`${high.length} high-severity issue(s) should be addressed soon.`);
33
+ }
34
+ }
35
+ else {
36
+ parts.push('No performance regressions detected.');
37
+ }
38
+ if (data.improvements.length > 0) {
39
+ parts.push(`${data.improvements.length} metric(s) improved.`);
40
+ }
41
+ return parts.join(' ');
42
+ }
43
+ /**
44
+ * Identify likely root causes based on heuristics
45
+ */
46
+ function identifyRootCauses(regressions, opportunities) {
47
+ const causes = [];
48
+ for (const regression of regressions) {
49
+ const cause = getRootCauseForMetric(regression, opportunities);
50
+ if (cause) {
51
+ causes.push(cause);
52
+ }
53
+ }
54
+ return causes;
55
+ }
56
+ /**
57
+ * Get root cause for a specific metric regression
58
+ */
59
+ function getRootCauseForMetric(regression, opportunities) {
60
+ const metric = regression.metric;
61
+ // Map metrics to likely causes
62
+ const causeMap = {
63
+ FCP: {
64
+ cause: 'First Contentful Paint regression often indicates render-blocking resources, slow server response, or increased initial HTML/CSS size.',
65
+ relatedOpps: ['render-blocking-resources', 'server-response-time', 'uses-rel-preconnect'],
66
+ },
67
+ LCP: {
68
+ cause: 'Largest Contentful Paint regression typically indicates slow loading of the main content element (images, video, or text blocks).',
69
+ relatedOpps: ['largest-contentful-paint-element', 'uses-responsive-images', 'offscreen-images'],
70
+ },
71
+ TBT: {
72
+ cause: 'Total Blocking Time regression indicates increased main thread blocking from JavaScript execution.',
73
+ relatedOpps: ['mainthread-work-breakdown', 'bootup-time', 'unused-javascript'],
74
+ },
75
+ CLS: {
76
+ cause: 'Cumulative Layout Shift regression indicates visual instability from elements moving after initial render.',
77
+ relatedOpps: ['layout-shift-elements', 'unsized-images', 'non-composited-animations'],
78
+ },
79
+ 'Speed Index': {
80
+ cause: 'Speed Index regression indicates slower visual progress during page load.',
81
+ relatedOpps: ['render-blocking-resources', 'critical-request-chains'],
82
+ },
83
+ TTI: {
84
+ cause: 'Time to Interactive regression indicates the page takes longer to become fully interactive.',
85
+ relatedOpps: ['mainthread-work-breakdown', 'bootup-time', 'third-party-summary'],
86
+ },
87
+ 'Performance Score': {
88
+ cause: 'Overall performance score decreased. Review individual Core Web Vitals for specific causes.',
89
+ relatedOpps: [],
90
+ },
91
+ };
92
+ const info = causeMap[metric];
93
+ if (!info) {
94
+ return null;
95
+ }
96
+ // Find related opportunities
97
+ const relatedOpportunities = opportunities.filter((o) => info.relatedOpps.includes(o.id));
98
+ let cause = info.cause;
99
+ if (relatedOpportunities.length > 0) {
100
+ cause += ` Lighthouse flagged: ${relatedOpportunities.map((o) => o.title).join(', ')}.`;
101
+ }
102
+ return {
103
+ metric,
104
+ cause,
105
+ confidence: relatedOpportunities.length > 0 ? 'high' : 'medium',
106
+ relatedFiles: undefined,
107
+ };
108
+ }
109
+ /**
110
+ * Generate recommendations based on regressions and opportunities
111
+ */
112
+ function generateRecommendations(regressions, opportunities) {
113
+ const recommendations = [];
114
+ // Add recommendations based on opportunities
115
+ for (const opp of opportunities.slice(0, 5)) {
116
+ recommendations.push({
117
+ title: opp.title,
118
+ description: getRecommendationDescription(opp),
119
+ priority: opp.savingsMs && opp.savingsMs > 500 ? 'high' : 'medium',
120
+ impact: opp.savingsMs ? `~${opp.savingsMs.toFixed(0)}ms potential improvement` : 'Variable impact',
121
+ effort: getEstimatedEffort(opp.id),
122
+ });
123
+ }
124
+ // Add general recommendations based on regression patterns
125
+ const hasLCPRegression = regressions.some((r) => r.metric === 'LCP');
126
+ const hasTBTRegression = regressions.some((r) => r.metric === 'TBT');
127
+ const hasCLSRegression = regressions.some((r) => r.metric === 'CLS');
128
+ if (hasLCPRegression && !recommendations.some((r) => r.title.includes('image'))) {
129
+ recommendations.push({
130
+ title: 'Optimize largest contentful paint element',
131
+ description: 'Identify the LCP element and ensure it loads quickly. Use preload for critical images, optimize image formats (WebP/AVIF), and implement responsive images.',
132
+ priority: 'high',
133
+ impact: 'Can improve LCP by 500ms-2s',
134
+ effort: 'medium',
135
+ });
136
+ }
137
+ if (hasTBTRegression && !recommendations.some((r) => r.title.includes('JavaScript'))) {
138
+ recommendations.push({
139
+ title: 'Reduce JavaScript execution time',
140
+ description: 'Audit third-party scripts, implement code splitting, and defer non-critical JavaScript. Consider using Web Workers for heavy computations.',
141
+ priority: 'high',
142
+ impact: 'Can improve TBT by 100-500ms',
143
+ effort: 'high',
144
+ });
145
+ }
146
+ if (hasCLSRegression) {
147
+ recommendations.push({
148
+ title: 'Fix layout shifts',
149
+ description: 'Add explicit dimensions to images and embedded content. Reserve space for dynamic content and avoid inserting content above existing content.',
150
+ priority: 'medium',
151
+ impact: 'Can reduce CLS by 0.05-0.1',
152
+ effort: 'low',
153
+ });
154
+ }
155
+ // Sort by priority
156
+ const priorityOrder = { high: 0, medium: 1, low: 2 };
157
+ return recommendations.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
158
+ }
159
+ /**
160
+ * Get a detailed description for a recommendation
161
+ */
162
+ function getRecommendationDescription(opp) {
163
+ const descriptions = {
164
+ 'render-blocking-resources': 'Identify and defer or inline critical CSS/JS. Use async/defer for non-critical scripts.',
165
+ 'unused-javascript': 'Remove or lazy-load unused JavaScript. Use code splitting to reduce initial bundle size.',
166
+ 'unused-css-rules': 'Remove unused CSS rules or implement critical CSS extraction.',
167
+ 'uses-responsive-images': 'Implement srcset and sizes attributes for responsive images. Use appropriate image dimensions.',
168
+ 'offscreen-images': 'Implement lazy loading for images below the fold using loading="lazy" or Intersection Observer.',
169
+ 'uses-optimized-images': 'Compress images without quality loss. Consider WebP or AVIF formats.',
170
+ 'uses-text-compression': 'Enable GZIP or Brotli compression on your server for text-based resources.',
171
+ 'server-response-time': 'Optimize server configuration, use caching, or consider a CDN for faster initial response.',
172
+ 'uses-rel-preconnect': 'Add preconnect hints for critical third-party origins.',
173
+ 'third-party-summary': 'Audit third-party scripts and remove or defer non-essential ones.',
174
+ };
175
+ return descriptions[opp.id] || opp.description || opp.title;
176
+ }
177
+ /**
178
+ * Estimate effort for an optimization
179
+ */
180
+ function getEstimatedEffort(oppId) {
181
+ const lowEffort = [
182
+ 'uses-text-compression',
183
+ 'uses-rel-preconnect',
184
+ 'uses-rel-preload',
185
+ 'offscreen-images',
186
+ ];
187
+ const highEffort = [
188
+ 'unused-javascript',
189
+ 'mainthread-work-breakdown',
190
+ 'bootup-time',
191
+ 'third-party-summary',
192
+ ];
193
+ if (lowEffort.includes(oppId))
194
+ return 'low';
195
+ if (highEffort.includes(oppId))
196
+ return 'high';
197
+ return 'medium';
198
+ }
199
+ //# sourceMappingURL=local.js.map