@haposoft/cafekit 0.3.2 → 0.3.5
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/bin/install.js +1 -0
- package/package.json +1 -1
- package/src/antigravity/workflows/impact-analysis-output-example.md +313 -0
- package/src/antigravity/workflows/impact-analysis.md +735 -0
- package/src/claude/migration-manifest.json +2 -1
- package/src/common/skills/impact-analysis/SKILL.md +271 -0
- package/src/common/skills/impact-analysis/references/change-detection.md +270 -0
- package/src/common/skills/impact-analysis/references/dependency-scouting.md +337 -0
- package/src/common/skills/impact-analysis/references/edge-case-identification.md +439 -0
- package/src/common/skills/impact-analysis/references/industry-techniques.md +695 -0
- package/src/common/skills/impact-analysis/references/practical-techniques-guide.md +753 -0
- package/src/common/skills/impact-analysis/references/project-detection.md +704 -0
- package/src/common/skills/impact-analysis/references/react-native-customization.md +508 -0
- package/src/common/skills/impact-analysis/references/report-template.md +604 -0
- package/src/common/skills/impact-analysis/references/test-scenario-generation.md +459 -0
- package/src/common/skills/impact-analysis/scripts/README.md +476 -0
- package/src/common/skills/impact-analysis/scripts/ast-analyze.js +403 -0
- package/src/common/skills/impact-analysis/scripts/calculate-risk.js +475 -0
- package/src/common/skills/impact-analysis/scripts/find-dependencies.sh +202 -0
- package/src/common/skills/impact-analysis/scripts/run-analysis.sh +312 -0
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Risk Calculation Script
|
|
5
|
+
* Đánh giá mức độ risk của code changes
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node calculate-risk.js
|
|
9
|
+
* node calculate-risk.js --files "file1.ts,file2.ts"
|
|
10
|
+
* node calculate-risk.js --json
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { execSync } = require('child_process');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
// Risk scoring configuration
|
|
18
|
+
const RISK_CONFIG = {
|
|
19
|
+
// File type weights
|
|
20
|
+
fileTypes: {
|
|
21
|
+
database: 5, // Schema, migrations
|
|
22
|
+
api: 3, // API endpoints
|
|
23
|
+
auth: 5, // Authentication
|
|
24
|
+
payment: 5, // Payment processing
|
|
25
|
+
security: 5, // Security-related
|
|
26
|
+
config: 2, // Configuration
|
|
27
|
+
frontend: 1, // UI components
|
|
28
|
+
test: 0 // Test files
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
// Change size weights
|
|
32
|
+
changeSize: {
|
|
33
|
+
huge: { threshold: 500, weight: 3 },
|
|
34
|
+
large: { threshold: 200, weight: 2 },
|
|
35
|
+
medium: { threshold: 50, weight: 1 },
|
|
36
|
+
small: { threshold: 0, weight: 0 }
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// Dependency weights
|
|
40
|
+
dependencies: {
|
|
41
|
+
many: { threshold: 20, weight: 3 },
|
|
42
|
+
some: { threshold: 10, weight: 2 },
|
|
43
|
+
few: { threshold: 5, weight: 1 },
|
|
44
|
+
none: { threshold: 0, weight: 0 }
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// Risk levels
|
|
48
|
+
levels: {
|
|
49
|
+
critical: { threshold: 15, label: 'CRITICAL', emoji: '⚠️⚠️⚠️' },
|
|
50
|
+
high: { threshold: 10, label: 'HIGH', emoji: '⚠️⚠️' },
|
|
51
|
+
medium: { threshold: 5, label: 'MEDIUM', emoji: '⚠️' },
|
|
52
|
+
low: { threshold: 0, label: 'LOW', emoji: '✅' }
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function getChangedFiles(fileList) {
|
|
57
|
+
if (fileList) {
|
|
58
|
+
return fileList.split(',').map(f => f.trim());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const output = execSync('git diff HEAD --name-only', { encoding: 'utf-8' });
|
|
63
|
+
return output.trim().split('\n').filter(f => f);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.error('❌ Error getting changed files:', e.message);
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getChangeStats(files) {
|
|
71
|
+
const stats = {
|
|
72
|
+
filesChanged: files.length,
|
|
73
|
+
linesAdded: 0,
|
|
74
|
+
linesRemoved: 0,
|
|
75
|
+
totalLines: 0
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const output = execSync('git diff HEAD --numstat', { encoding: 'utf-8' });
|
|
80
|
+
const lines = output.trim().split('\n');
|
|
81
|
+
|
|
82
|
+
lines.forEach(line => {
|
|
83
|
+
const [added, removed] = line.split('\t').map(n => parseInt(n) || 0);
|
|
84
|
+
stats.linesAdded += added;
|
|
85
|
+
stats.linesRemoved += removed;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
stats.totalLines = stats.linesAdded + stats.linesRemoved;
|
|
89
|
+
} catch (e) {
|
|
90
|
+
// Ignore errors
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return stats;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function classifyFiles(files) {
|
|
97
|
+
const classified = {
|
|
98
|
+
database: [],
|
|
99
|
+
api: [],
|
|
100
|
+
auth: [],
|
|
101
|
+
payment: [],
|
|
102
|
+
security: [],
|
|
103
|
+
config: [],
|
|
104
|
+
frontend: [],
|
|
105
|
+
test: [],
|
|
106
|
+
other: []
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
files.forEach(file => {
|
|
110
|
+
const lower = file.toLowerCase();
|
|
111
|
+
|
|
112
|
+
if (lower.includes('test.') || lower.includes('spec.')) {
|
|
113
|
+
classified.test.push(file);
|
|
114
|
+
} else if (lower.includes('schema') || lower.includes('migration') || lower.includes('.sql')) {
|
|
115
|
+
classified.database.push(file);
|
|
116
|
+
} else if (lower.includes('/api/') || lower.includes('route') || lower.includes('endpoint')) {
|
|
117
|
+
classified.api.push(file);
|
|
118
|
+
} else if (lower.includes('auth') || lower.includes('login') || lower.includes('session')) {
|
|
119
|
+
classified.auth.push(file);
|
|
120
|
+
} else if (lower.includes('payment') || lower.includes('checkout') || lower.includes('stripe')) {
|
|
121
|
+
classified.payment.push(file);
|
|
122
|
+
} else if (lower.includes('security') || lower.includes('crypto') || lower.includes('encrypt')) {
|
|
123
|
+
classified.security.push(file);
|
|
124
|
+
} else if (lower.includes('config') || lower.includes('.env') || lower.includes('package.json')) {
|
|
125
|
+
classified.config.push(file);
|
|
126
|
+
} else if (lower.includes('component') || lower.includes('screen') || lower.includes('page')) {
|
|
127
|
+
classified.frontend.push(file);
|
|
128
|
+
} else {
|
|
129
|
+
classified.other.push(file);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return classified;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function countDependencies(files) {
|
|
137
|
+
let totalDeps = 0;
|
|
138
|
+
|
|
139
|
+
files.forEach(file => {
|
|
140
|
+
if (!fs.existsSync(file)) return;
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const basename = path.basename(file, path.extname(file));
|
|
144
|
+
const srcDir = 'src';
|
|
145
|
+
|
|
146
|
+
if (!fs.existsSync(srcDir)) return;
|
|
147
|
+
|
|
148
|
+
// Count imports of this file
|
|
149
|
+
const grepCmd = `grep -r "from.*${basename}" ${srcDir} --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 2>/dev/null | wc -l`;
|
|
150
|
+
const count = parseInt(execSync(grepCmd, { encoding: 'utf-8' }).trim()) || 0;
|
|
151
|
+
totalDeps += count;
|
|
152
|
+
} catch (e) {
|
|
153
|
+
// Ignore errors
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
return totalDeps;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function detectBreakingChanges(files) {
|
|
161
|
+
const breaking = [];
|
|
162
|
+
|
|
163
|
+
files.forEach(file => {
|
|
164
|
+
if (!fs.existsSync(file)) return;
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
// Check git diff for breaking patterns
|
|
168
|
+
const diffCmd = `git diff HEAD -- "${file}"`;
|
|
169
|
+
const diff = execSync(diffCmd, { encoding: 'utf-8' });
|
|
170
|
+
|
|
171
|
+
// Detect function signature changes
|
|
172
|
+
if (diff.match(/^-.*function.*\(/m) && diff.match(/^\+.*function.*\(/m)) {
|
|
173
|
+
breaking.push({
|
|
174
|
+
file,
|
|
175
|
+
type: 'function_signature',
|
|
176
|
+
description: 'Function signature may have changed'
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Detect interface changes
|
|
181
|
+
if (diff.match(/^-.*interface/m) || diff.match(/^\+.*interface/m)) {
|
|
182
|
+
breaking.push({
|
|
183
|
+
file,
|
|
184
|
+
type: 'interface_change',
|
|
185
|
+
description: 'Interface definition changed'
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Detect API endpoint changes
|
|
190
|
+
if (diff.match(/^-.*\.(get|post|put|delete|patch)\(/m)) {
|
|
191
|
+
breaking.push({
|
|
192
|
+
file,
|
|
193
|
+
type: 'api_endpoint',
|
|
194
|
+
description: 'API endpoint may have changed'
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
} catch (e) {
|
|
198
|
+
// Ignore errors
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
return breaking;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function calculateRiskScore(data) {
|
|
206
|
+
let score = 0;
|
|
207
|
+
const breakdown = [];
|
|
208
|
+
|
|
209
|
+
// File type risk
|
|
210
|
+
Object.entries(data.classified).forEach(([type, files]) => {
|
|
211
|
+
if (files.length > 0 && RISK_CONFIG.fileTypes[type]) {
|
|
212
|
+
const weight = RISK_CONFIG.fileTypes[type];
|
|
213
|
+
const points = weight * Math.min(files.length, 3); // Cap at 3x
|
|
214
|
+
score += points;
|
|
215
|
+
breakdown.push({
|
|
216
|
+
factor: `${type} files`,
|
|
217
|
+
weight,
|
|
218
|
+
count: files.length,
|
|
219
|
+
points,
|
|
220
|
+
description: `${files.length} ${type} file(s) changed`
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Change size risk
|
|
226
|
+
const totalLines = data.stats.totalLines;
|
|
227
|
+
for (const [size, config] of Object.entries(RISK_CONFIG.changeSize)) {
|
|
228
|
+
if (totalLines >= config.threshold) {
|
|
229
|
+
score += config.weight;
|
|
230
|
+
breakdown.push({
|
|
231
|
+
factor: 'change size',
|
|
232
|
+
weight: config.weight,
|
|
233
|
+
count: totalLines,
|
|
234
|
+
points: config.weight,
|
|
235
|
+
description: `${totalLines} lines changed (${size})`
|
|
236
|
+
});
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Dependency risk
|
|
242
|
+
const depCount = data.dependencies;
|
|
243
|
+
for (const [level, config] of Object.entries(RISK_CONFIG.dependencies)) {
|
|
244
|
+
if (depCount >= config.threshold) {
|
|
245
|
+
score += config.weight;
|
|
246
|
+
breakdown.push({
|
|
247
|
+
factor: 'dependencies',
|
|
248
|
+
weight: config.weight,
|
|
249
|
+
count: depCount,
|
|
250
|
+
points: config.weight,
|
|
251
|
+
description: `${depCount} files depend on changes (${level})`
|
|
252
|
+
});
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Breaking changes risk
|
|
258
|
+
if (data.breakingChanges.length > 0) {
|
|
259
|
+
const points = Math.min(data.breakingChanges.length * 2, 5);
|
|
260
|
+
score += points;
|
|
261
|
+
breakdown.push({
|
|
262
|
+
factor: 'breaking changes',
|
|
263
|
+
weight: 2,
|
|
264
|
+
count: data.breakingChanges.length,
|
|
265
|
+
points,
|
|
266
|
+
description: `${data.breakingChanges.length} potential breaking change(s)`
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Determine risk level
|
|
271
|
+
let level = RISK_CONFIG.levels.low;
|
|
272
|
+
for (const [name, config] of Object.entries(RISK_CONFIG.levels)) {
|
|
273
|
+
if (score >= config.threshold) {
|
|
274
|
+
level = config;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
score,
|
|
280
|
+
maxScore: 25,
|
|
281
|
+
level: level.label,
|
|
282
|
+
emoji: level.emoji,
|
|
283
|
+
breakdown
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function generateRecommendations(data, risk) {
|
|
288
|
+
const recommendations = [];
|
|
289
|
+
|
|
290
|
+
// High risk recommendations
|
|
291
|
+
if (risk.level === 'CRITICAL' || risk.level === 'HIGH') {
|
|
292
|
+
recommendations.push({
|
|
293
|
+
priority: 'CRITICAL',
|
|
294
|
+
action: 'Extensive testing required',
|
|
295
|
+
description: 'Run full test suite including integration and E2E tests'
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
recommendations.push({
|
|
299
|
+
priority: 'CRITICAL',
|
|
300
|
+
action: 'Code review required',
|
|
301
|
+
description: 'Get at least 2 senior developers to review changes'
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Database changes
|
|
306
|
+
if (data.classified.database.length > 0) {
|
|
307
|
+
recommendations.push({
|
|
308
|
+
priority: 'HIGH',
|
|
309
|
+
action: 'Database migration plan',
|
|
310
|
+
description: 'Create rollback script and test migration on staging'
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// API changes
|
|
315
|
+
if (data.classified.api.length > 0) {
|
|
316
|
+
recommendations.push({
|
|
317
|
+
priority: 'HIGH',
|
|
318
|
+
action: 'API documentation update',
|
|
319
|
+
description: 'Update API docs and notify frontend team'
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Auth changes
|
|
324
|
+
if (data.classified.auth.length > 0) {
|
|
325
|
+
recommendations.push({
|
|
326
|
+
priority: 'CRITICAL',
|
|
327
|
+
action: 'Security review',
|
|
328
|
+
description: 'Review authentication flow and test all auth scenarios'
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Payment changes
|
|
333
|
+
if (data.classified.payment.length > 0) {
|
|
334
|
+
recommendations.push({
|
|
335
|
+
priority: 'CRITICAL',
|
|
336
|
+
action: 'Payment testing',
|
|
337
|
+
description: 'Test payment flow in sandbox environment'
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Breaking changes
|
|
342
|
+
if (data.breakingChanges.length > 0) {
|
|
343
|
+
recommendations.push({
|
|
344
|
+
priority: 'HIGH',
|
|
345
|
+
action: 'Update all consumers',
|
|
346
|
+
description: `Update ${data.dependencies} files that depend on changed code`
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Many dependencies
|
|
351
|
+
if (data.dependencies > 10) {
|
|
352
|
+
recommendations.push({
|
|
353
|
+
priority: 'MEDIUM',
|
|
354
|
+
action: 'Regression testing',
|
|
355
|
+
description: 'Test all affected features to prevent regression'
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return recommendations;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function printReport(data, risk, recommendations) {
|
|
363
|
+
console.log('\n🎯 Risk Assessment Report\n');
|
|
364
|
+
console.log('═'.repeat(60));
|
|
365
|
+
|
|
366
|
+
// Risk score
|
|
367
|
+
console.log(`\n📊 Risk Score: ${risk.score}/${risk.maxScore}`);
|
|
368
|
+
console.log(`🎚️ Risk Level: ${risk.emoji} ${risk.level}`);
|
|
369
|
+
|
|
370
|
+
// Summary
|
|
371
|
+
console.log(`\n📋 Summary:`);
|
|
372
|
+
console.log(` Files changed: ${data.stats.filesChanged}`);
|
|
373
|
+
console.log(` Lines changed: ${data.stats.totalLines} (+${data.stats.linesAdded}, -${data.stats.linesRemoved})`);
|
|
374
|
+
console.log(` Dependencies: ${data.dependencies} files affected`);
|
|
375
|
+
console.log(` Breaking changes: ${data.breakingChanges.length}`);
|
|
376
|
+
|
|
377
|
+
// File classification
|
|
378
|
+
console.log(`\n📁 File Classification:`);
|
|
379
|
+
Object.entries(data.classified).forEach(([type, files]) => {
|
|
380
|
+
if (files.length > 0) {
|
|
381
|
+
const weight = RISK_CONFIG.fileTypes[type] || 0;
|
|
382
|
+
const emoji = weight >= 5 ? '🔴' : weight >= 3 ? '🟡' : '🟢';
|
|
383
|
+
console.log(` ${emoji} ${type}: ${files.length} file(s)`);
|
|
384
|
+
files.slice(0, 3).forEach(file => {
|
|
385
|
+
console.log(` - ${file}`);
|
|
386
|
+
});
|
|
387
|
+
if (files.length > 3) {
|
|
388
|
+
console.log(` ... and ${files.length - 3} more`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// Risk breakdown
|
|
394
|
+
console.log(`\n⚖️ Risk Breakdown:`);
|
|
395
|
+
risk.breakdown.forEach(item => {
|
|
396
|
+
console.log(` • ${item.description}`);
|
|
397
|
+
console.log(` Points: ${item.points} (weight: ${item.weight})`);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// Breaking changes
|
|
401
|
+
if (data.breakingChanges.length > 0) {
|
|
402
|
+
console.log(`\n⚠️ Potential Breaking Changes:`);
|
|
403
|
+
data.breakingChanges.forEach((change, i) => {
|
|
404
|
+
console.log(` ${i + 1}. ${change.file}`);
|
|
405
|
+
console.log(` Type: ${change.type}`);
|
|
406
|
+
console.log(` ${change.description}`);
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Recommendations
|
|
411
|
+
console.log(`\n💡 Recommendations:`);
|
|
412
|
+
recommendations.forEach((rec, i) => {
|
|
413
|
+
const emoji = rec.priority === 'CRITICAL' ? '🔴' : rec.priority === 'HIGH' ? '🟡' : '🟢';
|
|
414
|
+
console.log(` ${emoji} [${rec.priority}] ${rec.action}`);
|
|
415
|
+
console.log(` ${rec.description}`);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
console.log('\n' + '═'.repeat(60));
|
|
419
|
+
console.log('');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function printJSON(data, risk, recommendations) {
|
|
423
|
+
const output = {
|
|
424
|
+
risk,
|
|
425
|
+
data,
|
|
426
|
+
recommendations,
|
|
427
|
+
timestamp: new Date().toISOString()
|
|
428
|
+
};
|
|
429
|
+
console.log(JSON.stringify(output, null, 2));
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Main
|
|
433
|
+
const args = process.argv.slice(2);
|
|
434
|
+
const fileList = args.includes('--files') ? args[args.indexOf('--files') + 1] : null;
|
|
435
|
+
const jsonOutput = args.includes('--json');
|
|
436
|
+
|
|
437
|
+
const files = getChangedFiles(fileList);
|
|
438
|
+
|
|
439
|
+
if (files.length === 0) {
|
|
440
|
+
console.error('❌ No changed files found');
|
|
441
|
+
console.error('Usage: node calculate-risk.js [--files "file1.ts,file2.ts"] [--json]');
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const stats = getChangeStats(files);
|
|
446
|
+
const classified = classifyFiles(files);
|
|
447
|
+
const dependencies = countDependencies(files);
|
|
448
|
+
const breakingChanges = detectBreakingChanges(files);
|
|
449
|
+
|
|
450
|
+
const data = {
|
|
451
|
+
files,
|
|
452
|
+
stats,
|
|
453
|
+
classified,
|
|
454
|
+
dependencies,
|
|
455
|
+
breakingChanges
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
const risk = calculateRiskScore(data);
|
|
459
|
+
const recommendations = generateRecommendations(data, risk);
|
|
460
|
+
|
|
461
|
+
if (jsonOutput) {
|
|
462
|
+
printJSON(data, risk, recommendations);
|
|
463
|
+
} else {
|
|
464
|
+
printReport(data, risk, recommendations);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Export for programmatic use
|
|
468
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
469
|
+
module.exports = {
|
|
470
|
+
calculateRiskScore,
|
|
471
|
+
generateRecommendations,
|
|
472
|
+
classifyFiles,
|
|
473
|
+
getChangeStats
|
|
474
|
+
};
|
|
475
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Find Dependencies Script
|
|
4
|
+
# Tìm tất cả files bị ảnh hưởng bởi code changes
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# ./find-dependencies.sh <file-path>
|
|
8
|
+
# ./find-dependencies.sh src/services/authService.ts
|
|
9
|
+
|
|
10
|
+
set -e
|
|
11
|
+
|
|
12
|
+
if [ $# -eq 0 ]; then
|
|
13
|
+
echo "Usage: $0 <file-path>"
|
|
14
|
+
echo "Example: $0 src/services/authService.ts"
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
FILE_PATH="$1"
|
|
19
|
+
FILE_NAME=$(basename "$FILE_PATH")
|
|
20
|
+
FILE_BASE="${FILE_NAME%.*}"
|
|
21
|
+
SRC_DIR="${SRC_DIR:-src}"
|
|
22
|
+
|
|
23
|
+
echo "🔍 Finding dependencies for: $FILE_PATH"
|
|
24
|
+
echo ""
|
|
25
|
+
|
|
26
|
+
# 1. Find direct imports
|
|
27
|
+
echo "📦 Direct Imports:"
|
|
28
|
+
echo "Files that import this module:"
|
|
29
|
+
echo ""
|
|
30
|
+
|
|
31
|
+
# TypeScript/JavaScript imports
|
|
32
|
+
grep -r "from ['\"].*$FILE_BASE['\"]" "$SRC_DIR" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 2>/dev/null | \
|
|
33
|
+
grep -v "node_modules" | \
|
|
34
|
+
grep -v ".test." | \
|
|
35
|
+
grep -v ".spec." | \
|
|
36
|
+
cut -d: -f1 | \
|
|
37
|
+
sort -u | \
|
|
38
|
+
while read -r file; do
|
|
39
|
+
echo " - $file"
|
|
40
|
+
done
|
|
41
|
+
|
|
42
|
+
# Alternative import syntax
|
|
43
|
+
grep -r "import.*from ['\"].*$FILE_BASE['\"]" "$SRC_DIR" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 2>/dev/null | \
|
|
44
|
+
grep -v "node_modules" | \
|
|
45
|
+
grep -v ".test." | \
|
|
46
|
+
grep -v ".spec." | \
|
|
47
|
+
cut -d: -f1 | \
|
|
48
|
+
sort -u | \
|
|
49
|
+
while read -r file; do
|
|
50
|
+
echo " - $file"
|
|
51
|
+
done
|
|
52
|
+
|
|
53
|
+
echo ""
|
|
54
|
+
|
|
55
|
+
# 2. Find function/class usage
|
|
56
|
+
echo "🔧 Function/Class Usage:"
|
|
57
|
+
echo "Searching for common exports..."
|
|
58
|
+
echo ""
|
|
59
|
+
|
|
60
|
+
# Extract exported names from the file
|
|
61
|
+
if [ -f "$FILE_PATH" ]; then
|
|
62
|
+
# Find exported functions
|
|
63
|
+
EXPORTS=$(grep -E "export (function|const|class|interface|type)" "$FILE_PATH" 2>/dev/null | \
|
|
64
|
+
sed -E 's/export (function|const|class|interface|type) ([a-zA-Z0-9_]+).*/\2/' | \
|
|
65
|
+
head -10)
|
|
66
|
+
|
|
67
|
+
if [ -n "$EXPORTS" ]; then
|
|
68
|
+
for export_name in $EXPORTS; do
|
|
69
|
+
echo " Searching for: $export_name"
|
|
70
|
+
|
|
71
|
+
# Find usage (excluding the source file itself)
|
|
72
|
+
grep -r "\b$export_name\b" "$SRC_DIR" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 2>/dev/null | \
|
|
73
|
+
grep -v "node_modules" | \
|
|
74
|
+
grep -v "$FILE_PATH" | \
|
|
75
|
+
grep -v ".test." | \
|
|
76
|
+
grep -v ".spec." | \
|
|
77
|
+
cut -d: -f1 | \
|
|
78
|
+
sort -u | \
|
|
79
|
+
head -5 | \
|
|
80
|
+
while read -r file; do
|
|
81
|
+
echo " - $file"
|
|
82
|
+
done
|
|
83
|
+
done
|
|
84
|
+
fi
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
echo ""
|
|
88
|
+
|
|
89
|
+
# 3. Find API endpoint usage (if this is an API file)
|
|
90
|
+
if [[ "$FILE_PATH" == *"/api/"* ]] || [[ "$FILE_PATH" == *"routes"* ]]; then
|
|
91
|
+
echo "🌐 API Endpoint Usage:"
|
|
92
|
+
echo "Searching for API calls..."
|
|
93
|
+
echo ""
|
|
94
|
+
|
|
95
|
+
# Extract API endpoints from the file
|
|
96
|
+
ENDPOINTS=$(grep -E "(get|post|put|delete|patch)\(['\"]" "$FILE_PATH" 2>/dev/null | \
|
|
97
|
+
sed -E "s/.*['\"]([^'\"]+)['\"].*/\1/" | \
|
|
98
|
+
head -5)
|
|
99
|
+
|
|
100
|
+
if [ -n "$ENDPOINTS" ]; then
|
|
101
|
+
for endpoint in $ENDPOINTS; do
|
|
102
|
+
echo " Endpoint: $endpoint"
|
|
103
|
+
|
|
104
|
+
# Find frontend calls to this endpoint
|
|
105
|
+
grep -r "$endpoint" "$SRC_DIR" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 2>/dev/null | \
|
|
106
|
+
grep -v "node_modules" | \
|
|
107
|
+
grep -v "$FILE_PATH" | \
|
|
108
|
+
cut -d: -f1 | \
|
|
109
|
+
sort -u | \
|
|
110
|
+
head -5 | \
|
|
111
|
+
while read -r file; do
|
|
112
|
+
echo " - $file"
|
|
113
|
+
done
|
|
114
|
+
done
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
echo ""
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# 4. Find component usage (if this is a React component)
|
|
121
|
+
if [[ "$FILE_PATH" == *"components"* ]] || [[ "$FILE_PATH" == *".tsx" ]]; then
|
|
122
|
+
echo "⚛️ Component Usage:"
|
|
123
|
+
echo "Searching for component references..."
|
|
124
|
+
echo ""
|
|
125
|
+
|
|
126
|
+
COMPONENT_NAME=$(basename "$FILE_PATH" .tsx)
|
|
127
|
+
COMPONENT_NAME=$(basename "$COMPONENT_NAME" .jsx)
|
|
128
|
+
|
|
129
|
+
# Find JSX usage
|
|
130
|
+
grep -r "<$COMPONENT_NAME" "$SRC_DIR" --include="*.tsx" --include="*.jsx" 2>/dev/null | \
|
|
131
|
+
grep -v "node_modules" | \
|
|
132
|
+
grep -v "$FILE_PATH" | \
|
|
133
|
+
cut -d: -f1 | \
|
|
134
|
+
sort -u | \
|
|
135
|
+
while read -r file; do
|
|
136
|
+
echo " - $file"
|
|
137
|
+
done
|
|
138
|
+
|
|
139
|
+
echo ""
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# 5. Find test files
|
|
143
|
+
echo "🧪 Related Tests:"
|
|
144
|
+
echo "Test files for this module:"
|
|
145
|
+
echo ""
|
|
146
|
+
|
|
147
|
+
# Find test files with similar names
|
|
148
|
+
find "$SRC_DIR" -type f \( -name "*$FILE_BASE*.test.*" -o -name "*$FILE_BASE*.spec.*" \) 2>/dev/null | \
|
|
149
|
+
while read -r file; do
|
|
150
|
+
echo " - $file"
|
|
151
|
+
done
|
|
152
|
+
|
|
153
|
+
# Find test files that import this module
|
|
154
|
+
grep -r "from ['\"].*$FILE_BASE['\"]" "$SRC_DIR" --include="*.test.*" --include="*.spec.*" 2>/dev/null | \
|
|
155
|
+
cut -d: -f1 | \
|
|
156
|
+
sort -u | \
|
|
157
|
+
while read -r file; do
|
|
158
|
+
echo " - $file"
|
|
159
|
+
done
|
|
160
|
+
|
|
161
|
+
echo ""
|
|
162
|
+
|
|
163
|
+
# 6. Summary
|
|
164
|
+
echo "📊 Summary:"
|
|
165
|
+
echo ""
|
|
166
|
+
|
|
167
|
+
IMPORT_COUNT=$(grep -r "from ['\"].*$FILE_BASE['\"]" "$SRC_DIR" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 2>/dev/null | \
|
|
168
|
+
grep -v "node_modules" | \
|
|
169
|
+
grep -v ".test." | \
|
|
170
|
+
grep -v ".spec." | \
|
|
171
|
+
wc -l | tr -d ' ')
|
|
172
|
+
|
|
173
|
+
TEST_COUNT=$(find "$SRC_DIR" -type f \( -name "*$FILE_BASE*.test.*" -o -name "*$FILE_BASE*.spec.*" \) 2>/dev/null | wc -l | tr -d ' ')
|
|
174
|
+
|
|
175
|
+
echo " Direct imports: $IMPORT_COUNT"
|
|
176
|
+
echo " Related tests: $TEST_COUNT"
|
|
177
|
+
echo ""
|
|
178
|
+
|
|
179
|
+
# 7. Recommendations
|
|
180
|
+
echo "💡 Recommendations:"
|
|
181
|
+
echo ""
|
|
182
|
+
|
|
183
|
+
if [ "$IMPORT_COUNT" -gt 10 ]; then
|
|
184
|
+
echo " ⚠️ HIGH IMPACT: This file is imported by many files ($IMPORT_COUNT)"
|
|
185
|
+
echo " → Test thoroughly before deploying"
|
|
186
|
+
echo " → Consider backward compatibility"
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
if [ "$TEST_COUNT" -eq 0 ]; then
|
|
190
|
+
echo " ⚠️ NO TESTS: No test files found for this module"
|
|
191
|
+
echo " → Add unit tests before deploying"
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
if [[ "$FILE_PATH" == *"/api/"* ]]; then
|
|
195
|
+
echo " ⚠️ API CHANGE: This is an API file"
|
|
196
|
+
echo " → Check for breaking changes"
|
|
197
|
+
echo " → Update API documentation"
|
|
198
|
+
echo " → Test all API consumers"
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
echo ""
|
|
202
|
+
echo "✅ Dependency analysis complete"
|