@oalacea/daemon 0.5.0 → 0.5.1

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 (38) hide show
  1. package/CHANGELOG.md +46 -38
  2. package/LICENSE +23 -23
  3. package/README.md +147 -141
  4. package/agents/deps-analyzer.js +366 -366
  5. package/agents/detector.js +570 -570
  6. package/agents/fix-engine.js +305 -305
  7. package/agents/lighthouse-scanner.js +405 -405
  8. package/agents/perf-analyzer.js +294 -294
  9. package/agents/perf-front-analyzer.js +229 -229
  10. package/agents/test-generator.js +387 -387
  11. package/agents/test-runner.js +318 -318
  12. package/bin/Dockerfile +75 -74
  13. package/bin/cli.js +449 -449
  14. package/lib/config.js +250 -250
  15. package/lib/docker.js +207 -207
  16. package/lib/reporter.js +297 -297
  17. package/package.json +34 -34
  18. package/prompts/DEPS_EFFICIENCY.md +558 -558
  19. package/prompts/E2E.md +491 -491
  20. package/prompts/EXECUTE.md +1060 -1060
  21. package/prompts/INTEGRATION_API.md +484 -484
  22. package/prompts/INTEGRATION_DB.md +425 -425
  23. package/prompts/PERF_API.md +433 -433
  24. package/prompts/PERF_DB.md +430 -430
  25. package/prompts/PERF_FRONT.md +357 -357
  26. package/prompts/REMEDIATION.md +482 -482
  27. package/prompts/UNIT.md +260 -260
  28. package/scripts/dev.js +106 -106
  29. package/templates/README.md +38 -38
  30. package/templates/k6/load-test.js +54 -54
  31. package/templates/playwright/e2e.spec.ts +61 -61
  32. package/templates/vitest/angular-component.test.ts +38 -38
  33. package/templates/vitest/api.test.ts +51 -51
  34. package/templates/vitest/component.test.ts +27 -27
  35. package/templates/vitest/hook.test.ts +36 -36
  36. package/templates/vitest/solid-component.test.ts +34 -34
  37. package/templates/vitest/svelte-component.test.ts +33 -33
  38. package/templates/vitest/vue-component.test.ts +39 -39
