@sun-asterisk/impact-analyzer 1.0.4 → 1.0.6

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 (29) hide show
  1. package/.github/copilot-instructions.md +116 -0
  2. package/.github/prompts/README.md +91 -0
  3. package/.github/prompts/task-001-refactor.prompt.md +241 -0
  4. package/.specify/bugs/bug-001-database-detector.md +222 -0
  5. package/.specify/bugs/bug-002-database-detector.md +478 -0
  6. package/.specify/bugs/bug-003-multiline-detection.md +527 -0
  7. package/.specify/plans/architecture.md +186 -0
  8. package/.specify/specs/features/api-impact-detection.md +317 -0
  9. package/.specify/specs/features/component-impact-detection.md +263 -0
  10. package/.specify/specs/features/database-impact-detection.md +247 -0
  11. package/.specify/tasks/task-001-refactor-api-detector.md +284 -0
  12. package/.specify/tasks/task-002-database-detector.md +593 -0
  13. package/.specify/tasks/task-003-component-detector.md +0 -0
  14. package/.specify/tasks/task-004-report.md +484 -0
  15. package/README.md +13 -19
  16. package/core/detectors/database-detector.js +912 -0
  17. package/{modules → core}/detectors/endpoint-detector.js +11 -8
  18. package/{modules → core}/report-generator.js +102 -20
  19. package/core/utils/logger.js +12 -0
  20. package/index.js +6 -5
  21. package/package.json +1 -1
  22. package/modules/detectors/database-detector.js +0 -182
  23. /package/{modules → core}/change-detector.js +0 -0
  24. /package/{modules → core}/impact-analyzer.js +0 -0
  25. /package/{modules → core}/utils/ast-parser.js +0 -0
  26. /package/{modules → core}/utils/dependency-graph.js +0 -0
  27. /package/{modules → core}/utils/file-utils.js +0 -0
  28. /package/{modules → core}/utils/git-utils.js +0 -0
  29. /package/{modules → core}/utils/method-call-graph.js +0 -0
