@haposoft/cafekit 0.3.1 → 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/README.md +3 -3
- 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,403 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AST-Based Analysis Script
|
|
5
|
+
* Phân tích function signatures, type changes, và breaking changes
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node ast-analyze.js <file-path>
|
|
9
|
+
* node ast-analyze.js src/services/authService.ts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
// Try to use @babel/parser if available, otherwise provide instructions
|
|
16
|
+
let parser, traverse;
|
|
17
|
+
try {
|
|
18
|
+
parser = require('@babel/parser');
|
|
19
|
+
traverse = require('@babel/traverse').default;
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.error('❌ Dependencies not installed');
|
|
22
|
+
console.error('Run: npm install --save-dev @babel/parser @babel/traverse');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function analyzeFunctionSignatures(filePath) {
|
|
27
|
+
if (!fs.existsSync(filePath)) {
|
|
28
|
+
console.error(`❌ File not found: ${filePath}`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const code = fs.readFileSync(filePath, 'utf-8');
|
|
33
|
+
|
|
34
|
+
let ast;
|
|
35
|
+
try {
|
|
36
|
+
ast = parser.parse(code, {
|
|
37
|
+
sourceType: 'module',
|
|
38
|
+
plugins: ['typescript', 'jsx', 'decorators-legacy']
|
|
39
|
+
});
|
|
40
|
+
} catch (e) {
|
|
41
|
+
console.error(`❌ Parse error: ${e.message}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const functions = [];
|
|
46
|
+
const classes = [];
|
|
47
|
+
const interfaces = [];
|
|
48
|
+
const types = [];
|
|
49
|
+
|
|
50
|
+
traverse(ast, {
|
|
51
|
+
// Function declarations
|
|
52
|
+
FunctionDeclaration(path) {
|
|
53
|
+
functions.push({
|
|
54
|
+
type: 'function',
|
|
55
|
+
name: path.node.id?.name || 'anonymous',
|
|
56
|
+
params: path.node.params.map(p => getParamName(p)),
|
|
57
|
+
paramCount: path.node.params.length,
|
|
58
|
+
async: path.node.async,
|
|
59
|
+
generator: path.node.generator,
|
|
60
|
+
line: path.node.loc.start.line,
|
|
61
|
+
exported: isExported(path)
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// Arrow functions
|
|
66
|
+
ArrowFunctionExpression(path) {
|
|
67
|
+
if (path.parent.type === 'VariableDeclarator') {
|
|
68
|
+
functions.push({
|
|
69
|
+
type: 'arrow',
|
|
70
|
+
name: path.parent.id.name,
|
|
71
|
+
params: path.node.params.map(p => getParamName(p)),
|
|
72
|
+
paramCount: path.node.params.length,
|
|
73
|
+
async: path.node.async,
|
|
74
|
+
line: path.node.loc.start.line,
|
|
75
|
+
exported: isExported(path.parentPath.parentPath)
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// Class declarations
|
|
81
|
+
ClassDeclaration(path) {
|
|
82
|
+
const methods = [];
|
|
83
|
+
path.node.body.body.forEach(member => {
|
|
84
|
+
if (member.type === 'ClassMethod') {
|
|
85
|
+
methods.push({
|
|
86
|
+
name: member.key.name,
|
|
87
|
+
kind: member.kind, // constructor, method, get, set
|
|
88
|
+
params: member.params.map(p => getParamName(p)),
|
|
89
|
+
paramCount: member.params.length,
|
|
90
|
+
async: member.async,
|
|
91
|
+
static: member.static,
|
|
92
|
+
line: member.loc.start.line
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
classes.push({
|
|
98
|
+
name: path.node.id.name,
|
|
99
|
+
methods,
|
|
100
|
+
line: path.node.loc.start.line,
|
|
101
|
+
exported: isExported(path)
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// TypeScript interfaces
|
|
106
|
+
TSInterfaceDeclaration(path) {
|
|
107
|
+
const properties = path.node.body.body.map(prop => ({
|
|
108
|
+
name: prop.key?.name || 'unknown',
|
|
109
|
+
optional: prop.optional,
|
|
110
|
+
type: getTypeAnnotation(prop.typeAnnotation)
|
|
111
|
+
}));
|
|
112
|
+
|
|
113
|
+
interfaces.push({
|
|
114
|
+
name: path.node.id.name,
|
|
115
|
+
properties,
|
|
116
|
+
line: path.node.loc.start.line,
|
|
117
|
+
exported: isExported(path)
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
// TypeScript type aliases
|
|
122
|
+
TSTypeAliasDeclaration(path) {
|
|
123
|
+
types.push({
|
|
124
|
+
name: path.node.id.name,
|
|
125
|
+
line: path.node.loc.start.line,
|
|
126
|
+
exported: isExported(path)
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
file: filePath,
|
|
133
|
+
functions,
|
|
134
|
+
classes,
|
|
135
|
+
interfaces,
|
|
136
|
+
types,
|
|
137
|
+
summary: {
|
|
138
|
+
totalFunctions: functions.length,
|
|
139
|
+
totalClasses: classes.length,
|
|
140
|
+
totalInterfaces: interfaces.length,
|
|
141
|
+
totalTypes: types.length,
|
|
142
|
+
exportedFunctions: functions.filter(f => f.exported).length,
|
|
143
|
+
asyncFunctions: functions.filter(f => f.async).length
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function getParamName(param) {
|
|
149
|
+
if (param.type === 'Identifier') {
|
|
150
|
+
return param.name;
|
|
151
|
+
} else if (param.type === 'AssignmentPattern') {
|
|
152
|
+
return `${getParamName(param.left)} = ${param.right.value || '...'}`;
|
|
153
|
+
} else if (param.type === 'RestElement') {
|
|
154
|
+
return `...${param.argument.name}`;
|
|
155
|
+
} else if (param.type === 'ObjectPattern') {
|
|
156
|
+
return `{ ${param.properties.map(p => p.key?.name || '?').join(', ')} }`;
|
|
157
|
+
}
|
|
158
|
+
return 'unknown';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function getTypeAnnotation(typeAnnotation) {
|
|
162
|
+
if (!typeAnnotation) return 'any';
|
|
163
|
+
const type = typeAnnotation.typeAnnotation;
|
|
164
|
+
if (!type) return 'any';
|
|
165
|
+
|
|
166
|
+
if (type.type === 'TSStringKeyword') return 'string';
|
|
167
|
+
if (type.type === 'TSNumberKeyword') return 'number';
|
|
168
|
+
if (type.type === 'TSBooleanKeyword') return 'boolean';
|
|
169
|
+
if (type.type === 'TSAnyKeyword') return 'any';
|
|
170
|
+
if (type.type === 'TSTypeReference') return type.typeName.name;
|
|
171
|
+
|
|
172
|
+
return 'unknown';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function isExported(path) {
|
|
176
|
+
let current = path;
|
|
177
|
+
while (current) {
|
|
178
|
+
if (current.node.type === 'ExportNamedDeclaration' ||
|
|
179
|
+
current.node.type === 'ExportDefaultDeclaration') {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
current = current.parentPath;
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function compareAnalysis(before, after) {
|
|
188
|
+
const changes = {
|
|
189
|
+
functionsAdded: [],
|
|
190
|
+
functionsRemoved: [],
|
|
191
|
+
functionsModified: [],
|
|
192
|
+
interfacesAdded: [],
|
|
193
|
+
interfacesRemoved: [],
|
|
194
|
+
interfacesModified: []
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Compare functions
|
|
198
|
+
const beforeFuncs = new Map(before.functions.map(f => [f.name, f]));
|
|
199
|
+
const afterFuncs = new Map(after.functions.map(f => [f.name, f]));
|
|
200
|
+
|
|
201
|
+
// Find added functions
|
|
202
|
+
afterFuncs.forEach((func, name) => {
|
|
203
|
+
if (!beforeFuncs.has(name)) {
|
|
204
|
+
changes.functionsAdded.push(func);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Find removed functions
|
|
209
|
+
beforeFuncs.forEach((func, name) => {
|
|
210
|
+
if (!afterFuncs.has(name)) {
|
|
211
|
+
changes.functionsRemoved.push(func);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Find modified functions
|
|
216
|
+
beforeFuncs.forEach((beforeFunc, name) => {
|
|
217
|
+
const afterFunc = afterFuncs.get(name);
|
|
218
|
+
if (afterFunc && beforeFunc.paramCount !== afterFunc.paramCount) {
|
|
219
|
+
changes.functionsModified.push({
|
|
220
|
+
name,
|
|
221
|
+
before: beforeFunc,
|
|
222
|
+
after: afterFunc,
|
|
223
|
+
breaking: true,
|
|
224
|
+
reason: `Parameter count changed: ${beforeFunc.paramCount} → ${afterFunc.paramCount}`
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Compare interfaces
|
|
230
|
+
const beforeInterfaces = new Map(before.interfaces.map(i => [i.name, i]));
|
|
231
|
+
const afterInterfaces = new Map(after.interfaces.map(i => [i.name, i]));
|
|
232
|
+
|
|
233
|
+
afterInterfaces.forEach((iface, name) => {
|
|
234
|
+
if (!beforeInterfaces.has(name)) {
|
|
235
|
+
changes.interfacesAdded.push(iface);
|
|
236
|
+
} else {
|
|
237
|
+
const beforeIface = beforeInterfaces.get(name);
|
|
238
|
+
const beforeProps = new Set(beforeIface.properties.map(p => p.name));
|
|
239
|
+
const afterProps = new Set(iface.properties.map(p => p.name));
|
|
240
|
+
|
|
241
|
+
const added = [...afterProps].filter(p => !beforeProps.has(p));
|
|
242
|
+
const removed = [...beforeProps].filter(p => !afterProps.has(p));
|
|
243
|
+
|
|
244
|
+
if (added.length > 0 || removed.length > 0) {
|
|
245
|
+
changes.interfacesModified.push({
|
|
246
|
+
name,
|
|
247
|
+
propertiesAdded: added,
|
|
248
|
+
propertiesRemoved: removed,
|
|
249
|
+
breaking: removed.length > 0
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
beforeInterfaces.forEach((iface, name) => {
|
|
256
|
+
if (!afterInterfaces.has(name)) {
|
|
257
|
+
changes.interfacesRemoved.push(iface);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
return changes;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function printAnalysis(analysis) {
|
|
265
|
+
console.log('\n📊 AST Analysis Results\n');
|
|
266
|
+
console.log(`File: ${analysis.file}`);
|
|
267
|
+
console.log(`\n📈 Summary:`);
|
|
268
|
+
console.log(` Functions: ${analysis.summary.totalFunctions} (${analysis.summary.exportedFunctions} exported, ${analysis.summary.asyncFunctions} async)`);
|
|
269
|
+
console.log(` Classes: ${analysis.summary.totalClasses}`);
|
|
270
|
+
console.log(` Interfaces: ${analysis.summary.totalInterfaces}`);
|
|
271
|
+
console.log(` Types: ${analysis.summary.totalTypes}`);
|
|
272
|
+
|
|
273
|
+
if (analysis.functions.length > 0) {
|
|
274
|
+
console.log(`\n🔧 Functions:`);
|
|
275
|
+
analysis.functions.forEach(func => {
|
|
276
|
+
const badge = func.exported ? '📤' : ' ';
|
|
277
|
+
const asyncBadge = func.async ? 'async ' : '';
|
|
278
|
+
console.log(` ${badge} ${asyncBadge}${func.name}(${func.params.join(', ')}) - line ${func.line}`);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (analysis.classes.length > 0) {
|
|
283
|
+
console.log(`\n🏗️ Classes:`);
|
|
284
|
+
analysis.classes.forEach(cls => {
|
|
285
|
+
console.log(` ${cls.exported ? '📤' : ' '} ${cls.name} - line ${cls.line}`);
|
|
286
|
+
cls.methods.forEach(method => {
|
|
287
|
+
const badge = method.static ? 'static ' : '';
|
|
288
|
+
const asyncBadge = method.async ? 'async ' : '';
|
|
289
|
+
console.log(` ${badge}${asyncBadge}${method.name}(${method.params.join(', ')})`);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (analysis.interfaces.length > 0) {
|
|
295
|
+
console.log(`\n📋 Interfaces:`);
|
|
296
|
+
analysis.interfaces.forEach(iface => {
|
|
297
|
+
console.log(` ${iface.exported ? '📤' : ' '} ${iface.name} - line ${iface.line}`);
|
|
298
|
+
iface.properties.forEach(prop => {
|
|
299
|
+
const optional = prop.optional ? '?' : '';
|
|
300
|
+
console.log(` ${prop.name}${optional}: ${prop.type}`);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function printComparison(changes) {
|
|
307
|
+
console.log('\n🔍 Changes Detected\n');
|
|
308
|
+
|
|
309
|
+
let hasChanges = false;
|
|
310
|
+
|
|
311
|
+
if (changes.functionsAdded.length > 0) {
|
|
312
|
+
hasChanges = true;
|
|
313
|
+
console.log('✅ Functions Added:');
|
|
314
|
+
changes.functionsAdded.forEach(func => {
|
|
315
|
+
console.log(` + ${func.name}(${func.params.join(', ')})`);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (changes.functionsRemoved.length > 0) {
|
|
320
|
+
hasChanges = true;
|
|
321
|
+
console.log('\n❌ Functions Removed:');
|
|
322
|
+
changes.functionsRemoved.forEach(func => {
|
|
323
|
+
console.log(` - ${func.name}(${func.params.join(', ')})`);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (changes.functionsModified.length > 0) {
|
|
328
|
+
hasChanges = true;
|
|
329
|
+
console.log('\n⚠️ Functions Modified (BREAKING):');
|
|
330
|
+
changes.functionsModified.forEach(change => {
|
|
331
|
+
console.log(` ~ ${change.name}`);
|
|
332
|
+
console.log(` Before: ${change.before.params.join(', ')}`);
|
|
333
|
+
console.log(` After: ${change.after.params.join(', ')}`);
|
|
334
|
+
console.log(` Reason: ${change.reason}`);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (changes.interfacesAdded.length > 0) {
|
|
339
|
+
hasChanges = true;
|
|
340
|
+
console.log('\n✅ Interfaces Added:');
|
|
341
|
+
changes.interfacesAdded.forEach(iface => {
|
|
342
|
+
console.log(` + ${iface.name}`);
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (changes.interfacesRemoved.length > 0) {
|
|
347
|
+
hasChanges = true;
|
|
348
|
+
console.log('\n❌ Interfaces Removed:');
|
|
349
|
+
changes.interfacesRemoved.forEach(iface => {
|
|
350
|
+
console.log(` - ${iface.name}`);
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (changes.interfacesModified.length > 0) {
|
|
355
|
+
hasChanges = true;
|
|
356
|
+
console.log('\n⚠️ Interfaces Modified:');
|
|
357
|
+
changes.interfacesModified.forEach(change => {
|
|
358
|
+
console.log(` ~ ${change.name} ${change.breaking ? '(BREAKING)' : ''}`);
|
|
359
|
+
if (change.propertiesAdded.length > 0) {
|
|
360
|
+
console.log(` Added: ${change.propertiesAdded.join(', ')}`);
|
|
361
|
+
}
|
|
362
|
+
if (change.propertiesRemoved.length > 0) {
|
|
363
|
+
console.log(` Removed: ${change.propertiesRemoved.join(', ')}`);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (!hasChanges) {
|
|
369
|
+
console.log('✅ No semantic changes detected');
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Main
|
|
374
|
+
const args = process.argv.slice(2);
|
|
375
|
+
|
|
376
|
+
if (args.length === 0) {
|
|
377
|
+
console.error('Usage: node ast-analyze.js <file-path> [--compare <before-file>]');
|
|
378
|
+
console.error('Example: node ast-analyze.js src/services/authService.ts');
|
|
379
|
+
console.error('Example: node ast-analyze.js src/services/authService.ts --compare /tmp/before.ts');
|
|
380
|
+
process.exit(1);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const filePath = args[0];
|
|
384
|
+
const compareMode = args.includes('--compare');
|
|
385
|
+
const beforeFile = compareMode ? args[args.indexOf('--compare') + 1] : null;
|
|
386
|
+
|
|
387
|
+
if (compareMode && beforeFile) {
|
|
388
|
+
const before = analyzeFunctionSignatures(beforeFile);
|
|
389
|
+
const after = analyzeFunctionSignatures(filePath);
|
|
390
|
+
const changes = compareAnalysis(before, after);
|
|
391
|
+
printComparison(changes);
|
|
392
|
+
} else {
|
|
393
|
+
const analysis = analyzeFunctionSignatures(filePath);
|
|
394
|
+
printAnalysis(analysis);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Export for programmatic use
|
|
398
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
399
|
+
module.exports = {
|
|
400
|
+
analyzeFunctionSignatures,
|
|
401
|
+
compareAnalysis
|
|
402
|
+
};
|
|
403
|
+
}
|