@vibecheckai/cli 3.0.3 → 3.0.4

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 (69) hide show
  1. package/bin/cli-hygiene.js +241 -0
  2. package/bin/guardrail.js +834 -0
  3. package/bin/runners/cli-utils.js +1070 -0
  4. package/bin/runners/context/ai-task-decomposer.js +337 -0
  5. package/bin/runners/context/analyzer.js +462 -0
  6. package/bin/runners/context/api-contracts.js +427 -0
  7. package/bin/runners/context/context-diff.js +342 -0
  8. package/bin/runners/context/context-pruner.js +291 -0
  9. package/bin/runners/context/dependency-graph.js +414 -0
  10. package/bin/runners/context/generators/claude.js +107 -0
  11. package/bin/runners/context/generators/codex.js +108 -0
  12. package/bin/runners/context/generators/copilot.js +119 -0
  13. package/bin/runners/context/generators/cursor.js +514 -0
  14. package/bin/runners/context/generators/mcp.js +151 -0
  15. package/bin/runners/context/generators/windsurf.js +180 -0
  16. package/bin/runners/context/git-context.js +302 -0
  17. package/bin/runners/context/index.js +1042 -0
  18. package/bin/runners/context/insights.js +173 -0
  19. package/bin/runners/context/mcp-server/generate-rules.js +337 -0
  20. package/bin/runners/context/mcp-server/index.js +1176 -0
  21. package/bin/runners/context/mcp-server/package.json +24 -0
  22. package/bin/runners/context/memory.js +200 -0
  23. package/bin/runners/context/monorepo.js +215 -0
  24. package/bin/runners/context/multi-repo-federation.js +404 -0
  25. package/bin/runners/context/patterns.js +253 -0
  26. package/bin/runners/context/proof-context.js +972 -0
  27. package/bin/runners/context/security-scanner.js +303 -0
  28. package/bin/runners/context/semantic-search.js +350 -0
  29. package/bin/runners/context/shared.js +264 -0
  30. package/bin/runners/context/team-conventions.js +310 -0
  31. package/bin/runners/lib/ai-bridge.js +416 -0
  32. package/bin/runners/lib/analysis-core.js +271 -0
  33. package/bin/runners/lib/analyzers.js +541 -0
  34. package/bin/runners/lib/audit-bridge.js +391 -0
  35. package/bin/runners/lib/auth-truth.js +193 -0
  36. package/bin/runners/lib/auth.js +215 -0
  37. package/bin/runners/lib/backup.js +62 -0
  38. package/bin/runners/lib/billing.js +107 -0
  39. package/bin/runners/lib/claims.js +118 -0
  40. package/bin/runners/lib/cli-ui.js +540 -0
  41. package/bin/runners/lib/compliance-bridge-new.js +0 -0
  42. package/bin/runners/lib/compliance-bridge.js +165 -0
  43. package/bin/runners/lib/contracts/auth-contract.js +194 -0
  44. package/bin/runners/lib/contracts/env-contract.js +178 -0
  45. package/bin/runners/lib/contracts/external-contract.js +198 -0
  46. package/bin/runners/lib/contracts/guard.js +168 -0
  47. package/bin/runners/lib/contracts/index.js +89 -0
  48. package/bin/runners/lib/contracts/plan-validator.js +311 -0
  49. package/bin/runners/lib/contracts/route-contract.js +192 -0
  50. package/bin/runners/lib/detect.js +89 -0
  51. package/bin/runners/lib/doctor/autofix.js +254 -0
  52. package/bin/runners/lib/doctor/index.js +37 -0
  53. package/bin/runners/lib/doctor/modules/dependencies.js +325 -0
  54. package/bin/runners/lib/doctor/modules/index.js +46 -0
  55. package/bin/runners/lib/doctor/modules/network.js +250 -0
  56. package/bin/runners/lib/doctor/modules/project.js +312 -0
  57. package/bin/runners/lib/doctor/modules/runtime.js +224 -0
  58. package/bin/runners/lib/doctor/modules/security.js +348 -0
  59. package/bin/runners/lib/doctor/modules/system.js +213 -0
  60. package/bin/runners/lib/doctor/modules/vibecheck.js +394 -0
  61. package/bin/runners/lib/doctor/reporter.js +262 -0
  62. package/bin/runners/lib/doctor/service.js +262 -0
  63. package/bin/runners/lib/doctor/types.js +113 -0
  64. package/bin/runners/lib/doctor/ui.js +263 -0
  65. package/bin/runners/lib/doctor-enhanced.js +233 -0
  66. package/bin/runners/lib/doctor-v2.js +608 -0
  67. package/bin/runners/lib/enforcement.js +72 -0
  68. package/bin/vibecheck.js +0 -0
  69. package/package.json +8 -9
