@vibecheckai/cli 3.0.2 → 3.0.3
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/package.json +9 -1
- package/bin/cli-hygiene.js +0 -241
- package/bin/guardrail.js +0 -834
- package/bin/runners/cli-utils.js +0 -1070
- package/bin/runners/context/ai-task-decomposer.js +0 -337
- package/bin/runners/context/analyzer.js +0 -462
- package/bin/runners/context/api-contracts.js +0 -427
- package/bin/runners/context/context-diff.js +0 -342
- package/bin/runners/context/context-pruner.js +0 -291
- package/bin/runners/context/dependency-graph.js +0 -414
- package/bin/runners/context/generators/claude.js +0 -107
- package/bin/runners/context/generators/codex.js +0 -108
- package/bin/runners/context/generators/copilot.js +0 -119
- package/bin/runners/context/generators/cursor.js +0 -514
- package/bin/runners/context/generators/mcp.js +0 -151
- package/bin/runners/context/generators/windsurf.js +0 -180
- package/bin/runners/context/git-context.js +0 -302
- package/bin/runners/context/index.js +0 -1042
- package/bin/runners/context/insights.js +0 -173
- package/bin/runners/context/mcp-server/generate-rules.js +0 -337
- package/bin/runners/context/mcp-server/index.js +0 -1176
- package/bin/runners/context/mcp-server/package.json +0 -24
- package/bin/runners/context/memory.js +0 -200
- package/bin/runners/context/monorepo.js +0 -215
- package/bin/runners/context/multi-repo-federation.js +0 -404
- package/bin/runners/context/patterns.js +0 -253
- package/bin/runners/context/proof-context.js +0 -972
- package/bin/runners/context/security-scanner.js +0 -303
- package/bin/runners/context/semantic-search.js +0 -350
- package/bin/runners/context/shared.js +0 -264
- package/bin/runners/context/team-conventions.js +0 -310
- package/bin/runners/lib/ai-bridge.js +0 -416
- package/bin/runners/lib/analysis-core.js +0 -271
- package/bin/runners/lib/analyzers.js +0 -541
- package/bin/runners/lib/audit-bridge.js +0 -391
- package/bin/runners/lib/auth-truth.js +0 -193
- package/bin/runners/lib/auth.js +0 -215
- package/bin/runners/lib/backup.js +0 -62
- package/bin/runners/lib/billing.js +0 -107
- package/bin/runners/lib/claims.js +0 -118
- package/bin/runners/lib/cli-ui.js +0 -540
- package/bin/runners/lib/compliance-bridge-new.js +0 -0
- package/bin/runners/lib/compliance-bridge.js +0 -165
- package/bin/runners/lib/contracts/auth-contract.js +0 -194
- package/bin/runners/lib/contracts/env-contract.js +0 -178
- package/bin/runners/lib/contracts/external-contract.js +0 -198
- package/bin/runners/lib/contracts/guard.js +0 -168
- package/bin/runners/lib/contracts/index.js +0 -89
- package/bin/runners/lib/contracts/plan-validator.js +0 -311
- package/bin/runners/lib/contracts/route-contract.js +0 -192
- package/bin/runners/lib/detect.js +0 -89
- package/bin/runners/lib/doctor/autofix.js +0 -254
- package/bin/runners/lib/doctor/index.js +0 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +0 -325
- package/bin/runners/lib/doctor/modules/index.js +0 -46
- package/bin/runners/lib/doctor/modules/network.js +0 -250
- package/bin/runners/lib/doctor/modules/project.js +0 -312
- package/bin/runners/lib/doctor/modules/runtime.js +0 -224
- package/bin/runners/lib/doctor/modules/security.js +0 -348
- package/bin/runners/lib/doctor/modules/system.js +0 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +0 -394
- package/bin/runners/lib/doctor/reporter.js +0 -262
- package/bin/runners/lib/doctor/service.js +0 -262
- package/bin/runners/lib/doctor/types.js +0 -113
- package/bin/runners/lib/doctor/ui.js +0 -263
- package/bin/runners/lib/doctor-enhanced.js +0 -233
- package/bin/runners/lib/doctor-v2.js +0 -608
- package/bin/runners/lib/enforcement.js +0 -72
|
@@ -1,404 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,253 +0,0 @@
|
|
|
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
|
-
};
|