@sun-asterisk/impact-analyzer 1.0.3 → 1.0.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.
@@ -0,0 +1,186 @@
1
+ # Impact Analyzer - Architecture
2
+
3
+ ## What This Tool Does
4
+
5
+ Analyzes code changes in TypeScript/JavaScript projects to identify impacts on APIs, databases, and logic dependencies. Generates severity-based reports (LOW/MEDIUM/HIGH/CRITICAL).
6
+
7
+ ## System Architecture
8
+
9
+ High-level flow through 4 stages:
10
+
11
+ **Stage 1: Change Detection** (`change-detector.js`)
12
+ - Parse git diff to find changed files
13
+ - Extract modified symbols using AST parser
14
+ - Output: List of changed methods/functions/classes
15
+
16
+ **Stage 2: Call Graph Building** (`method-call-graph.js`, `dependency-graph.js`)
17
+ - Parse all source files with ts-morph
18
+ - Build method-to-caller relationships
19
+ - Build method-to-callee relationships
20
+ - Map HTTP decorators to methods
21
+ - Output: Complete call graph
22
+
23
+ **Stage 3: Impact Detection** (`detectors/`)
24
+ - Endpoint Detector: Traverse call graph upward to find affected API endpoints
25
+ - Database Detector: Detect DB operations and extract table/field information
26
+ - Output: Affected endpoints + DB impacts
27
+
28
+ **Stage 4: Report Generation** (`report-generator.js`)
29
+ - Calculate impact score based on endpoints, DB ops, and callers
30
+ - Apply severity multipliers
31
+ - Generate console/markdown/JSON reports
32
+ - Output: Final impact report with severity level
33
+
34
+ ## File Structure
35
+
36
+ ```
37
+ core/
38
+ ├── change-detector.js # Git diff + AST extraction
39
+ ├── impact-analyzer.js # Main orchestrator
40
+ ├── report-generator.js # Score calculation + output
41
+ ├── detectors/
42
+ │ ├── endpoint-detector.js # API impact via call graph traversal
43
+ │ └── database-detector.js # DB impact via pattern detection
44
+ └── utils/
45
+ ├── ast-parser.js # Parse TS/JS with ts-morph
46
+ ├── method-call-graph.js # Build method-level call graphs
47
+ ├── dependency-graph.js # Track file/module dependencies
48
+ ├── file-utils.js # File system operations
49
+ └── git-utils.js # Git operations (diff, show)
50
+ ```
51
+
52
+ ## Key Algorithms
53
+
54
+ ### 1. Call Graph Traversal (Endpoint Detection)
55
+
56
+ ```javascript
57
+ // Find endpoints affected by changed method
58
+ function findAffectedEndpoints(changedMethod, callGraph) {
59
+ const visited = new Set();
60
+ const endpoints = [];
61
+
62
+ // Traverse upward through callers
63
+ function traverseUp(method) {
64
+ if (visited.has(method)) return;
65
+ visited.add(method);
66
+
67
+ const callers = callGraph.getCallers(method);
68
+ for (const caller of callers) {
69
+ // Check if this caller is an endpoint
70
+ if (hasHttpDecorator(caller)) {
71
+ endpoints.push(extractEndpointInfo(caller));
72
+ } else {
73
+ // Continue traversing up
74
+ traverseUp(caller);
75
+ }
76
+ }
77
+ }
78
+
79
+ traverseUp(changedMethod);
80
+ return endpoints;
81
+ }
82
+ ```
83
+
84
+ ### 2. Database Impact Detection
85
+
86
+ ```javascript
87
+ // Detect DB operations in repository methods
88
+ function detectDatabaseImpact(changedFile, ast) {
89
+ const impacts = [];
90
+
91
+ // Find repository layer methods
92
+ const repoMethods = findRepositoryMethods(ast);
93
+
94
+ for (const method of repoMethods) {
95
+ // Look for ORM calls: find, save, update, delete
96
+ const dbCalls = findDatabaseCalls(method);
97
+
98
+ for (const call of dbCalls) {
99
+ impacts.push({
100
+ table: extractTableName(call),
101
+ fields: extractFields(call),
102
+ operation: getOperationType(call), // READ/WRITE
103
+ orm: detectORM(call) // TypeORM/Prisma/etc
104
+ });
105
+ }
106
+ }
107
+
108
+ return impacts;
109
+ }
110
+ ```
111
+
112
+ ### 3. Impact Score Calculation
113
+
114
+ ```javascript
115
+ function calculateImpactScore(impacts) {
116
+ let score = 0;
117
+
118
+ // Base scoring
119
+ score += impacts.endpoints.length * 10;
120
+ score += impacts.dbTables.length * 5;
121
+ score += impacts.directCallers.length * 3;
122
+ score += impacts.indirectCallers.length * 1;
123
+
124
+ // Apply multipliers
125
+ if (impacts.hasHighRiskLogic) {
126
+ score *= 1.5;
127
+ }
128
+ if (impacts.hasDatabaseMigration) {
129
+ score += 20;
130
+ }
131
+
132
+ // Determine severity
133
+ const severity =
134
+ score < 21 ? 'LOW' :
135
+ score < 51 ? 'MEDIUM' :
136
+ score < 101 ? 'HIGH' : 'CRITICAL';
137
+
138
+ return { score, severity };
139
+ }
140
+ ```
141
+
142
+ ## Framework Support
143
+
144
+ ### Supported Patterns
145
+
146
+ **Backend Frameworks:**
147
+ - NestJS: `@Controller`, `@Get`, `@Post`, `@Put`, `@Delete`
148
+ - Express: `router.get()`, `router.post()`, etc.
149
+ - Fastify: `fastify.get()`, `fastify.post()`, etc.
150
+
151
+ **ORM Frameworks:**
152
+ - TypeORM: `@Entity`, `@Table`, `find()`, `save()`, `update()`
153
+ - Prisma: `prisma.model.findMany()`, `prisma.model.create()`
154
+ - Sequelize: `Model.findAll()`, `Model.create()`
155
+
156
+ **Detection Strategy:**
157
+ 1. Parse decorators (`@Controller`, `@Entity`)
158
+ 2. Match method call patterns (`find`, `save`)
159
+ 3. Extract metadata (route paths, table names)
160
+
161
+ ## Design Principles
162
+
163
+ **1. Single Responsibility**
164
+ - Each module has one clear purpose
165
+ - Detectors are independent and composable
166
+
167
+ **2. Layer-Aware Analysis**
168
+ ```
169
+ Database Layer (Repository)
170
+
171
+ Business Layer (Service)
172
+
173
+ Presentation Layer (Controller)
174
+
175
+ API Endpoints
176
+ ```
177
+
178
+ **3. Optimized Parsing**
179
+ - Only parse source files (skip node_modules)
180
+ - Build call graph once, reuse for all detectors
181
+ - Stream processing for large codebases
182
+
183
+ **4. Extensible Detection**
184
+ - Add new detectors without modifying core
185
+ - Framework patterns defined in config
186
+ - Custom rules per project
@@ -0,0 +1,317 @@
1
+ # Feature Spec: API Impact Detection
2
+
3
+ ## What It Does
4
+
5
+ Finds API endpoints affected by code changes using call graph traversal.
6
+
7
+ ## How It Works
8
+
9
+ **Input:** Changed methods from git diff
10
+ **Output:** List of affected API endpoints with HTTP method and path
11
+
12
+ ### Algorithm
13
+
14
+ ```javascript
15
+ function findAffectedEndpoints(changedMethod, callGraph) {
16
+ // 1. Start from changed method
17
+ // 2. Traverse upward through callers
18
+ // 3. Stop when reaching method with HTTP decorator
19
+ // 4. Extract endpoint info (method, path)
20
+
21
+ const endpoints = [];
22
+ const visited = new Set();
23
+
24
+ function traverse(method) {
25
+ if (visited.has(method)) return;
26
+ visited.add(method);
27
+
28
+ // Check if this is an endpoint
29
+ if (hasHttpDecorator(method)) {
30
+ endpoints.push({
31
+ httpMethod: extractHttpMethod(method),
32
+ path: extractPath(method),
33
+ controller: method.name
34
+ });
35
+ return;
36
+ }
37
+
38
+ // Continue traversing up
39
+ const callers = callGraph.getCallers(method);
40
+ callers.forEach(traverse);
41
+ }
42
+
43
+ traverse(changedMethod);
44
+ return endpoints;
45
+ }
46
+ ```
47
+
48
+ ### Flow Example
49
+
50
+ **Synchronous (Direct Call):**
51
+ ```
52
+ Changed: UserService.updateEmail()
53
+
54
+ Caller: UserController.updateProfile()
55
+
56
+ Check: Has @Put(':id') decorator
57
+
58
+ Output: PUT /users/:id
59
+ ```
60
+
61
+ **Asynchronous (Command/Queue):**
62
+ ```
63
+ Changed: OrderService.processOrder()
64
+
65
+ Caller: ProcessOrderHandler.handle()
66
+
67
+ Check: Has @Process('processOrder') decorator
68
+
69
+ Search: Find where 'processOrder' command is published
70
+
71
+ Found: OrderController.create() with queue.add('processOrder')
72
+
73
+ Check: Has @Post('orders') decorator
74
+
75
+ Output: POST /orders (via command: processOrder)
76
+ ```
77
+
78
+ ## Supported Frameworks
79
+
80
+ ### NestJS (Primary)
81
+
82
+ **HTTP Decorators:**
83
+ - `@Controller('path')` - Base path
84
+ - `@Get('subpath')` - GET method
85
+ - `@Post('subpath')` - POST method
86
+ - `@Put('subpath')` - PUT method
87
+ - `@Delete('subpath')` - DELETE method
88
+ - `@Patch('subpath')` - PATCH method
89
+
90
+ **Queue/Command Decorators:**
91
+ - `@Process('commandName')` - Queue processor
92
+ - `@OnQueueCompleted()` - Queue event handler
93
+
94
+ ```javascript
95
+ // Detection logic for HTTP endpoints
96
+ function hasHttpDecorator(method) {
97
+ const decorators = ['Get', 'Post', 'Put', 'Delete', 'Patch'];
98
+ return method.decorators.some(d => decorators.includes(d.name));
99
+ }
100
+
101
+ // Detection logic for queue handlers
102
+ function isQueueHandler(method) {
103
+ return method.decorators.some(d => d.name === 'Process');
104
+ }
105
+
106
+ function extractCommandName(method) {
107
+ const processDecorator = method.decorators.find(d => d.name === 'Process');
108
+ return processDecorator?.arguments[0]; // Returns 'processOrder'
109
+ }
110
+
111
+ function extractEndpointInfo(method) {
112
+ const httpDecorator = method.decorators.find(d =>
113
+ ['Get', 'Post', 'Put', 'Delete', 'Patch'].includes(d.name)
114
+ );
115
+
116
+ const controllerDecorator = method.class.decorators.find(d =>
117
+ d.name === 'Controller'
118
+ );
119
+
120
+ return {
121
+ method: httpDecorator.name.toUpperCase(),
122
+ path: joinPath(
123
+ controllerDecorator.arguments[0],
124
+ httpDecorator.arguments[0]
125
+ )
126
+ };
127
+ }
128
+ ```
129
+
130
+ ### Express
131
+
132
+ Detect route definitions:
133
+ - `router.get(path, handler)`
134
+ - `router.post(path, handler)`
135
+ - `app.get(path, handler)`
136
+
137
+ ```javascript
138
+ // Detection logic
139
+ function isExpressRoute(callExpression) {
140
+ const methods = ['get', 'post', 'put', 'delete', 'patch'];
141
+ return callExpression.expression.object.name === 'router' &&
142
+ methods.includes(callExpression.expression.property.name);
143
+ }
144
+ ```
145
+
146
+ ## Async Patterns (Command/Queue)
147
+
148
+ ### Queue Publishers
149
+
150
+ Detect queue/command publishing patterns:
151
+ - BullMQ: `queue.add('commandName', data)`
152
+ - NestJS Queue: `await this.queue.add('commandName', data)`
153
+ - AWS SQS: `await sqs.send({ command: 'commandName' })`
154
+ - AWS SNS: `await sns.send({ message: { command: 'commandName' } })`
155
+ - Command Bus: `await commandBus.execute('commandName', data)`
156
+
157
+ ### Detection Algorithm for Async Flow
158
+
159
+ ```javascript
160
+ function findAsyncEndpoints(changedMethod, callGraph) {
161
+ const endpoints = [];
162
+
163
+ // 1. Traverse up to find queue handler
164
+ const handler = findQueueHandler(changedMethod, callGraph);
165
+ if (!handler) return endpoints;
166
+
167
+ // 2. Extract command name from handler decorator
168
+ const commandName = extractCommandName(handler);
169
+ if (!commandName) return endpoints;
170
+
171
+ // 3. Search for publishers of this command
172
+ const publishers = findCommandPublishers(commandName);
173
+
174
+ // 4. For each publisher, find the endpoint
175
+ for (const publisher of publishers) {
176
+ const endpoint = findEndpointForMethod(publisher, callGraph);
177
+ if (endpoint) {
178
+ endpoints.push({
179
+ ...endpoint,
180
+ flow: 'async',
181
+ command: commandName,
182
+ handler: handler.name
183
+ });
184
+ }
185
+ }
186
+
187
+ return endpoints;
188
+ }
189
+
190
+ function findQueueHandler(method, callGraph) {
191
+ const callers = callGraph.getCallers(method);
192
+ for (const caller of callers) {
193
+ if (isQueueHandler(caller)) {
194
+ return caller;
195
+ }
196
+ }
197
+ return null;
198
+ }
199
+
200
+ function findCommandPublishers(commandName) {
201
+ // Search all files for queue.add('commandName')
202
+ const publishers = [];
203
+ const pattern = new RegExp(`\\.add\\(['"\`]${commandName}['"\`]`);
204
+
205
+ // Use AST to find call expressions matching pattern
206
+ // Returns array of methods that publish this command
207
+
208
+ return publishers;
209
+ }
210
+ ```
211
+
212
+ ### Example: Async Flow
213
+
214
+ ```typescript
215
+ // Controller publishes command
216
+ @Controller('orders')
217
+ class OrderController {
218
+ @Post()
219
+ async create(@Body() dto: CreateOrderDto) {
220
+ const command = 'processOrder';
221
+ await this.sns.publishMessage({
222
+ message: { command: command, payload: dto }
223
+ });
224
+
225
+ return { success: true };
226
+ }
227
+ }
228
+
229
+ // Case 1. Handler processes command
230
+ @Processor('orderQueue')
231
+ class OrderProcessor {
232
+ @Process('processOrder')
233
+ async handle(job: Job) {
234
+ await this.orderService.processOrder(job.data); // Changed method
235
+ }
236
+ }
237
+
238
+ // Case 2. Handler command by nest
239
+ @command({name: 'processOrder'})
240
+ export class OrderCommand extends CommonComand {
241
+ async run() {
242
+ await this.orderService.processOrder(job.data); // Changed method
243
+ }
244
+
245
+ // Service method (changed)
246
+ class OrderService {
247
+ async processOrder(data) {
248
+ // Implementation changed here
249
+ }
250
+ }
251
+
252
+ // Detection result:
253
+ // POST /orders (via command: processOrder)
254
+ ```
255
+
256
+ ## Output Structure
257
+
258
+ **Synchronous Endpoint:**
259
+ ```javascript
260
+ {
261
+ type: 'endpoint',
262
+ flow: 'sync',
263
+ method: 'PUT', // HTTP method
264
+ path: '/users/:id', // Route path
265
+ controller: 'UserController.updateProfile',
266
+ changedMethod: 'UserService.updateEmail',
267
+ file: 'src/services/user.service.js'
268
+ }
269
+ ```
270
+
271
+ **Asynchronous Endpoint:**
272
+ ```javascript
273
+ {
274
+ type: 'endpoint',
275
+ flow: 'async',
276
+ method: 'POST',
277
+ path: '/orders',
278
+ controller: 'OrderController.create',
279
+ command: 'processOrder', // Command name
280
+ handler: 'OrderProcessor.handle', // Handler method
281
+ changedMethod: 'OrderService.processOrder',
282
+ file: 'src/services/order.service.js'
283
+ }
284
+ ```
285
+
286
+ ## Implementation Notes
287
+
288
+ **Call Graph Building:**
289
+ - Use `ts-morph` to parse TypeScript/JavaScript files
290
+ - Build reverse map: method → callers
291
+ - Store decorator information on methods
292
+ - Store command names from queue operations
293
+
294
+ **Traversal Strategy:**
295
+ - BFS (breadth-first search) to find closest endpoints first
296
+ - Stop at HTTP decorators (sync) or queue handlers (async)
297
+ - Track visited nodes to avoid cycles
298
+ - Max depth: 10 levels
299
+
300
+ **Async Detection Strategy:**
301
+ 1. Traverse up from changed method to find queue handler
302
+ 2. Extract command name from `@Process('commandName')`
303
+ 3. Search codebase for `queue.add('commandName')` patterns
304
+ 4. Find endpoint that publishes the command
305
+ 5. Combine handler info with endpoint info
306
+
307
+ **Path Construction:**
308
+ - Combine `@Controller` path with HTTP decorator path
309
+ - Handle dynamic segments (`:id`, `:slug`)
310
+ - Normalize paths (remove trailing slashes)
311
+
312
+ **Edge Cases:**
313
+ - Skip if circular dependency detected
314
+ - Skip if max depth reached
315
+ - Report multiple endpoints if method has multiple callers
316
+ - For async: Skip if command name is dynamic/variable
317
+ - For async: Mark as external if command publisher not found