@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,366 +1,366 @@
1
- /**
2
- * Daemon - Dependency Efficiency Analyzer
3
- *
4
- * Analyzes codebase for dependency usage patterns and inefficiencies:
5
- * - TanStack Router patterns
6
- * - React Query usage
7
- * - Prisma query patterns
8
- * - Zustand store patterns
9
- * - React Compiler readiness
10
- */
11
-
12
- const fs = require('fs');
13
- const path = require('path');
14
-
15
- /**
16
- * Find files matching a pattern
17
- */
18
- function findFiles(dir, pattern, excludeDirs = ['node_modules', '.next', 'dist', 'build']) {
19
- const files = [];
20
-
21
- function traverse(currentDir) {
22
- if (!fs.existsSync(currentDir)) return;
23
-
24
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
25
-
26
- for (const entry of entries) {
27
- if (entry.isDirectory()) {
28
- if (excludeDirs.includes(entry.name)) continue;
29
- traverse(path.join(currentDir, entry.name));
30
- } else if (entry.isFile() && entry.name.match(pattern)) {
31
- files.push(path.join(currentDir, entry.name));
32
- }
33
- }
34
- }
35
-
36
- traverse(dir);
37
- return files;
38
- }
39
-
40
- /**
41
- * Read file content
42
- */
43
- function readFile(filePath) {
44
- try {
45
- return fs.readFileSync(filePath, 'utf-8');
46
- } catch {
47
- return '';
48
- }
49
- }
50
-
51
- /**
52
- * Analyze TanStack Router usage
53
- */
54
- function analyzeTanStackRouter(projectDir) {
55
- const findings = {
56
- good: [],
57
- issues: [],
58
- recommendations: [],
59
- };
60
-
61
- const routeFiles = findFiles(path.join(projectDir, 'src'), /routes/);
62
-
63
- for (const file of routeFiles) {
64
- const content = readFile(file);
65
-
66
- // Check for typed routes
67
- if (content.includes('useParams') || content.includes('$')) {
68
- findings.good.push(`Typed params in ${path.relative(projectDir, file)}`);
69
- }
70
-
71
- // Check for loaders
72
- if (content.includes('loader:') || content.includes('loaderBefore')) {
73
- findings.good.push(`Data loader in ${path.relative(projectDir, file)}`);
74
- } else if (content.includes('useQuery') || content.includes('useFetch')) {
75
- findings.issues.push(`Missing loader in ${path.relative(projectDir, file)} - data fetching without loader`);
76
- }
77
-
78
- // Check for error boundaries
79
- if (content.includes('loader:') && !content.includes('errorComponent') && !content.includes('ErrorBoundary')) {
80
- findings.issues.push(`Missing error boundary in ${path.relative(projectDir, file)}`);
81
- findings.recommendations.push(`Add errorComponent to route in ${path.relative(projectDir, file)}`);
82
- }
83
- }
84
-
85
- // Check navigation for prefetching
86
- const componentFiles = findFiles(path.join(projectDir, 'src'), /.*\.(tsx|jsx)$/);
87
- for (const file of componentFiles) {
88
- const content = readFile(file);
89
- if (content.includes('<Link') && !content.includes('prefetch=') && !content.includes('prefetchIntent')) {
90
- findings.issues.push(`Link prefetching not enabled in ${path.relative(projectDir, file)}`);
91
- }
92
- }
93
-
94
- return findings;
95
- }
96
-
97
- /**
98
- * Analyze React Query usage
99
- */
100
- function analyzeReactQuery(projectDir) {
101
- const findings = {
102
- good: [],
103
- issues: [],
104
- recommendations: [],
105
- };
106
-
107
- const hookFiles = findFiles(path.join(projectDir, 'src'), /hooks/);
108
- const componentFiles = findFiles(path.join(projectDir, 'src'), /.*\.(tsx|jsx)$/);
109
- const allFiles = [...hookFiles, ...componentFiles];
110
-
111
- for (const file of allFiles) {
112
- const content = readFile(file);
113
-
114
- if (!content.includes('useQuery') && !content.includes('useMutation')) continue;
115
-
116
- // Check for array cache keys
117
- if (content.includes('useQuery(') || content.includes('useInfiniteQuery(')) {
118
- const hasArrayKey = /\['/.test(content) || /queryKey:\s*\[/.test(content);
119
- if (hasArrayKey) {
120
- findings.good.push(`Array-based cache keys in ${path.relative(projectDir, file)}`);
121
- } else {
122
- findings.issues.push(`Non-array cache keys in ${path.relative(projectDir, file)}`);
123
- findings.recommendations.push(`Use array-based cache keys in ${path.relative(projectDir, file)}`);
124
- }
125
- }
126
-
127
- // Check for staleTime
128
- if (content.includes('useQuery(') && !content.includes('staleTime')) {
129
- findings.issues.push(`Missing staleTime in ${path.relative(projectDir, file)}`);
130
- findings.recommendations.push(`Add staleTime to queries in ${path.relative(projectDir, file)}`);
131
- }
132
-
133
- // Check for mutation invalidation
134
- if (content.includes('useMutation(')) {
135
- if (content.includes('invalidateQueries')) {
136
- findings.good.push(`Proper invalidation in ${path.relative(projectDir, file)}`);
137
- } else {
138
- findings.issues.push(`Missing invalidation in mutation at ${path.relative(projectDir, file)}`);
139
- }
140
- }
141
- }
142
-
143
- return findings;
144
- }
145
-
146
- /**
147
- * Analyze Prisma usage
148
- */
149
- function analyzePrisma(projectDir) {
150
- const findings = {
151
- good: [],
152
- issues: [],
153
- recommendations: [],
154
- };
155
-
156
- const libFiles = findFiles(path.join(projectDir, 'src'), /.*\.(ts|js)$/);
157
-
158
- for (const file of libFiles) {
159
- const content = readFile(file);
160
-
161
- if (!content.includes('prisma.')) continue;
162
-
163
- // Check for select usage
164
- if (content.includes('prisma.') && content.includes('findMany')) {
165
- if (content.includes('select:')) {
166
- findings.good.push(`Using select in ${path.relative(projectDir, file)}`);
167
- } else if (content.includes('.findMany().then')) {
168
- findings.issues.push(`Not using select in ${path.relative(projectDir, file)} - returning full objects`);
169
- findings.recommendations.push(`Add select to prisma queries in ${path.relative(projectDir, file)}`);
170
- }
171
- }
172
-
173
- // Check for potential N+1
174
- const lines = content.split('\n');
175
- for (let i = 0; i < lines.length; i++) {
176
- if (lines[i].includes('findMany') || lines[i].includes('findFirst')) {
177
- // Check next 10 lines for forEach with prisma query
178
- for (let j = i + 1; j < Math.min(i + 10, lines.length); j++) {
179
- if (lines[j].includes('forEach') && lines[j].includes('prisma.')) {
180
- findings.issues.push(`Potential N+1 query in ${path.relative(projectDir, file)}:${i + 1}`);
181
- findings.recommendations.push(`Use include or separate query with WHERE in ${path.relative(projectDir, file)}`);
182
- break;
183
- }
184
- }
185
- }
186
- }
187
- }
188
-
189
- // Check schema for indexes
190
- const schemaPath = path.join(projectDir, 'prisma', 'schema.prisma');
191
- if (fs.existsSync(schemaPath)) {
192
- const schema = readFile(schemaPath);
193
- const models = schema.matchAll(/model\s+(\w+)\s*{([^}]+)}/g);
194
-
195
- for (const modelMatch of models) {
196
- const modelName = modelMatch[1];
197
- const body = modelMatch[2];
198
-
199
- if (body.includes('email') && !body.includes('@@index') && !body.includes('@@unique')) {
200
- findings.recommendations.push(`Add index on ${modelName}.email for faster lookups`);
201
- }
202
- }
203
- }
204
-
205
- return findings;
206
- }
207
-
208
- /**
209
- * Analyze Zustand usage
210
- */
211
- function analyzeZustand(projectDir) {
212
- const findings = {
213
- good: [],
214
- issues: [],
215
- recommendations: [],
216
- };
217
-
218
- const storeFiles = findFiles(path.join(projectDir, 'src'), /store|stores/);
219
- const componentFiles = findFiles(path.join(projectDir, 'src'), /.*\.(tsx|jsx)$/);
220
-
221
- for (const file of storeFiles) {
222
- const content = readFile(file);
223
-
224
- // Check store size
225
- const lines = content.split('\n').length;
226
- if (lines > 500) {
227
- findings.issues.push(`Large store file ${path.relative(projectDir, file)} (${lines} lines)`);
228
- findings.recommendations.push(`Consider splitting ${path.basename(file)} into multiple stores`);
229
- }
230
- }
231
-
232
- for (const file of componentFiles) {
233
- const content = readFile(file);
234
-
235
- if (!content.includes('useStore')) continue;
236
-
237
- // Check for full-store subscriptions
238
- if (content.includes('useStore()') || content.match(/useStore\(\s*state\s*=>\s*state/)) {
239
- findings.issues.push(`Full-store subscription in ${path.relative(projectDir, file)}`);
240
- findings.recommendations.push(`Use selectors for specific fields in ${path.relative(projectDir, file)}`);
241
- }
242
- }
243
-
244
- return findings;
245
- }
246
-
247
- /**
248
- * Analyze React Compiler readiness
249
- */
250
- function analyzeReactCompilerReadiness(projectDir) {
251
- const findings = {
252
- good: [],
253
- issues: [],
254
- recommendations: [],
255
- };
256
-
257
- const componentFiles = findFiles(path.join(projectDir, 'src'), /.*\.(tsx|jsx)$/);
258
-
259
- for (const file of componentFiles) {
260
- const content = readFile(file);
261
-
262
- // Check for simple useMemo that can be removed
263
- const simpleMemo = content.match(/useMemo\(\(\)\s*=>\s*([^,]+),\s*\[[^\]]*\]\)/g);
264
- if (simpleMemo) {
265
- for (const memo of simpleMemo) {
266
- const value = memo.match(/=>\s*(.+),/)?.[1];
267
- if (value && !value.includes('()') && !value.includes('function')) {
268
- findings.recommendations.push(`Remove simple useMemo in ${path.basename(file)} - React Compiler will handle this`);
269
- }
270
- }
271
- }
272
-
273
- // Check for useCallback dependencies
274
- const useCallbacks = content.matchAll(/useCallback\([^)]+\)/g);
275
- for (const callback of useCallbacks) {
276
- const deps = callback[0].match(/\[([^\]]*)\]/)?.[1];
277
- if (deps && deps.trim() === '') {
278
- findings.issues.push(`useCallback with empty deps in ${path.basename(file)}`);
279
- }
280
- }
281
-
282
- // Check for large inline objects
283
- const largeObjects = content.match(/{{[\s\S]{200,}}}/g);
284
- if (largeObjects) {
285
- findings.issues.push(`Large inline object in ${path.basename(file)} - move outside component`);
286
- findings.recommendations.push(`Extract large objects to constants in ${path.basename(file)}`);
287
- }
288
- }
289
-
290
- return findings;
291
- }
292
-
293
- /**
294
- * Analyze bundle optimization
295
- */
296
- function analyzeBundleOptimization(projectDir) {
297
- const findings = {
298
- good: [],
299
- issues: [],
300
- recommendations: [],
301
- };
302
-
303
- const files = findFiles(path.join(projectDir, 'src'), /.*\.(ts|tsx|js|jsx)$/);
304
-
305
- for (const file of files) {
306
- const content = readFile(file);
307
-
308
- // Check for namespace imports
309
- if (content.includes('* as ')) {
310
- findings.issues.push(`Namespace import in ${path.basename(file)}`);
311
- findings.recommendations.push(`Use named imports for better tree-shaking in ${path.basename(file)}`);
312
- }
313
-
314
- // Check for large library imports
315
- const largeLibs = ['monaco-editor', 'codemirror', 'pdfjs-dist', 'fabric'];
316
- for (const lib of largeLibs) {
317
- if (content.includes(`from '${lib}'`) || content.includes(`from "${lib}"`)) {
318
- if (!content.includes('dynamic(') && !content.includes('React.lazy')) {
319
- findings.recommendations.push(`Use dynamic import for ${lib} in ${path.basename(file)}`);
320
- }
321
- }
322
- }
323
- }
324
-
325
- // Check package.json for duplicate dependencies
326
- const pkgPath = path.join(projectDir, 'package.json');
327
- if (fs.existsSync(pkgPath)) {
328
- const pkg = JSON.parse(readFile(pkgPath));
329
- const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
330
-
331
- for (const [name, version] of Object.entries(deps)) {
332
- if (name.startsWith('@types/')) {
333
- const mainPkg = name.substring(6);
334
- if (mainPkg in deps) {
335
- findings.good.push(`Types package for ${mainPkg} found`);
336
- }
337
- }
338
- }
339
- }
340
-
341
- return findings;
342
- }
343
-
344
- /**
345
- * Run full analysis
346
- */
347
- function analyze(projectDir) {
348
- return {
349
- tanStackRouter: analyzeTanStackRouter(projectDir),
350
- reactQuery: analyzeReactQuery(projectDir),
351
- prisma: analyzePrisma(projectDir),
352
- zustand: analyzeZustand(projectDir),
353
- reactCompiler: analyzeReactCompilerReadiness(projectDir),
354
- bundleOptimization: analyzeBundleOptimization(projectDir),
355
- };
356
- }
357
-
358
- module.exports = {
359
- analyze,
360
- analyzeTanStackRouter,
361
- analyzeReactQuery,
362
- analyzePrisma,
363
- analyzeZustand,
364
- analyzeReactCompilerReadiness,
365
- analyzeBundleOptimization,
366
- };
1
+ /**
2
+ * Daemon - Dependency Efficiency Analyzer
3
+ *
4
+ * Analyzes codebase for dependency usage patterns and inefficiencies:
5
+ * - TanStack Router patterns
6
+ * - React Query usage
7
+ * - Prisma query patterns
8
+ * - Zustand store patterns
9
+ * - React Compiler readiness
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ /**
16
+ * Find files matching a pattern
17
+ */
18
+ function findFiles(dir, pattern, excludeDirs = ['node_modules', '.next', 'dist', 'build']) {
19
+ const files = [];
20
+
21
+ function traverse(currentDir) {
22
+ if (!fs.existsSync(currentDir)) return;
23
+
24
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
25
+
26
+ for (const entry of entries) {
27
+ if (entry.isDirectory()) {
28
+ if (excludeDirs.includes(entry.name)) continue;
29
+ traverse(path.join(currentDir, entry.name));
30
+ } else if (entry.isFile() && entry.name.match(pattern)) {
31
+ files.push(path.join(currentDir, entry.name));
32
+ }
33
+ }
34
+ }
35
+
36
+ traverse(dir);
37
+ return files;
38
+ }
39
+
40
+ /**
41
+ * Read file content
42
+ */
43
+ function readFile(filePath) {
44
+ try {
45
+ return fs.readFileSync(filePath, 'utf-8');
46
+ } catch {
47
+ return '';
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Analyze TanStack Router usage
53
+ */
54
+ function analyzeTanStackRouter(projectDir) {
55
+ const findings = {
56
+ good: [],
57
+ issues: [],
58
+ recommendations: [],
59
+ };
60
+
61
+ const routeFiles = findFiles(path.join(projectDir, 'src'), /routes/);
62
+
63
+ for (const file of routeFiles) {
64
+ const content = readFile(file);
65
+
66
+ // Check for typed routes
67
+ if (content.includes('useParams') || content.includes('$')) {
68
+ findings.good.push(`Typed params in ${path.relative(projectDir, file)}`);
69
+ }
70
+
71
+ // Check for loaders
72
+ if (content.includes('loader:') || content.includes('loaderBefore')) {
73
+ findings.good.push(`Data loader in ${path.relative(projectDir, file)}`);
74
+ } else if (content.includes('useQuery') || content.includes('useFetch')) {
75
+ findings.issues.push(`Missing loader in ${path.relative(projectDir, file)} - data fetching without loader`);
76
+ }
77
+
78
+ // Check for error boundaries
79
+ if (content.includes('loader:') && !content.includes('errorComponent') && !content.includes('ErrorBoundary')) {
80
+ findings.issues.push(`Missing error boundary in ${path.relative(projectDir, file)}`);
81
+ findings.recommendations.push(`Add errorComponent to route in ${path.relative(projectDir, file)}`);
82
+ }
83
+ }
84
+
85
+ // Check navigation for prefetching
86
+ const componentFiles = findFiles(path.join(projectDir, 'src'), /.*\.(tsx|jsx)$/);
87
+ for (const file of componentFiles) {
88
+ const content = readFile(file);
89
+ if (content.includes('<Link') && !content.includes('prefetch=') && !content.includes('prefetchIntent')) {
90
+ findings.issues.push(`Link prefetching not enabled in ${path.relative(projectDir, file)}`);
91
+ }
92
+ }
93
+
94
+ return findings;
95
+ }
96
+
97
+ /**
98
+ * Analyze React Query usage
99
+ */
100
+ function analyzeReactQuery(projectDir) {
101
+ const findings = {
102
+ good: [],
103
+ issues: [],
104
+ recommendations: [],
105
+ };
106
+
107
+ const hookFiles = findFiles(path.join(projectDir, 'src'), /hooks/);
108
+ const componentFiles = findFiles(path.join(projectDir, 'src'), /.*\.(tsx|jsx)$/);
109
+ const allFiles = [...hookFiles, ...componentFiles];
110
+
111
+ for (const file of allFiles) {
112
+ const content = readFile(file);
113
+
114
+ if (!content.includes('useQuery') && !content.includes('useMutation')) continue;
115
+
116
+ // Check for array cache keys
117
+ if (content.includes('useQuery(') || content.includes('useInfiniteQuery(')) {
118
+ const hasArrayKey = /\['/.test(content) || /queryKey:\s*\[/.test(content);
119
+ if (hasArrayKey) {
120
+ findings.good.push(`Array-based cache keys in ${path.relative(projectDir, file)}`);
121
+ } else {
122
+ findings.issues.push(`Non-array cache keys in ${path.relative(projectDir, file)}`);
123
+ findings.recommendations.push(`Use array-based cache keys in ${path.relative(projectDir, file)}`);
124
+ }
125
+ }
126
+
127
+ // Check for staleTime
128
+ if (content.includes('useQuery(') && !content.includes('staleTime')) {
129
+ findings.issues.push(`Missing staleTime in ${path.relative(projectDir, file)}`);
130
+ findings.recommendations.push(`Add staleTime to queries in ${path.relative(projectDir, file)}`);
131
+ }
132
+
133
+ // Check for mutation invalidation
134
+ if (content.includes('useMutation(')) {
135
+ if (content.includes('invalidateQueries')) {
136
+ findings.good.push(`Proper invalidation in ${path.relative(projectDir, file)}`);
137
+ } else {
138
+ findings.issues.push(`Missing invalidation in mutation at ${path.relative(projectDir, file)}`);
139
+ }
140
+ }
141
+ }
142
+
143
+ return findings;
144
+ }
145
+
146
+ /**
147
+ * Analyze Prisma usage
148
+ */
149
+ function analyzePrisma(projectDir) {
150
+ const findings = {
151
+ good: [],
152
+ issues: [],
153
+ recommendations: [],
154
+ };
155
+
156
+ const libFiles = findFiles(path.join(projectDir, 'src'), /.*\.(ts|js)$/);
157
+
158
+ for (const file of libFiles) {
159
+ const content = readFile(file);
160
+
161
+ if (!content.includes('prisma.')) continue;
162
+
163
+ // Check for select usage
164
+ if (content.includes('prisma.') && content.includes('findMany')) {
165
+ if (content.includes('select:')) {
166
+ findings.good.push(`Using select in ${path.relative(projectDir, file)}`);
167
+ } else if (content.includes('.findMany().then')) {
168
+ findings.issues.push(`Not using select in ${path.relative(projectDir, file)} - returning full objects`);
169
+ findings.recommendations.push(`Add select to prisma queries in ${path.relative(projectDir, file)}`);
170
+ }
171
+ }
172
+
173
+ // Check for potential N+1
174
+ const lines = content.split('\n');
175
+ for (let i = 0; i < lines.length; i++) {
176
+ if (lines[i].includes('findMany') || lines[i].includes('findFirst')) {
177
+ // Check next 10 lines for forEach with prisma query
178
+ for (let j = i + 1; j < Math.min(i + 10, lines.length); j++) {
179
+ if (lines[j].includes('forEach') && lines[j].includes('prisma.')) {
180
+ findings.issues.push(`Potential N+1 query in ${path.relative(projectDir, file)}:${i + 1}`);
181
+ findings.recommendations.push(`Use include or separate query with WHERE in ${path.relative(projectDir, file)}`);
182
+ break;
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+ // Check schema for indexes
190
+ const schemaPath = path.join(projectDir, 'prisma', 'schema.prisma');
191
+ if (fs.existsSync(schemaPath)) {
192
+ const schema = readFile(schemaPath);
193
+ const models = schema.matchAll(/model\s+(\w+)\s*{([^}]+)}/g);
194
+
195
+ for (const modelMatch of models) {
196
+ const modelName = modelMatch[1];
197
+ const body = modelMatch[2];
198
+
199
+ if (body.includes('email') && !body.includes('@@index') && !body.includes('@@unique')) {
200
+ findings.recommendations.push(`Add index on ${modelName}.email for faster lookups`);
201
+ }
202
+ }
203
+ }
204
+
205
+ return findings;
206
+ }
207
+
208
+ /**
209
+ * Analyze Zustand usage
210
+ */
211
+ function analyzeZustand(projectDir) {
212
+ const findings = {
213
+ good: [],
214
+ issues: [],
215
+ recommendations: [],
216
+ };
217
+
218
+ const storeFiles = findFiles(path.join(projectDir, 'src'), /store|stores/);
219
+ const componentFiles = findFiles(path.join(projectDir, 'src'), /.*\.(tsx|jsx)$/);
220
+
221
+ for (const file of storeFiles) {
222
+ const content = readFile(file);
223
+
224
+ // Check store size
225
+ const lines = content.split('\n').length;
226
+ if (lines > 500) {
227
+ findings.issues.push(`Large store file ${path.relative(projectDir, file)} (${lines} lines)`);
228
+ findings.recommendations.push(`Consider splitting ${path.basename(file)} into multiple stores`);
229
+ }
230
+ }
231
+
232
+ for (const file of componentFiles) {
233
+ const content = readFile(file);
234
+
235
+ if (!content.includes('useStore')) continue;
236
+
237
+ // Check for full-store subscriptions
238
+ if (content.includes('useStore()') || content.match(/useStore\(\s*state\s*=>\s*state/)) {
239
+ findings.issues.push(`Full-store subscription in ${path.relative(projectDir, file)}`);
240
+ findings.recommendations.push(`Use selectors for specific fields in ${path.relative(projectDir, file)}`);
241
+ }
242
+ }
243
+
244
+ return findings;
245
+ }
246
+
247
+ /**
248
+ * Analyze React Compiler readiness
249
+ */
250
+ function analyzeReactCompilerReadiness(projectDir) {
251
+ const findings = {
252
+ good: [],
253
+ issues: [],
254
+ recommendations: [],
255
+ };
256
+
257
+ const componentFiles = findFiles(path.join(projectDir, 'src'), /.*\.(tsx|jsx)$/);
258
+
259
+ for (const file of componentFiles) {
260
+ const content = readFile(file);
261
+
262
+ // Check for simple useMemo that can be removed
263
+ const simpleMemo = content.match(/useMemo\(\(\)\s*=>\s*([^,]+),\s*\[[^\]]*\]\)/g);
264
+ if (simpleMemo) {
265
+ for (const memo of simpleMemo) {
266
+ const value = memo.match(/=>\s*(.+),/)?.[1];
267
+ if (value && !value.includes('()') && !value.includes('function')) {
268
+ findings.recommendations.push(`Remove simple useMemo in ${path.basename(file)} - React Compiler will handle this`);
269
+ }
270
+ }
271
+ }
272
+
273
+ // Check for useCallback dependencies
274
+ const useCallbacks = content.matchAll(/useCallback\([^)]+\)/g);
275
+ for (const callback of useCallbacks) {
276
+ const deps = callback[0].match(/\[([^\]]*)\]/)?.[1];
277
+ if (deps && deps.trim() === '') {
278
+ findings.issues.push(`useCallback with empty deps in ${path.basename(file)}`);
279
+ }
280
+ }
281
+
282
+ // Check for large inline objects
283
+ const largeObjects = content.match(/{{[\s\S]{200,}}}/g);
284
+ if (largeObjects) {
285
+ findings.issues.push(`Large inline object in ${path.basename(file)} - move outside component`);
286
+ findings.recommendations.push(`Extract large objects to constants in ${path.basename(file)}`);
287
+ }
288
+ }
289
+
290
+ return findings;
291
+ }
292
+
293
+ /**
294
+ * Analyze bundle optimization
295
+ */
296
+ function analyzeBundleOptimization(projectDir) {
297
+ const findings = {
298
+ good: [],
299
+ issues: [],
300
+ recommendations: [],
301
+ };
302
+
303
+ const files = findFiles(path.join(projectDir, 'src'), /.*\.(ts|tsx|js|jsx)$/);
304
+
305
+ for (const file of files) {
306
+ const content = readFile(file);
307
+
308
+ // Check for namespace imports
309
+ if (content.includes('* as ')) {
310
+ findings.issues.push(`Namespace import in ${path.basename(file)}`);
311
+ findings.recommendations.push(`Use named imports for better tree-shaking in ${path.basename(file)}`);
312
+ }
313
+
314
+ // Check for large library imports
315
+ const largeLibs = ['monaco-editor', 'codemirror', 'pdfjs-dist', 'fabric'];
316
+ for (const lib of largeLibs) {
317
+ if (content.includes(`from '${lib}'`) || content.includes(`from "${lib}"`)) {
318
+ if (!content.includes('dynamic(') && !content.includes('React.lazy')) {
319
+ findings.recommendations.push(`Use dynamic import for ${lib} in ${path.basename(file)}`);
320
+ }
321
+ }
322
+ }
323
+ }
324
+
325
+ // Check package.json for duplicate dependencies
326
+ const pkgPath = path.join(projectDir, 'package.json');
327
+ if (fs.existsSync(pkgPath)) {
328
+ const pkg = JSON.parse(readFile(pkgPath));
329
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
330
+
331
+ for (const [name, version] of Object.entries(deps)) {
332
+ if (name.startsWith('@types/')) {
333
+ const mainPkg = name.substring(6);
334
+ if (mainPkg in deps) {
335
+ findings.good.push(`Types package for ${mainPkg} found`);
336
+ }
337
+ }
338
+ }
339
+ }
340
+
341
+ return findings;
342
+ }
343
+
344
+ /**
345
+ * Run full analysis
346
+ */
347
+ function analyze(projectDir) {
348
+ return {
349
+ tanStackRouter: analyzeTanStackRouter(projectDir),
350
+ reactQuery: analyzeReactQuery(projectDir),
351
+ prisma: analyzePrisma(projectDir),
352
+ zustand: analyzeZustand(projectDir),
353
+ reactCompiler: analyzeReactCompilerReadiness(projectDir),
354
+ bundleOptimization: analyzeBundleOptimization(projectDir),
355
+ };
356
+ }
357
+
358
+ module.exports = {
359
+ analyze,
360
+ analyzeTanStackRouter,
361
+ analyzeReactQuery,
362
+ analyzePrisma,
363
+ analyzeZustand,
364
+ analyzeReactCompilerReadiness,
365
+ analyzeBundleOptimization,
366
+ };