@@ -1,294 +1,294 @@
1
- /**
2
- * Daemon - Performance Analyzer Agent
3
- *
4
- * Analyzes application performance including:
5
- * - API response times
6
- * - Database query efficiency
7
- * - Bundle size
8
- * - Web Vitals
9
- */
10
-
11
- const { execSync } = require('child_process');
12
- const fs = require('fs');
13
- const path = require('path');
14
-
15
- const CONFIG = {
16
- container: 'daemon-tools',
17
- docker: 'docker exec',
18
- };
19
-
20
- /**
21
- * Run k6 performance test
22
- */
23
- function runK6Test(testFile, options = {}) {
24
- const stages = options.stages || [
25
- { duration: '30s', target: 20 },
26
- { duration: '1m', target: 20 },
27
- { duration: '30s', target: 0 },
28
- ];
29
-
30
- const thresholds = options.thresholds || [
31
- 'http_req_duration[p(95)]<200',
32
- 'http_req_failed<0.01',
33
- ];
34
-
35
- // Create temp k6 script
36
- const script = generateK6Script(options.target || 'http://host.docker.internal:3000', stages, thresholds);
37
-
38
- return runDockerCommand(`k6 run -`, { input: script });
39
- }
40
-
41
- /**
42
- * Generate k6 test script
43
- */
44
- function generateK6Script(target, stages, thresholds) {
45
- return `
46
- import http from 'k6/http';
47
- import { check, sleep } from 'k6';
48
-
49
- export const options = {
50
- stages: ${JSON.stringify(stages)},
51
- thresholds: {
52
- ${thresholds.map(t => `'${t}': true`).join(',\n ')}
53
- },
54
- };
55
-
56
- const BASE_URL = '${target}';
57
-
58
- export default function () {
59
- let res = http.get(\`\${BASE_URL}/\`);
60
- check(res, {
61
- 'homepage OK': r => r.status === 200,
62
- });
63
-
64
- sleep(1);
65
-
66
- res = http.get(\`\${BASE_URL}/api/users\`);
67
- check(res, {
68
- 'users API OK': r => r.status === 200,
69
- 'response time < 200ms': r => r.timings.duration < 200,
70
- });
71
-
72
- sleep(1);
73
- }
74
- `;
75
- }
76
-
77
- /**
78
- * Analyze bundle size
79
- */
80
- function analyzeBundleSize(projectDir) {
81
- const buildDir = path.join(projectDir, '.next', 'static', 'chunks');
82
- const distDir = path.join(projectDir, 'dist', 'assets');
83
-
84
- let chunksDir;
85
- if (fs.existsSync(buildDir)) chunksDir = buildDir;
86
- else if (fs.existsSync(distDir)) chunksDir = distDir;
87
- else return null;
88
-
89
- const chunks = [];
90
- let totalSize = 0;
91
-
92
- function analyzeDir(dir) {
93
- const entries = fs.readdirSync(dir, { withFileTypes: true });
94
-
95
- for (const entry of entries) {
96
- const fullPath = path.join(dir, entry.name);
97
-
98
- if (entry.isDirectory()) {
99
- analyzeDir(fullPath);
100
- } else if (entry.isFile() && (entry.name.endsWith('.js') || entry.name.endsWith('.css'))) {
101
- const stats = fs.statSync(fullPath);
102
- const size = stats.size;
103
- totalSize += size;
104
-
105
- chunks.push({
106
- name: entry.name,
107
- size: size,
108
- sizeKB: (size / 1024).toFixed(2),
109
- path: path.relative(projectDir, fullPath),
110
- });
111
- }
112
- }
113
- }
114
-
115
- analyzeDir(chunksDir);
116
-
117
- return {
118
- totalSize,
119
- totalSizeKB: (totalSize / 1024).toFixed(2),
120
- totalSizeMB: (totalSize / 1024 / 1024).toFixed(2),
121
- chunks: chunks.sort((a, b) => b.size - a.size).slice(0, 20), // Top 20
122
- };
123
- }
124
-
125
- /**
126
- * Analyze database queries (Prisma)
127
- */
128
- function analyzeDbQueries(projectDir) {
129
- const prismaDir = path.join(projectDir, 'prisma');
130
- if (!fs.existsSync(prismaDir)) {
131
- return null;
132
- }
133
-
134
- const schemaPath = path.join(prismaDir, 'schema.prisma');
135
- if (!fs.existsSync(schemaPath)) {
136
- return null;
137
- }
138
-
139
- const schema = fs.readFileSync(schemaPath, 'utf-8');
140
-
141
- const models = [];
142
- const modelMatches = schema.matchAll(/model\s+(\w+)\s*{([^}]+)}/g);
143
-
144
- for (const match of modelMatches) {
145
- const modelName = match[1];
146
- const body = match[2];
147
-
148
- const fields = [];
149
- const fieldMatches = body.matchAll(/(\w+)\s+(\w+)(?:\s+@([^{\s]+[^}]*))?/g);
150
-
151
- for (const fieldMatch of fieldMatches) {
152
- const fieldName = fieldMatch[1];
153
- const fieldType = fieldMatch[2];
154
- const attributes = fieldMatch[3] || '';
155
-
156
- fields.push({
157
- name: fieldName,
158
- type: fieldType,
159
- attributes: attributes,
160
- isIndexed: attributes.includes('index') || attributes.includes('unique') || attributes.includes('id'),
161
- });
162
- }
163
-
164
- models.push({
165
- name: modelName,
166
- fields: fields,
167
- });
168
- }
169
-
170
- return {
171
- models,
172
- recommendations: generateDbRecommendations(models),
173
- };
174
- }
175
-
176
- /**
177
- * Generate database recommendations
178
- */
179
- function generateDbRecommendations(models) {
180
- const recommendations = [];
181
-
182
- for (const model of models) {
183
- // Check for common filter fields without indexes
184
- const filterFields = ['email', 'username', 'slug', 'status', 'published', 'createdAt'];
185
-
186
- for (const field of model.fields) {
187
- if (filterFields.includes(field.name) && !field.isIndexed) {
188
- recommendations.push({
189
- type: 'index',
190
- model: model.name,
191
- field: field.name,
192
- message: `Consider adding index on ${model.name}.${field.name}`,
193
- priority: field.name === 'email' ? 'high' : 'medium',
194
- });
195
- }
196
- }
197
- }
198
-
199
- return recommendations;
200
- }
201
-
202
- /**
203
- * Analyze Web Vitals (Lighthouse)
204
- */
205
- function analyzeWebVitals(target) {
206
- // This would run Playwright with metrics
207
- return runDockerCommand(`npx playwright test --reporter=json`, {
208
- cwd: path.join(process.cwd(), 'tests', 'e2e'),
209
- });
210
- }
211
-
212
- /**
213
- * Run command in Docker container
214
- */
215
- function runDockerCommand(command, options = {}) {
216
- const dockerCmd = `${CONFIG.docker} ${CONFIG.container} ${command}`;
217
-
218
- try {
219
- return {
220
- success: true,
221
- output: execSync(dockerCmd, {
222
- encoding: 'utf-8',
223
- stdio: options.silent ? 'pipe' : 'inherit',
224
- input: options.input,
225
- }),
226
- };
227
- } catch (error) {
228
- return {
229
- success: false,
230
- error: error.message,
231
- };
232
- }
233
- }
234
-
235
- /**
236
- * Generate performance report
237
- */
238
- function generateReport(analysis) {
239
- const report = {
240
- summary: {},
241
- recommendations: [],
242
- };
243
-
244
- // Bundle analysis
245
- if (analysis.bundle) {
246
- report.summary.bundle = {
247
- totalSize: analysis.bundle.totalSizeMB + ' MB',
248
- largestChunks: analysis.bundle.chunks.slice(0, 5),
249
- };
250
-
251
- // Check for large bundles
252
- analysis.bundle.chunks.forEach((chunk) => {
253
- if (chunk.size > 200 * 1024) { // > 200KB
254
- report.recommendations.push({
255
- type: 'bundle',
256
- message: `Large chunk: ${chunk.name} (${chunk.sizeKB} KB)`,
257
- priority: 'medium',
258
- });
259
- }
260
- });
261
- }
262
-
263
- // DB analysis
264
- if (analysis.db) {
265
- report.summary.database = {
266
- models: analysis.db.models.length,
267
- recommendations: analysis.db.recommendations,
268
- };
269
- report.recommendations.push(...analysis.db.recommendations);
270
- }
271
-
272
- return report;
273
- }
274
-
275
- /**
276
- * Full performance analysis
277
- */
278
- function analyze(projectDir) {
279
- const analysis = {
280
- bundle: analyzeBundleSize(projectDir),
281
- db: analyzeDbQueries(projectDir),
282
- };
283
-
284
- return generateReport(analysis);
285
- }
286
-
287
- module.exports = {
288
- runK6Test,
289
- analyzeBundleSize,
290
- analyzeDbQueries,
291
- analyzeWebVitals,
292
- generateReport,
293
- analyze,
294
- };
1
+ /**
2
+ * Daemon - Performance Analyzer Agent
3
+ *
4
+ * Analyzes application performance including:
5
+ * - API response times
6
+ * - Database query efficiency
7
+ * - Bundle size
8
+ * - Web Vitals
9
+ */
10
+
11
+ const { execSync } = require('child_process');
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ const CONFIG = {
16
+ container: 'daemon-tools',
17
+ docker: 'docker exec',
18
+ };
19
+
20
+ /**
21
+ * Run k6 performance test
22
+ */
23
+ function runK6Test(testFile, options = {}) {
24
+ const stages = options.stages || [
25
+ { duration: '30s', target: 20 },
26
+ { duration: '1m', target: 20 },
27
+ { duration: '30s', target: 0 },
28
+ ];
29
+
30
+ const thresholds = options.thresholds || [
31
+ 'http_req_duration[p(95)]<200',
32
+ 'http_req_failed<0.01',
33
+ ];
34
+
35
+ // Create temp k6 script
36
+ const script = generateK6Script(options.target || 'http://host.docker.internal:3000', stages, thresholds);
37
+
38
+ return runDockerCommand(`k6 run -`, { input: script });
39
+ }
40
+
41
+ /**
42
+ * Generate k6 test script
43
+ */
44
+ function generateK6Script(target, stages, thresholds) {
45
+ return `
46
+ import http from 'k6/http';
47
+ import { check, sleep } from 'k6';
48
+
49
+ export const options = {
50
+ stages: ${JSON.stringify(stages)},
51
+ thresholds: {
52
+ ${thresholds.map(t => `'${t}': true`).join(',\n ')}
53
+ },
54
+ };
55
+
56
+ const BASE_URL = '${target}';
57
+
58
+ export default function () {
59
+ let res = http.get(\`\${BASE_URL}/\`);
60
+ check(res, {
61
+ 'homepage OK': r => r.status === 200,
62
+ });
63
+
64
+ sleep(1);
65
+
66
+ res = http.get(\`\${BASE_URL}/api/users\`);
67
+ check(res, {
68
+ 'users API OK': r => r.status === 200,
69
+ 'response time < 200ms': r => r.timings.duration < 200,
70
+ });
71
+
72
+ sleep(1);
73
+ }
74
+ `;
75
+ }
76
+
77
+ /**
78
+ * Analyze bundle size
79
+ */
80
+ function analyzeBundleSize(projectDir) {
81
+ const buildDir = path.join(projectDir, '.next', 'static', 'chunks');
82
+ const distDir = path.join(projectDir, 'dist', 'assets');
83
+
84
+ let chunksDir;
85
+ if (fs.existsSync(buildDir)) chunksDir = buildDir;
86
+ else if (fs.existsSync(distDir)) chunksDir = distDir;
87
+ else return null;
88
+
89
+ const chunks = [];
90
+ let totalSize = 0;
91
+
92
+ function analyzeDir(dir) {
93
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
94
+
95
+ for (const entry of entries) {
96
+ const fullPath = path.join(dir, entry.name);
97
+
98
+ if (entry.isDirectory()) {
99
+ analyzeDir(fullPath);
100
+ } else if (entry.isFile() && (entry.name.endsWith('.js') || entry.name.endsWith('.css'))) {
101
+ const stats = fs.statSync(fullPath);
102
+ const size = stats.size;
103
+ totalSize += size;
104
+
105
+ chunks.push({
106
+ name: entry.name,
107
+ size: size,
108
+ sizeKB: (size / 1024).toFixed(2),
109
+ path: path.relative(projectDir, fullPath),
110
+ });
111
+ }
112
+ }
113
+ }
114
+
115
+ analyzeDir(chunksDir);
116
+
117
+ return {
118
+ totalSize,
119
+ totalSizeKB: (totalSize / 1024).toFixed(2),
120
+ totalSizeMB: (totalSize / 1024 / 1024).toFixed(2),
121
+ chunks: chunks.sort((a, b) => b.size - a.size).slice(0, 20), // Top 20
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Analyze database queries (Prisma)
127
+ */
128
+ function analyzeDbQueries(projectDir) {
129
+ const prismaDir = path.join(projectDir, 'prisma');
130
+ if (!fs.existsSync(prismaDir)) {
131
+ return null;
132
+ }
133
+
134
+ const schemaPath = path.join(prismaDir, 'schema.prisma');
135
+ if (!fs.existsSync(schemaPath)) {
136
+ return null;
137
+ }
138
+
139
+ const schema = fs.readFileSync(schemaPath, 'utf-8');
140
+
141
+ const models = [];
142
+ const modelMatches = schema.matchAll(/model\s+(\w+)\s*{([^}]+)}/g);
143
+
144
+ for (const match of modelMatches) {
145
+ const modelName = match[1];
146
+ const body = match[2];
147
+
148
+ const fields = [];
149
+ const fieldMatches = body.matchAll(/(\w+)\s+(\w+)(?:\s+@([^{\s]+[^}]*))?/g);
150
+
151
+ for (const fieldMatch of fieldMatches) {
152
+ const fieldName = fieldMatch[1];
153
+ const fieldType = fieldMatch[2];
154
+ const attributes = fieldMatch[3] || '';
155
+
156
+ fields.push({
157
+ name: fieldName,
158
+ type: fieldType,
159
+ attributes: attributes,
160
+ isIndexed: attributes.includes('index') || attributes.includes('unique') || attributes.includes('id'),
161
+ });
162
+ }
163
+
164
+ models.push({
165
+ name: modelName,
166
+ fields: fields,
167
+ });
168
+ }
169
+
170
+ return {
171
+ models,
172
+ recommendations: generateDbRecommendations(models),
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Generate database recommendations
178
+ */
179
+ function generateDbRecommendations(models) {
180
+ const recommendations = [];
181
+
182
+ for (const model of models) {
183
+ // Check for common filter fields without indexes
184
+ const filterFields = ['email', 'username', 'slug', 'status', 'published', 'createdAt'];
185
+
186
+ for (const field of model.fields) {
187
+ if (filterFields.includes(field.name) && !field.isIndexed) {
188
+ recommendations.push({
189
+ type: 'index',
190
+ model: model.name,
191
+ field: field.name,
192
+ message: `Consider adding index on ${model.name}.${field.name}`,
193
+ priority: field.name === 'email' ? 'high' : 'medium',
194
+ });
195
+ }
196
+ }
197
+ }
198
+
199
+ return recommendations;
200
+ }
201
+
202
+ /**
203
+ * Analyze Web Vitals (Lighthouse)
204
+ */
205
+ function analyzeWebVitals(target) {
206
+ // This would run Playwright with metrics
207
+ return runDockerCommand(`npx playwright test --reporter=json`, {
208
+ cwd: path.join(process.cwd(), 'tests', 'e2e'),
209
+ });
210
+ }
211
+
212
+ /**
213
+ * Run command in Docker container
214
+ */
215
+ function runDockerCommand(command, options = {}) {
216
+ const dockerCmd = `${CONFIG.docker} ${CONFIG.container} ${command}`;
217
+
218
+ try {
219
+ return {
220
+ success: true,
221
+ output: execSync(dockerCmd, {
222
+ encoding: 'utf-8',
223
+ stdio: options.silent ? 'pipe' : 'inherit',
224
+ input: options.input,
225
+ }),
226
+ };
227
+ } catch (error) {
228
+ return {
229
+ success: false,
230
+ error: error.message,
231
+ };
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Generate performance report
237
+ */
238
+ function generateReport(analysis) {
239
+ const report = {
240
+ summary: {},
241
+ recommendations: [],
242
+ };
243
+
244
+ // Bundle analysis
245
+ if (analysis.bundle) {
246
+ report.summary.bundle = {
247
+ totalSize: analysis.bundle.totalSizeMB + ' MB',
248
+ largestChunks: analysis.bundle.chunks.slice(0, 5),
249
+ };
250
+
251
+ // Check for large bundles
252
+ analysis.bundle.chunks.forEach((chunk) => {
253
+ if (chunk.size > 200 * 1024) { // > 200KB
254
+ report.recommendations.push({
255
+ type: 'bundle',
256
+ message: `Large chunk: ${chunk.name} (${chunk.sizeKB} KB)`,
257
+ priority: 'medium',
258
+ });
259
+ }
260
+ });
261
+ }
262
+
263
+ // DB analysis
264
+ if (analysis.db) {
265
+ report.summary.database = {
266
+ models: analysis.db.models.length,
267
+ recommendations: analysis.db.recommendations,
268
+ };
269
+ report.recommendations.push(...analysis.db.recommendations);
270
+ }
271
+
272
+ return report;
273
+ }
274
+
275
+ /**
276
+ * Full performance analysis
277
+ */
278
+ function analyze(projectDir) {
279
+ const analysis = {
280
+ bundle: analyzeBundleSize(projectDir),
281
+ db: analyzeDbQueries(projectDir),
282
+ };
283
+
284
+ return generateReport(analysis);
285
+ }
286
+
287
+ module.exports = {
288
+ runK6Test,
289
+ analyzeBundleSize,
290
+ analyzeDbQueries,
291
+ analyzeWebVitals,
292
+ generateReport,
293
+ analyze,
294
+ };