@@ -0,0 +1,478 @@
1
+ # BUG-002: Database Detector Cannot Detect Repository Injection in Services
2
+
3
+ **Bug ID:** BUG-002
4
+ **Status:** ✅ RESOLVED
5
+ **Priority:** HIGH
6
+ **Severity:** Critical
7
+ **Created:** 2025-12-26
8
+ **Updated:** 2025-12-26
9
+ **Resolved:** 2025-12-26
10
+
11
+ ---
12
+
13
+ ## Problem Statement
14
+
15
+ Database detector fails to detect database changes when repositories are injected into services via dependency injection (DI). The current implementation only detects repository methods through call graph traversal, missing direct repository usage in service classes.
16
+
17
+ ### Observed Behavior
18
+
19
+ When a service uses an injected repository, database operations are **not detected**:
20
+
21
+ ```typescript
22
+ // ❌ Not detected:
23
+ export class UserService {
24
+ constructor(
25
+ @InjectRepository(UserEntity)
26
+ private readonly userRepository: Repository<UserEntity>,
27
+ ) {}
28
+
29
+ async updateData() {
30
+ // This change is NOT detected
31
+ const currentData = this.userRepository
32
+ .createQueryBuilder('user')
33
+ .select([
34
+ 'user.id',
35
+ 'user.name',
36
+ 'user.email' // <- Field added here
37
+ ])
38
+ .innerJoin('posts', 'post', 'post.userId = user.id')
39
+ .where('user.status = :status', { status: 'active' })
40
+ .getRawMany();
41
+
42
+ // This is also NOT detected
43
+ await this.userRepository.update({ id: 1 }, { name: 'Updated' });
44
+ }
45
+ }
46
+ ```
47
+
48
+ ### Expected Behavior
49
+
50
+ All database operations should be detected, regardless of whether they are in repository classes or injected into services.
51
+
52
+ ---
53
+
54
+ ## Root Cause
55
+
56
+ **File:** `core/detectors/database-detector.js` (line ~263-289)
57
+
58
+ ### Issue 1: Call Graph Dependency
59
+
60
+ The `findAffectedRepositoryMethods()` method relies on call graph traversal:
61
+
62
+ ```javascript
63
+ findAffectedRepositoryMethods(changedMethods) {
64
+ const visited = new Set();
65
+ const repoMethods = [];
66
+ const queue = [...changedMethods];
67
+
68
+ while (queue.length > 0) {
69
+ const method = queue.shift();
70
+
71
+ // Only finds methods in repository files or with "repository" in class name
72
+ const isRepository = method.file.includes('repository') ||
73
+ (method.className && method.className.toLowerCase().includes('repository'));
74
+
75
+ if (isRepository) {
76
+ repoMethods.push(method);
77
+ }
78
+
79
+ const callers = this.methodCallGraph.getCallers(method);
80
+ queue.push(...callers);
81
+ }
82
+
83
+ return repoMethods;
84
+ }
85
+ ```
86
+
87
+ **Problems:**
88
+ 1. ❌ Requires method call graph to connect service → repository
89
+ 2. ❌ Only detects repository classes, not injected repository instances
90
+ 3. ❌ Misses `this.userRepository` usage in services
91
+ 4. ❌ Cannot detect direct query builder usage
92
+
93
+ ### Issue 2: Missing Direct Detection
94
+
95
+ The `analyzeChangedCodeForDatabaseOps()` method only detects operations in changed lines:
96
+
97
+ ```javascript
98
+ // Only looks at added lines in diff
99
+ for (const [opName, opType] of Object.entries(typeOrmOps)) {
100
+ if (cleanLine.includes(`.${opName}(`)) {
101
+ // Detect operation
102
+ }
103
+ }
104
+ ```
105
+
106
+ **Problems:**
107
+ 1. ❌ Doesn't detect `@InjectRepository()` decorator
108
+ 2. ❌ Doesn't track injected repository field names
109
+ 3. ❌ Doesn't detect `this.repoName.operation()` patterns
110
+ 4. ❌ Misses query builder chains
111
+
112
+ ---
113
+
114
+ ## Fix Strategy
115
+
116
+ ### Approach
117
+
118
+ Enhance detection to work without call graph dependency. Directly analyze changed files for:
119
+ 1. Repository injection patterns (`@InjectRepository`)
120
+ 2. Repository field usage (`this.userRepository`)
121
+ 3. Query builder patterns (`.createQueryBuilder()`, `.select()`, `.where()`)
122
+ 4. Entity references to determine affected tables
123
+
124
+ ### Implementation Steps
125
+
126
+ #### Step 1: Detect Repository Injection
127
+
128
+ Add method to detect injected repositories from constructor parameters:
129
+
130
+ ```javascript
131
+ /**
132
+ * Detect @InjectRepository decorators and extract entity information
133
+ */
134
+ detectInjectedRepositories(content, filePath) {
135
+ const injectedRepos = [];
136
+ const lines = content.split('\n');
137
+
138
+ for (let i = 0; i < lines.length; i++) {
139
+ const line = lines[i];
140
+
141
+ // Pattern: @InjectRepository(UserEntity)
142
+ const injectMatch = line.match(/@InjectRepository\s*\(\s*(\w+Entity)\s*\)/);
143
+ if (injectMatch) {
144
+ const entityName = injectMatch[1];
145
+
146
+ // Look for field name in next few lines
147
+ // private readonly userRepository: Repository<UserEntity>
148
+ for (let j = i; j < Math.min(i + 3, lines.length); j++) {
149
+ const fieldMatch = lines[j].match(/(?:private|public|protected)\s+(?:readonly\s+)?(\w+)\s*:/);
150
+ if (fieldMatch) {
151
+ injectedRepos.push({
152
+ entityName: entityName,
153
+ fieldName: fieldMatch[1],
154
+ tableName: this.entityToTableName(entityName),
155
+ file: filePath
156
+ });
157
+ break;
158
+ }
159
+ }
160
+ }
161
+ }
162
+
163
+ return injectedRepos;
164
+ }
165
+ ```
166
+
167
+ #### Step 2: Detect Repository Usage in Changes
168
+
169
+ Enhance `analyzeChangedCodeForDatabaseOps()` to detect repository field usage:
170
+
171
+ ```javascript
172
+ analyzeChangedCodeForDatabaseOps(changedFile, databaseChanges) {
173
+ const diff = changedFile.diff || '';
174
+ const content = changedFile.content || '';
175
+
176
+ // Step 1: Detect injected repositories in file
177
+ const injectedRepos = this.detectInjectedRepositories(content, changedFile.path);
178
+
179
+ if (injectedRepos.length > 0) {
180
+ this.logger.verbose('DatabaseDetector',
181
+ `Found ${injectedRepos.length} injected repositories in ${changedFile.path}`);
182
+ }
183
+
184
+ // Step 2: Analyze diff for repository usage
185
+ const lines = diff.split('\n');
186
+
187
+ for (const line of lines) {
188
+ if (!line.startsWith('+')) continue;
189
+
190
+ const addedLine = line.substring(1).trim();
191
+
192
+ // Check if line uses any injected repository
193
+ for (const repo of injectedRepos) {
194
+ // Pattern: this.userRepository.find(...)
195
+ if (addedLine.includes(`this.${repo.fieldName}.`)) {
196
+ this.detectOperationFromRepositoryUsage(
197
+ addedLine,
198
+ repo,
199
+ databaseChanges
200
+ );
201
+ }
202
+ }
203
+ }
204
+ }
205
+ ```
206
+
207
+ #### Step 3: Detect Query Builder Patterns
208
+
209
+ Add specific detection for query builder chains:
210
+
211
+ ```javascript
212
+ /**
213
+ * Detect database operations from repository field usage
214
+ */
215
+ detectOperationFromRepositoryUsage(line, repo, databaseChanges) {
216
+ const operations = {
217
+ 'createQueryBuilder': 'SELECT',
218
+ 'find': 'SELECT',
219
+ 'findOne': 'SELECT',
220
+ 'update': 'UPDATE',
221
+ 'insert': 'INSERT',
222
+ 'delete': 'DELETE',
223
+ 'save': 'INSERT/UPDATE',
224
+ };
225
+
226
+ // Detect operation
227
+ let detectedOp = null;
228
+ for (const [method, opType] of Object.entries(operations)) {
229
+ if (line.includes(`.${method}(`)) {
230
+ detectedOp = opType;
231
+ break;
232
+ }
233
+ }
234
+
235
+ if (!detectedOp) return;
236
+
237
+ // Extract entity/table info
238
+ const tableName = repo.tableName;
239
+
240
+ if (!databaseChanges.tables.has(tableName)) {
241
+ databaseChanges.tables.set(tableName, {
242
+ entity: repo.entityName,
243
+ file: repo.file,
244
+ operations: new Set(),
245
+ fields: new Set(),
246
+ isEntityFile: false,
247
+ hasRelationChange: false,
248
+ changeSource: { path: repo.file }
249
+ });
250
+ }
251
+
252
+ const tableData = databaseChanges.tables.get(tableName);
253
+ tableData.operations.add(detectedOp);
254
+
255
+ // Extract fields from query builder
256
+ this.extractFieldsFromQueryBuilder(line, tableData);
257
+
258
+ this.logger.verbose('DatabaseDetector',
259
+ `Detected ${detectedOp} on ${tableName} via ${repo.fieldName}`);
260
+ }
261
+ ```
262
+
263
+ #### Step 4: Extract Fields from Query Builder
264
+
265
+ ```javascript
266
+ /**
267
+ * Extract field names from query builder select()
268
+ */
269
+ extractFieldsFromQueryBuilder(line, tableData) {
270
+ // Pattern: .select(['user.id', 'user.name', 'user.email'])
271
+ const selectMatch = line.match(/\.select\s*\(\s*\[([^\]]+)\]/);
272
+ if (selectMatch) {
273
+ const fieldsStr = selectMatch[1];
274
+ const fieldMatches = fieldsStr.matchAll(/['"](?:\w+\.)?(\w+)['"]/g);
275
+
276
+ for (const match of fieldMatches) {
277
+ const fieldName = match[1];
278
+ if (fieldName !== 'delFlg') {
279
+ tableData.fields.add(fieldName);
280
+ }
281
+ }
282
+ }
283
+
284
+ // Pattern: .where('user.name = :name', ...)
285
+ const whereMatch = line.match(/\.where\s*\(\s*['"](?:\w+\.)?(\w+)\s*[=<>]/);
286
+ if (whereMatch) {
287
+ const fieldName = whereMatch[1];
288
+ if (fieldName !== 'delFlg') {
289
+ tableData.fields.add(fieldName);
290
+ }
291
+ }
292
+ }
293
+ ```
294
+
295
+ ---
296
+
297
+ ## Verification
298
+
299
+ ### Test Case 1: Injected Repository Detection
300
+
301
+ **Input:**
302
+ ```typescript
303
+ + export class UserService {
304
+ + constructor(
305
+ + @InjectRepository(UserEntity)
306
+ + private readonly userRepository: Repository<UserEntity>,
307
+ + ) {}
308
+ +
309
+ + async updateData() {
310
+ + await this.userRepository.update({ id: 1 }, { name: 'Updated' });
311
+ + }
312
+ + }
313
+ ```
314
+
315
+ **Expected Output:**
316
+ ```javascript
317
+ {
318
+ tableName: "user",
319
+ modelName: "UserEntity",
320
+ operations: ["UPDATE"],
321
+ fields: ["id", "name"],
322
+ severity: "medium"
323
+ }
324
+ ```
325
+
326
+ ### Test Case 2: Query Builder with Select
327
+
328
+ **Input:**
329
+ ```typescript
330
+ + const users = this.userRepository
331
+ + .createQueryBuilder('user')
332
+ + .select(['user.id', 'user.name', 'user.email'])
333
+ + .where('user.status = :status', { status: 'active' })
334
+ + .getRawMany();
335
+ ```
336
+
337
+ **Expected Output:**
338
+ ```javascript
339
+ {
340
+ tableName: "user",
341
+ modelName: "UserEntity",
342
+ operations: ["SELECT"],
343
+ fields: ["id", "name", "email", "status"],
344
+ severity: "low"
345
+ }
346
+ ```
347
+
348
+ ### Test Case 3: Multiple Injected Repositories
349
+
350
+ **Input:**
351
+ ```typescript
352
+ + export class PostService {
353
+ + constructor(
354
+ + @InjectRepository(PostEntity)
355
+ + private readonly postRepository: Repository<PostEntity>,
356
+ + @InjectRepository(UserEntity)
357
+ + private readonly userRepository: Repository<UserEntity>,
358
+ + ) {}
359
+ +
360
+ + async updatePost() {
361
+ + await this.postRepository.update({ id: 1 }, { title: 'New' });
362
+ + await this.userRepository.findOne({ where: { id: 1 } });
363
+ + }
364
+ + }
365
+ ```
366
+
367
+ **Expected Output:**
368
+ ```javascript
369
+ [
370
+ {
371
+ tableName: "post",
372
+ operations: ["UPDATE"],
373
+ fields: ["id", "title"]
374
+ },
375
+ {
376
+ tableName: "user",
377
+ operations: ["SELECT"],
378
+ fields: ["id"]
379
+ }
380
+ ]
381
+ ```
382
+
383
+ ---
384
+
385
+ ## Impact Assessment
386
+
387
+ ### Risk Level: **MEDIUM** ⚠️
388
+
389
+ **Why Medium Risk:**
390
+ - Adds new detection methods alongside existing logic
391
+ - Does not remove call graph detection (backward compatible)
392
+ - Extends functionality without breaking existing behavior
393
+ - More comprehensive detection may find more issues (good thing)
394
+
395
+ ### Areas Affected
396
+
397
+ | Component | Change Type | Risk |
398
+ |-----------|-------------|------|
399
+ | `database-detector.js` | New methods added | Medium |
400
+ | `analyzeChangedCodeForDatabaseOps()` | Enhanced | Medium |
401
+ | Report output | More complete data | Low |
402
+ | Existing detections | Unchanged | None |
403
+
404
+ ### Benefits
405
+
406
+ - ✅ Detects repository injection patterns
407
+ - ✅ Works without call graph dependency
408
+ - ✅ Catches direct repository usage in services
409
+ - ✅ More accurate field detection
410
+ - ✅ Detects query builder patterns
411
+
412
+ ### Testing Required
413
+
414
+ - [ ] Test with `@InjectRepository` decorator
415
+ - [ ] Test with `this.repository.method()` patterns
416
+ - [ ] Test with query builder chains
417
+ - [ ] Test with multiple injected repositories
418
+ - [ ] Verify existing call graph detection still works
419
+ - [ ] Integration test with real service files
420
+
421
+ ---
422
+
423
+ ## Implementation Checklist
424
+
425
+ - [x] Add `detectInjectedRepositories()` method
426
+ - [x] Add `detectOperationFromRepositoryUsage()` method
427
+ - [x] Add `extractFieldsFromQueryBuilder()` method
428
+ - [x] Enhance `analyzeChangedCodeForDatabaseOps()` to use new methods
429
+ - [x] Test with `@InjectRepository` pattern
430
+ - [x] Test with `this.repo.find()` pattern
431
+ - [x] Test with query builder `.select()`, `.where()`
432
+ - [x] Test with multiple repositories
433
+ - [ ] Update tests to cover new patterns
434
+ - [x] Verify backward compatibility
435
+ - [ ] Update documentation
436
+ - [x] Mark bug as RESOLVED
437
+
438
+ ---
439
+
440
+ ## References
441
+
442
+ **Code Files:**
443
+ - `core/detectors/database-detector.js` (line ~263-289, ~290-380)
444
+ - `core/report-generator.js` (displays operations)
445
+
446
+ **Related:**
447
+ - BUG-001: `.specify/bugs/bug-001-database-detector.md`
448
+ - Task 002: `.specify/tasks/task-002-database-detector.md`
449
+ - Spec: `.specify/specs/features/database-impact-detection.md`
450
+ - Architecture Doc: `.specify/plans/architecture.md`
451
+ - Code Rules: `.github/copilot-instructions.md`
452
+
453
+
454
+ **NestJS/TypeORM Documentation:**
455
+ - Dependency Injection: https://docs.nestjs.com/providers
456
+ - @InjectRepository: https://docs.nestjs.com/techniques/database#repository-pattern
457
+ - Query Builder: https://typeorm.io/select-query-builder
458
+
459
+ ---
460
+
461
+ ## Notes
462
+
463
+ ### Why Call Graph Approach Fails
464
+
465
+ 1. **DI Pattern:** Repositories are injected, not called as methods
466
+ 2. **Instance Methods:** `this.repo.method()` doesn't appear in call graph as class method
467
+ 3. **Query Builder:** Chained methods create no clear method signature
468
+ 4. **Service Layer:** Most business logic uses injected repos, not repository classes directly
469
+
470
+ ### Better Approach
471
+
472
+ **Direct pattern matching** is more reliable than call graph for DI patterns:
473
+ - Detect `@InjectRepository(Entity)` → know which table
474
+ - Track field name → know which variable to watch
475
+ - Match `this.fieldName.operation()` → detect usage
476
+ - Parse query builder → extract fields and conditions
477
+
478
+ This approach is **more accurate** and **doesn't depend on AST call graph**.