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.
- package/LICENSE +21 -0
- package/README.md +228 -0
- package/dist/ai/copilot.d.ts +40 -0
- package/dist/ai/copilot.d.ts.map +1 -0
- package/dist/ai/copilot.js +176 -0
- package/dist/ai/copilot.js.map +1 -0
- package/dist/ai/local.d.ts +11 -0
- package/dist/ai/local.d.ts.map +1 -0
- package/dist/ai/local.js +199 -0
- package/dist/ai/local.js.map +1 -0
- package/dist/ai/openai.d.ts +39 -0
- package/dist/ai/openai.d.ts.map +1 -0
- package/dist/ai/openai.js +113 -0
- package/dist/ai/openai.js.map +1 -0
- package/dist/ai/prompt.d.ts +36 -0
- package/dist/ai/prompt.d.ts.map +1 -0
- package/dist/ai/prompt.js +190 -0
- package/dist/ai/prompt.js.map +1 -0
- package/dist/analyzer.d.ts +19 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzer.js +264 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/autofix/generator.d.ts +27 -0
- package/dist/autofix/generator.d.ts.map +1 -0
- package/dist/autofix/generator.js +153 -0
- package/dist/autofix/generator.js.map +1 -0
- package/dist/autofix/patterns.d.ts +34 -0
- package/dist/autofix/patterns.d.ts.map +1 -0
- package/dist/autofix/patterns.js +247 -0
- package/dist/autofix/patterns.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +159 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +109 -0
- package/dist/config.js.map +1 -0
- package/dist/github/diff-fetcher.d.ts +43 -0
- package/dist/github/diff-fetcher.d.ts.map +1 -0
- package/dist/github/diff-fetcher.js +183 -0
- package/dist/github/diff-fetcher.js.map +1 -0
- package/dist/github/pr-commenter.d.ts +18 -0
- package/dist/github/pr-commenter.d.ts.map +1 -0
- package/dist/github/pr-commenter.js +229 -0
- package/dist/github/pr-commenter.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +85 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics/comparator.d.ts +10 -0
- package/dist/metrics/comparator.d.ts.map +1 -0
- package/dist/metrics/comparator.js +228 -0
- package/dist/metrics/comparator.js.map +1 -0
- package/dist/metrics/extractor.d.ts +26 -0
- package/dist/metrics/extractor.d.ts.map +1 -0
- package/dist/metrics/extractor.js +174 -0
- package/dist/metrics/extractor.js.map +1 -0
- package/dist/metrics/loader.d.ts +35 -0
- package/dist/metrics/loader.d.ts.map +1 -0
- package/dist/metrics/loader.js +94 -0
- package/dist/metrics/loader.js.map +1 -0
- package/dist/output/json.d.ts +23 -0
- package/dist/output/json.d.ts.map +1 -0
- package/dist/output/json.js +144 -0
- package/dist/output/json.js.map +1 -0
- package/dist/output/markdown.d.ts +10 -0
- package/dist/output/markdown.d.ts.map +1 -0
- package/dist/output/markdown.js +226 -0
- package/dist/output/markdown.js.map +1 -0
- package/dist/output/terminal.d.ts +22 -0
- package/dist/output/terminal.d.ts.map +1 -0
- package/dist/output/terminal.js +238 -0
- package/dist/output/terminal.js.map +1 -0
- package/dist/types.d.ts +156 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- 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"}
|
package/dist/ai/local.js
ADDED
|
@@ -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
|