@@ -0,0 +1,404 @@
1
+ /**
2
+ * Multi-Repo Context Federation Module
3
+ * Unified context across related repositories
4
+ */
5
+
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const os = require("os");
9
+
10
+ const VIBECHECK_HOME = path.join(os.homedir(), ".vibecheck");
11
+ const FEDERATION_FILE = path.join(VIBECHECK_HOME, "repo-federation.json");
12
+
13
+ /**
14
+ * Initialize federation config
15
+ */
16
+ function initializeFederation() {
17
+ if (!fs.existsSync(VIBECHECK_HOME)) {
18
+ fs.mkdirSync(VIBECHECK_HOME, { recursive: true });
19
+ }
20
+
21
+ if (!fs.existsSync(FEDERATION_FILE)) {
22
+ fs.writeFileSync(FEDERATION_FILE, JSON.stringify({
23
+ version: "1.0.0",
24
+ groups: {},
25
+ repositories: {},
26
+ sharedArtifacts: {},
27
+ }, null, 2));
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Load federation config
33
+ */
34
+ function loadFederation() {
35
+ initializeFederation();
36
+ try {
37
+ return JSON.parse(fs.readFileSync(FEDERATION_FILE, "utf-8"));
38
+ } catch {
39
+ return { groups: {}, repositories: {}, sharedArtifacts: {} };
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Save federation config
45
+ */
46
+ function saveFederation(config) {
47
+ initializeFederation();
48
+ fs.writeFileSync(FEDERATION_FILE, JSON.stringify(config, null, 2));
49
+ }
50
+
51
+ /**
52
+ * Register repository in federation
53
+ */
54
+ function registerRepository(repoPath, options = {}) {
55
+ const config = loadFederation();
56
+ const repoId = path.basename(repoPath);
57
+
58
+ // Detect repo type and patterns
59
+ const packageJsonPath = path.join(repoPath, "package.json");
60
+ let repoInfo = {
61
+ id: repoId,
62
+ path: repoPath,
63
+ type: "unknown",
64
+ registered: new Date().toISOString(),
65
+ ...options,
66
+ };
67
+
68
+ if (fs.existsSync(packageJsonPath)) {
69
+ try {
70
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
71
+ repoInfo.type = pkg.private ? "private" : "public";
72
+ repoInfo.name = pkg.name;
73
+ repoInfo.description = pkg.description;
74
+ repoInfo.dependencies = Object.keys(pkg.dependencies || {});
75
+ repoInfo.devDependencies = Object.keys(pkg.devDependencies || {});
76
+
77
+ // Detect framework
78
+ if (pkg.dependencies?.next) repoInfo.framework = "Next.js";
79
+ else if (pkg.dependencies?.react) repoInfo.framework = "React";
80
+ else if (pkg.dependencies?.express) repoInfo.framework = "Express";
81
+ else if (pkg.dependencies?.vue) repoInfo.framework = "Vue";
82
+ } catch {}
83
+ }
84
+
85
+ config.repositories[repoId] = repoInfo;
86
+
87
+ // Auto-add to groups based on patterns
88
+ if (repoInfo.framework) {
89
+ const groupName = `${repoInfo.framework.toLowerCase()}-projects`;
90
+ if (!config.groups[groupName]) {
91
+ config.groups[groupName] = {
92
+ name: groupName,
93
+ description: `${repoInfo.framework} projects`,
94
+ repositories: [],
95
+ };
96
+ }
97
+ if (!config.groups[groupName].repositories.includes(repoId)) {
98
+ config.groups[groupName].repositories.push(repoId);
99
+ }
100
+ }
101
+
102
+ saveFederation(config);
103
+ return repoInfo;
104
+ }
105
+
106
+ /**
107
+ * Create repository group
108
+ */
109
+ function createGroup(name, description, repoIds = []) {
110
+ const config = loadFederation();
111
+
112
+ config.groups[name] = {
113
+ name,
114
+ description,
115
+ repositories: repoIds,
116
+ created: new Date().toISOString(),
117
+ };
118
+
119
+ saveFederation(config);
120
+ }
121
+
122
+ /**
123
+ * Get shared artifacts across repositories
124
+ */
125
+ function getSharedArtifacts(groupName = null) {
126
+ const config = loadFederation();
127
+ const artifacts = {
128
+ components: new Map(),
129
+ hooks: new Map(),
130
+ utilities: new Map(),
131
+ types: new Map(),
132
+ patterns: new Map(),
133
+ };
134
+
135
+ const repos = groupName
136
+ ? config.groups[groupName]?.repositories || []
137
+ : Object.keys(config.repositories);
138
+
139
+ for (const repoId of repos) {
140
+ const repo = config.repositories[repoId];
141
+ if (!repo) continue;
142
+
143
+ // Load context from each repo
144
+ const contextFile = path.join(repo.path, ".vibecheck", "context.json");
145
+ if (fs.existsSync(contextFile)) {
146
+ try {
147
+ const context = JSON.parse(fs.readFileSync(contextFile, "utf-8"));
148
+
149
+ // Collect components
150
+ if (context.structure?.components) {
151
+ for (const comp of context.structure.components) {
152
+ if (!artifacts.components.has(comp)) {
153
+ artifacts.components.set(comp, []);
154
+ }
155
+ artifacts.components.get(comp).push({
156
+ repo: repoId,
157
+ path: comp,
158
+ framework: context.project?.framework,
159
+ });
160
+ }
161
+ }
162
+
163
+ // Collect hooks
164
+ if (context.patterns?.hooks) {
165
+ for (const hook of context.patterns.hooks) {
166
+ if (!artifacts.hooks.has(hook)) {
167
+ artifacts.hooks.set(hook, []);
168
+ }
169
+ artifacts.hooks.get(hook).push({
170
+ repo: repoId,
171
+ name: hook,
172
+ });
173
+ }
174
+ }
175
+
176
+ // Collect patterns
177
+ if (context.patterns?.stateManagement) {
178
+ const pattern = context.patterns.stateManagement;
179
+ if (!artifacts.patterns.has(pattern)) {
180
+ artifacts.patterns.set(pattern, []);
181
+ }
182
+ artifacts.patterns.get(pattern).push(repoId);
183
+ }
184
+
185
+ // Collect types
186
+ if (context.types?.interfaces) {
187
+ for (const type of context.types.interfaces) {
188
+ if (!artifacts.types.has(type)) {
189
+ artifacts.types.set(type, []);
190
+ }
191
+ artifacts.types.get(type).push({
192
+ repo: repoId,
193
+ interface: type,
194
+ });
195
+ }
196
+ }
197
+ } catch {}
198
+ }
199
+ }
200
+
201
+ // Convert Maps to arrays and filter for shared items
202
+ const shared = {};
203
+ for (const [key, map] of Object.entries(artifacts)) {
204
+ shared[key] = Array.from(map.entries())
205
+ .filter(([_, items]) => items.length > 1) // Only keep items shared across repos
206
+ .map(([name, items]) => ({
207
+ name,
208
+ repositories: items,
209
+ count: items.length,
210
+ }))
211
+ .sort((a, b) => b.count - a.count);
212
+ }
213
+
214
+ return shared;
215
+ }
216
+
217
+ /**
218
+ * Generate federated context
219
+ */
220
+ function generateFederatedContext(groupName = null, options = {}) {
221
+ const { maxTokens = 8000, includePatterns = true } = options;
222
+ const config = loadFederation();
223
+
224
+ const repos = groupName
225
+ ? config.groups[groupName]?.repositories || []
226
+ : Object.keys(config.repositories);
227
+
228
+ const federated = {
229
+ version: "1.0.0",
230
+ generated: new Date().toISOString(),
231
+ group: groupName,
232
+ repositories: repos.map(id => ({
233
+ id,
234
+ ...config.repositories[id],
235
+ })),
236
+ sharedArtifacts: getSharedArtifacts(groupName),
237
+ context: {
238
+ components: [],
239
+ hooks: [],
240
+ patterns: [],
241
+ types: [],
242
+ },
243
+ stats: {
244
+ totalRepos: repos.length,
245
+ sharedComponents: 0,
246
+ sharedHooks: 0,
247
+ sharedPatterns: 0,
248
+ },
249
+ };
250
+
251
+ // Build unified context
252
+ let totalTokens = 0;
253
+
254
+ // Add shared components
255
+ for (const comp of federated.sharedArtifacts.components.slice(0, 20)) {
256
+ if (totalTokens > maxTokens * 0.4) break;
257
+
258
+ federated.context.components.push({
259
+ name: comp.name,
260
+ usage: comp.repositories,
261
+ example: comp.repositories[0],
262
+ });
263
+ federated.stats.sharedComponents++;
264
+ }
265
+
266
+ // Add shared hooks
267
+ for (const hook of federated.sharedArtifacts.hooks.slice(0, 15)) {
268
+ if (totalTokens > maxTokens * 0.6) break;
269
+
270
+ federated.context.hooks.push({
271
+ name: hook.name,
272
+ repos: hook.repositories,
273
+ });
274
+ federated.stats.sharedHooks++;
275
+ }
276
+
277
+ // Add shared patterns
278
+ for (const pattern of federated.sharedArtifacts.patterns) {
279
+ if (totalTokens > maxTokens * 0.8) break;
280
+
281
+ federated.context.patterns.push({
282
+ name: pattern.name,
283
+ repos: pattern.repositories,
284
+ });
285
+ federated.stats.sharedPatterns++;
286
+ }
287
+
288
+ return federated;
289
+ }
290
+
291
+ /**
292
+ * Find related repositories
293
+ */
294
+ function findRelatedRepositories(repoPath, limit = 5) {
295
+ const config = loadFederation();
296
+ const repoId = path.basename(repoPath);
297
+ const repo = config.repositories[repoId];
298
+
299
+ if (!repo) return [];
300
+
301
+ const related = [];
302
+
303
+ // Find repos with similar dependencies
304
+ for (const [id, otherRepo] of Object.entries(config.repositories)) {
305
+ if (id === repoId) continue;
306
+
307
+ let similarity = 0;
308
+
309
+ // Same framework
310
+ if (repo.framework === otherRepo.framework) {
311
+ similarity += 3;
312
+ }
313
+
314
+ // Shared dependencies
315
+ const sharedDeps = (repo.dependencies || []).filter(d =>
316
+ (otherRepo.dependencies || []).includes(d)
317
+ ).length;
318
+ similarity += sharedDeps * 0.5;
319
+
320
+ // Same type (private/public)
321
+ if (repo.type === otherRepo.type) {
322
+ similarity += 1;
323
+ }
324
+
325
+ if (similarity > 0) {
326
+ related.push({
327
+ id,
328
+ ...otherRepo,
329
+ similarity,
330
+ });
331
+ }
332
+ }
333
+
334
+ return related
335
+ .sort((a, b) => b.similarity - a.similarity)
336
+ .slice(0, limit);
337
+ }
338
+
339
+ /**
340
+ * Sync federation with remote
341
+ */
342
+ function syncFederation(remoteUrl) {
343
+ // In a real implementation, this would sync with a remote service
344
+ // For now, just mark as synced
345
+ const config = loadFederation();
346
+ config.lastSync = new Date().toISOString();
347
+ config.remoteUrl = remoteUrl;
348
+ saveFederation(config);
349
+ }
350
+
351
+ /**
352
+ * Generate federation report
353
+ */
354
+ function generateFederationReport(federated) {
355
+ let report = `# Multi-Repository Federation Report\n\n`;
356
+ report += `Generated: ${new Date(federated.generated).toLocaleString()}\n`;
357
+ report += `Group: ${federated.group || "All"}\n`;
358
+ report += `Repositories: ${federated.stats.totalRepos}\n\n`;
359
+
360
+ // Repositories
361
+ report += `## Repositories\n\n`;
362
+ for (const repo of federated.repositories) {
363
+ report += `- **${repo.name || repo.id}** (${repo.framework || "Unknown"})\n`;
364
+ report += ` - Path: ${repo.path}\n`;
365
+ report += ` - Dependencies: ${repo.dependencies?.length || 0}\n`;
366
+ }
367
+
368
+ // Shared artifacts
369
+ report += `\n## Shared Artifacts\n\n`;
370
+
371
+ if (federated.sharedArtifacts.components.length > 0) {
372
+ report += `### Components (${federated.stats.sharedComponents})\n\n`;
373
+ for (const comp of federated.sharedArtifacts.components.slice(0, 10)) {
374
+ report += `- **${comp.name}** - Used in ${comp.count} repos\n`;
375
+ }
376
+ }
377
+
378
+ if (federated.sharedArtifacts.hooks.length > 0) {
379
+ report += `\n### Hooks (${federated.stats.sharedHooks})\n\n`;
380
+ for (const hook of federated.sharedArtifacts.hooks.slice(0, 10)) {
381
+ report += `- **${hook.name}** - Used in ${hook.count} repos\n`;
382
+ }
383
+ }
384
+
385
+ if (federated.sharedArtifacts.patterns.length > 0) {
386
+ report += `\n### Patterns (${federated.stats.sharedPatterns})\n\n`;
387
+ for (const pattern of federated.sharedArtifacts.patterns) {
388
+ report += `- **${pattern.name}** - Used in ${pattern.count} repos\n`;
389
+ }
390
+ }
391
+
392
+ return report;
393
+ }
394
+
395
+ module.exports = {
396
+ registerRepository,
397
+ createGroup,
398
+ getSharedArtifacts,
399
+ generateFederatedContext,
400
+ findRelatedRepositories,
401
+ syncFederation,
402
+ generateFederationReport,
403
+ loadFederation,
404
+ };
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Pattern Detection Module
3
+ * Detects code patterns, hooks, state management, styling, and anti-patterns
4
+ */
5
+
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+
9
+ /**
10
+ * Find files recursively (local helper)
11
+ */
12
+ function findFiles(dir, extensions, maxDepth = 5, currentDepth = 0) {
13
+ if (currentDepth >= maxDepth || !fs.existsSync(dir)) return [];
14
+
15
+ const files = [];
16
+ try {
17
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
18
+ for (const entry of entries) {
19
+ const fullPath = path.join(dir, entry.name);
20
+ if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
21
+ files.push(...findFiles(fullPath, extensions, maxDepth, currentDepth + 1));
22
+ } else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
23
+ files.push(fullPath);
24
+ }
25
+ }
26
+ } catch {}
27
+ return files;
28
+ }
29
+
30
+ /**
31
+ * Extract a function example from code
32
+ */
33
+ function extractFunctionExample(content, functionName) {
34
+ const regex = new RegExp(`(export\\s+)?(function|const)\\s+${functionName}[^{]*\\{`, 'g');
35
+ const match = regex.exec(content);
36
+ if (!match) return null;
37
+
38
+ const startIndex = match.index;
39
+ let braceCount = 0;
40
+ let endIndex = startIndex;
41
+ let started = false;
42
+
43
+ for (let i = startIndex; i < content.length && i < startIndex + 500; i++) {
44
+ if (content[i] === '{') {
45
+ braceCount++;
46
+ started = true;
47
+ } else if (content[i] === '}') {
48
+ braceCount--;
49
+ }
50
+ if (started && braceCount === 0) {
51
+ endIndex = i + 1;
52
+ break;
53
+ }
54
+ }
55
+
56
+ const example = content.slice(startIndex, endIndex);
57
+ return example.length < 400 ? example : example.slice(0, 400) + '\n // ...';
58
+ }
59
+
60
+ /**
61
+ * Detect anti-patterns in code
62
+ */
63
+ function detectAntiPatterns(content, filePath, antiPatterns) {
64
+ // Console.log in production code
65
+ if (!filePath.includes('.test.') && !filePath.includes('.spec.') && !filePath.includes('__tests__')) {
66
+ if (/console\.(log|error|warn)\(/.test(content)) {
67
+ const existing = antiPatterns.find(a => a.type === 'console-log');
68
+ if (!existing) {
69
+ antiPatterns.push({
70
+ type: 'console-log',
71
+ severity: 'warning',
72
+ message: 'Console statements found in production code',
73
+ suggestion: 'Use a proper logger or remove before production',
74
+ });
75
+ }
76
+ }
77
+ }
78
+
79
+ // Any type usage
80
+ if (/:\s*any\b/.test(content)) {
81
+ const existing = antiPatterns.find(a => a.type === 'any-type');
82
+ if (!existing) {
83
+ antiPatterns.push({
84
+ type: 'any-type',
85
+ severity: 'warning',
86
+ message: 'Usage of `any` type detected',
87
+ suggestion: 'Use proper TypeScript types or `unknown`',
88
+ });
89
+ }
90
+ }
91
+
92
+ // Hardcoded secrets
93
+ if (/(api[_-]?key|password|secret|token)\s*[:=]\s*['"][^'"]+['"]/i.test(content)) {
94
+ const existing = antiPatterns.find(a => a.type === 'hardcoded-secret');
95
+ if (!existing) {
96
+ antiPatterns.push({
97
+ type: 'hardcoded-secret',
98
+ severity: 'error',
99
+ message: 'Potential hardcoded secrets detected',
100
+ suggestion: 'Use environment variables for sensitive data',
101
+ });
102
+ }
103
+ }
104
+
105
+ // Mock data patterns
106
+ if (/(jsonplaceholder|reqres\.in|mockapi|faker|fake[A-Z])/i.test(content)) {
107
+ const existing = antiPatterns.find(a => a.type === 'mock-data');
108
+ if (!existing) {
109
+ antiPatterns.push({
110
+ type: 'mock-data',
111
+ severity: 'warning',
112
+ message: 'Mock data or fake APIs detected',
113
+ suggestion: 'Replace with real API endpoints before production',
114
+ });
115
+ }
116
+ }
117
+
118
+ // TODO/FIXME comments
119
+ if (/\/\/\s*(TODO|FIXME|HACK|XXX):/i.test(content)) {
120
+ const existing = antiPatterns.find(a => a.type === 'todo-comments');
121
+ if (!existing) {
122
+ antiPatterns.push({
123
+ type: 'todo-comments',
124
+ severity: 'info',
125
+ message: 'TODO/FIXME comments found',
126
+ suggestion: 'Address these items before shipping',
127
+ });
128
+ }
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Deep pattern detection - analyzes actual code patterns
134
+ */
135
+ function detectPatterns(projectPath) {
136
+ const patterns = {
137
+ hooks: [],
138
+ stateManagement: null,
139
+ styling: [],
140
+ testing: [],
141
+ dataFetching: [],
142
+ validation: null,
143
+ authentication: null,
144
+ codeExamples: {},
145
+ antiPatterns: [],
146
+ };
147
+
148
+ const srcFiles = findFiles(projectPath, [".ts", ".tsx", ".js", ".jsx"], 6);
149
+
150
+ for (const file of srcFiles.slice(0, 100)) {
151
+ try {
152
+ const content = fs.readFileSync(file, "utf-8");
153
+ const relativePath = path.relative(projectPath, file);
154
+
155
+ // Detect custom hooks
156
+ const hookMatches = content.match(/export\s+(function|const)\s+(use[A-Z]\w+)/g);
157
+ if (hookMatches) {
158
+ hookMatches.forEach(match => {
159
+ const hookName = match.match(/use[A-Z]\w+/)?.[0];
160
+ if (hookName && !patterns.hooks.includes(hookName)) {
161
+ patterns.hooks.push(hookName);
162
+ if (!patterns.codeExamples.hooks) {
163
+ const hookExample = extractFunctionExample(content, hookName);
164
+ if (hookExample) {
165
+ patterns.codeExamples.hooks = { name: hookName, code: hookExample, file: relativePath };
166
+ }
167
+ }
168
+ }
169
+ });
170
+ }
171
+
172
+ // Detect state management
173
+ if (content.includes("zustand") || content.includes("create(")) {
174
+ patterns.stateManagement = "Zustand";
175
+ } else if (content.includes("@reduxjs/toolkit") || content.includes("createSlice")) {
176
+ patterns.stateManagement = "Redux Toolkit";
177
+ } else if (content.includes("recoil") || content.includes("atom(")) {
178
+ patterns.stateManagement = "Recoil";
179
+ } else if (content.includes("jotai")) {
180
+ patterns.stateManagement = "Jotai";
181
+ } else if (content.includes("createContext") && !patterns.stateManagement) {
182
+ patterns.stateManagement = "React Context";
183
+ }
184
+
185
+ // Detect styling patterns
186
+ if (content.includes("styled-components") || content.includes("styled.")) {
187
+ if (!patterns.styling.includes("Styled Components")) patterns.styling.push("Styled Components");
188
+ }
189
+ if (content.includes("@emotion") || content.includes("css``")) {
190
+ if (!patterns.styling.includes("Emotion")) patterns.styling.push("Emotion");
191
+ }
192
+ if (content.includes("className=") && content.includes("tailwind")) {
193
+ if (!patterns.styling.includes("Tailwind CSS")) patterns.styling.push("Tailwind CSS");
194
+ }
195
+ if (content.includes(".module.css") || content.includes(".module.scss")) {
196
+ if (!patterns.styling.includes("CSS Modules")) patterns.styling.push("CSS Modules");
197
+ }
198
+
199
+ // Detect data fetching patterns
200
+ if (content.includes("@tanstack/react-query") || content.includes("useQuery")) {
201
+ if (!patterns.dataFetching.includes("TanStack Query")) patterns.dataFetching.push("TanStack Query");
202
+ }
203
+ if (content.includes("useSWR") || content.includes("swr")) {
204
+ if (!patterns.dataFetching.includes("SWR")) patterns.dataFetching.push("SWR");
205
+ }
206
+ if (content.includes("trpc") || content.includes("createTRPCReact")) {
207
+ if (!patterns.dataFetching.includes("tRPC")) patterns.dataFetching.push("tRPC");
208
+ }
209
+
210
+ // Detect validation
211
+ if (content.includes("zod") || content.includes("z.object")) {
212
+ patterns.validation = "Zod";
213
+ } else if (content.includes("yup") && !patterns.validation) {
214
+ patterns.validation = "Yup";
215
+ }
216
+
217
+ // Detect authentication
218
+ if (content.includes("next-auth") || content.includes("NextAuth")) {
219
+ patterns.authentication = "NextAuth.js";
220
+ } else if (content.includes("clerk") || content.includes("@clerk")) {
221
+ patterns.authentication = "Clerk";
222
+ } else if (content.includes("supabase") && content.includes("auth")) {
223
+ patterns.authentication = "Supabase Auth";
224
+ }
225
+
226
+ // Detect testing patterns
227
+ if (content.includes("@testing-library") || content.includes("render(")) {
228
+ if (!patterns.testing.includes("React Testing Library")) patterns.testing.push("React Testing Library");
229
+ }
230
+ if (content.includes("vitest") || content.includes("vi.mock")) {
231
+ if (!patterns.testing.includes("Vitest")) patterns.testing.push("Vitest");
232
+ }
233
+ if (content.includes("jest") || content.includes("describe(")) {
234
+ if (!patterns.testing.includes("Jest")) patterns.testing.push("Jest");
235
+ }
236
+ if (content.includes("playwright") || content.includes("@playwright")) {
237
+ if (!patterns.testing.includes("Playwright")) patterns.testing.push("Playwright");
238
+ }
239
+
240
+ // Detect anti-patterns
241
+ detectAntiPatterns(content, relativePath, patterns.antiPatterns);
242
+
243
+ } catch {}
244
+ }
245
+
246
+ return patterns;
247
+ }
248
+
249
+ module.exports = {
250
+ detectPatterns,
251
+ detectAntiPatterns,
252
+ extractFunctionExample,
253
+ };