@sun-asterisk/sunlint 1.3.47 → 1.3.49

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 (185) hide show
  1. package/config/rules/rules-registry-generated.json +1717 -282
  2. package/core/architecture-integration.js +57 -15
  3. package/core/cli-action-handler.js +51 -36
  4. package/core/config-manager.js +6 -0
  5. package/core/config-merger.js +33 -0
  6. package/core/config-validator.js +37 -2
  7. package/core/file-targeting-service.js +148 -15
  8. package/core/init-command.js +118 -70
  9. package/core/output-service.js +12 -3
  10. package/core/project-detector.js +517 -0
  11. package/core/scoring-service.js +12 -6
  12. package/core/summary-report-service.js +9 -4
  13. package/core/tui-select.js +245 -0
  14. package/engines/arch-detect/rules/layered/l001-presentation-layer.js +7 -15
  15. package/engines/arch-detect/rules/layered/l002-business-layer.js +7 -15
  16. package/engines/arch-detect/rules/layered/l003-data-layer.js +7 -15
  17. package/engines/arch-detect/rules/layered/l004-model-layer.js +7 -15
  18. package/engines/arch-detect/rules/layered/l005-layer-separation.js +22 -2
  19. package/engines/arch-detect/rules/layered/l006-dependency-direction.js +8 -5
  20. package/engines/arch-detect/rules/modular/m005-no-deep-imports.js +67 -29
  21. package/engines/arch-detect/rules/presentation/pr001-view-layer.js +16 -9
  22. package/engines/arch-detect/rules/presentation/pr006-router-layer.js +33 -8
  23. package/engines/arch-detect/rules/presentation/pr007-interactor-layer.js +35 -6
  24. package/engines/arch-detect/rules/project-scanner/ps003-framework-detection.js +56 -10
  25. package/engines/impact/cli.js +54 -39
  26. package/engines/impact/config/default-config.js +105 -5
  27. package/engines/impact/core/impact-analyzer.js +12 -15
  28. package/engines/impact/core/utils/gitignore-parser.js +123 -0
  29. package/engines/impact/core/utils/method-call-graph.js +272 -87
  30. package/origin-rules/dart-en.md +1 -1
  31. package/origin-rules/go-en.md +231 -0
  32. package/origin-rules/php-en.md +107 -0
  33. package/origin-rules/python-en.md +113 -0
  34. package/origin-rules/ruby-en.md +607 -0
  35. package/package.json +1 -1
  36. package/scripts/copy-arch-detect.js +5 -1
  37. package/scripts/copy-impact-analyzer.js +5 -1
  38. package/scripts/generate-rules-registry.js +30 -14
  39. package/skill-assets/sunlint-code-quality/SKILL.md +3 -2
  40. package/skill-assets/sunlint-code-quality/rules/dart/C006-verb-noun-functions.md +45 -0
  41. package/skill-assets/sunlint-code-quality/rules/dart/C013-no-dead-code.md +53 -0
  42. package/skill-assets/sunlint-code-quality/rules/dart/C014-dependency-injection.md +92 -0
  43. package/skill-assets/sunlint-code-quality/rules/dart/C017-no-constructor-logic.md +62 -0
  44. package/skill-assets/sunlint-code-quality/rules/dart/C018-generic-errors.md +57 -0
  45. package/skill-assets/sunlint-code-quality/rules/dart/C019-error-log-level.md +50 -0
  46. package/skill-assets/sunlint-code-quality/rules/dart/C020-no-unused-imports.md +46 -0
  47. package/skill-assets/sunlint-code-quality/rules/dart/C022-no-unused-variables.md +50 -0
  48. package/skill-assets/sunlint-code-quality/rules/dart/C023-no-duplicate-names.md +56 -0
  49. package/skill-assets/sunlint-code-quality/rules/dart/C024-centralize-constants.md +75 -0
  50. package/skill-assets/sunlint-code-quality/rules/dart/C029-catch-log-root-cause.md +53 -0
  51. package/skill-assets/sunlint-code-quality/rules/dart/C030-custom-error-classes.md +86 -0
  52. package/skill-assets/sunlint-code-quality/rules/dart/C033-separate-data-access.md +90 -0
  53. package/skill-assets/sunlint-code-quality/rules/dart/C035-error-context-logging.md +62 -0
  54. package/skill-assets/sunlint-code-quality/rules/dart/C041-no-hardcoded-secrets.md +75 -0
  55. package/skill-assets/sunlint-code-quality/rules/dart/C042-boolean-naming.md +73 -0
  56. package/skill-assets/sunlint-code-quality/rules/dart/C052-widget-parsing.md +84 -0
  57. package/skill-assets/sunlint-code-quality/rules/dart/C060-superclass-logic.md +91 -0
  58. package/skill-assets/sunlint-code-quality/rules/dart/C067-no-hardcoded-config.md +108 -0
  59. package/skill-assets/sunlint-code-quality/rules/go/G001-explicit-error-handling.md +53 -0
  60. package/skill-assets/sunlint-code-quality/rules/go/G002-context-first-argument.md +44 -0
  61. package/skill-assets/sunlint-code-quality/rules/go/G003-receiver-consistency.md +38 -0
  62. package/skill-assets/sunlint-code-quality/rules/go/G004-avoid-panic.md +49 -0
  63. package/skill-assets/sunlint-code-quality/rules/go/G005-goroutine-leak-prevention.md +49 -0
  64. package/skill-assets/sunlint-code-quality/rules/go/G006-interface-consumer-definition.md +45 -0
  65. package/skill-assets/sunlint-code-quality/rules/go/GN001-gin-binding-validation.md +57 -0
  66. package/skill-assets/sunlint-code-quality/rules/go/GN002-gin-error-response.md +48 -0
  67. package/skill-assets/sunlint-code-quality/rules/go/GN003-graceful-shutdown.md +57 -0
  68. package/skill-assets/sunlint-code-quality/rules/go/GN004-gin-route-logical-grouping.md +54 -0
  69. package/skill-assets/sunlint-code-quality/rules/go-gin/AGENTS.md +149 -0
  70. package/skill-assets/sunlint-code-quality/rules/go-gin/GN001-abort-after-response.md +75 -0
  71. package/skill-assets/sunlint-code-quality/rules/go-gin/GN002-request-context.md +64 -0
  72. package/skill-assets/sunlint-code-quality/rules/go-gin/GN003-bind-error-handling.md +70 -0
  73. package/skill-assets/sunlint-code-quality/rules/go-gin/GN004-dependency-injection.md +78 -0
  74. package/skill-assets/sunlint-code-quality/rules/go-gin/GN005-route-groups-middleware.md +71 -0
  75. package/skill-assets/sunlint-code-quality/rules/go-gin/GN006-http-status-codes.md +91 -0
  76. package/skill-assets/sunlint-code-quality/rules/go-gin/GN007-release-mode.md +64 -0
  77. package/skill-assets/sunlint-code-quality/rules/go-gin/GN008-struct-validation-tags.md +90 -0
  78. package/skill-assets/sunlint-code-quality/rules/go-gin/GN009-recovery-middleware.md +68 -0
  79. package/skill-assets/sunlint-code-quality/rules/go-gin/GN010-context-scope.md +68 -0
  80. package/skill-assets/sunlint-code-quality/rules/go-gin/GN011-middleware-concerns.md +92 -0
  81. package/skill-assets/sunlint-code-quality/rules/go-gin/GN012-no-log-sensitive.md +84 -0
  82. package/skill-assets/sunlint-code-quality/rules/java/J001-try-with-resources.md +86 -0
  83. package/skill-assets/sunlint-code-quality/rules/java/J002-equals-and-hashcode.md +88 -0
  84. package/skill-assets/sunlint-code-quality/rules/java/J003-string-comparison.md +72 -0
  85. package/skill-assets/sunlint-code-quality/rules/java/J004-use-java-time.md +91 -0
  86. package/skill-assets/sunlint-code-quality/rules/java/J005-no-print-stack-trace.md +80 -0
  87. package/skill-assets/sunlint-code-quality/rules/java/J006-no-system-println.md +89 -0
  88. package/skill-assets/sunlint-code-quality/rules/java/J007-proper-logger.md +91 -0
  89. package/skill-assets/sunlint-code-quality/rules/java/J008-thread-safe-singleton.md +119 -0
  90. package/skill-assets/sunlint-code-quality/rules/java/J009-utility-class-constructor.md +82 -0
  91. package/skill-assets/sunlint-code-quality/rules/java/J010-preserve-stack-trace.md +119 -0
  92. package/skill-assets/sunlint-code-quality/rules/java/J011-null-safe-compare.md +88 -0
  93. package/skill-assets/sunlint-code-quality/rules/java/J012-use-enum-collections.md +104 -0
  94. package/skill-assets/sunlint-code-quality/rules/java/J013-return-empty-not-null.md +102 -0
  95. package/skill-assets/sunlint-code-quality/rules/java/J014-hardcoded-crypto-key.md +108 -0
  96. package/skill-assets/sunlint-code-quality/rules/java/J015-optional-instead-of-null.md +109 -0
  97. package/skill-assets/sunlint-code-quality/rules/php-laravel/AGENTS.md +124 -0
  98. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV001-form-request-validation.md +64 -0
  99. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV002-eager-load-no-n-plus-1.md +58 -0
  100. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV003-config-not-env.md +54 -0
  101. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV004-fillable-mass-assignment.md +51 -0
  102. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV005-policies-gates-authorization.md +71 -0
  103. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV006-queue-heavy-tasks.md +68 -0
  104. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV007-hash-passwords.md +51 -0
  105. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV008-route-model-binding.md +67 -0
  106. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV009-api-resources.md +72 -0
  107. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV010-chunk-large-datasets.md +58 -0
  108. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV011-db-transactions.md +73 -0
  109. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV012-service-layer.md +78 -0
  110. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV013-testing-factories.md +75 -0
  111. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV014-service-container.md +61 -0
  112. package/skill-assets/sunlint-code-quality/rules/python/P001-mutable-default-argument.md +55 -0
  113. package/skill-assets/sunlint-code-quality/rules/python/P002-specify-file-encoding.md +45 -0
  114. package/skill-assets/sunlint-code-quality/rules/python/P003-context-manager-for-resources.md +54 -0
  115. package/skill-assets/sunlint-code-quality/rules/python/P004-no-bare-except.md +65 -0
  116. package/skill-assets/sunlint-code-quality/rules/python/P005-use-isinstance.md +60 -0
  117. package/skill-assets/sunlint-code-quality/rules/python/P006-timezone-aware-datetime.md +58 -0
  118. package/skill-assets/sunlint-code-quality/rules/python/P007-use-pathlib.md +62 -0
  119. package/skill-assets/sunlint-code-quality/rules/python/P008-no-wildcard-import.md +52 -0
  120. package/skill-assets/sunlint-code-quality/rules/python/P009-logging-lazy-format.md +50 -0
  121. package/skill-assets/sunlint-code-quality/rules/python/P010-exception-chaining.md +57 -0
  122. package/skill-assets/sunlint-code-quality/rules/python/P011-subprocess-check.md +59 -0
  123. package/skill-assets/sunlint-code-quality/rules/python/P012-requests-timeout.md +70 -0
  124. package/skill-assets/sunlint-code-quality/rules/python/P013-no-global-statement.md +73 -0
  125. package/skill-assets/sunlint-code-quality/rules/python/P014-no-modify-collection-while-iterating.md +66 -0
  126. package/skill-assets/sunlint-code-quality/rules/python/P015-prefer-fstrings.md +61 -0
  127. package/skill-assets/sunlint-code-quality/rules/ruby-rails/AGENTS.md +121 -0
  128. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR001-strong-parameters.md +55 -0
  129. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR002-eager-load-includes.md +51 -0
  130. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR003-service-objects.md +99 -0
  131. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR004-active-job-background.md +67 -0
  132. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR005-pagination.md +53 -0
  133. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR006-find-each-batches.md +53 -0
  134. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR007-http-status-codes.md +76 -0
  135. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR008-before-action-auth.md +77 -0
  136. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR009-rails-credentials.md +61 -0
  137. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR010-scopes.md +57 -0
  138. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR011-counter-cache.md +59 -0
  139. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR012-render-json-status.md +42 -0
  140. package/skill-assets/sunlint-code-quality/rules/swift/C006-verb-noun-functions.md +37 -0
  141. package/skill-assets/sunlint-code-quality/rules/swift/C013-no-dead-code.md +55 -0
  142. package/skill-assets/sunlint-code-quality/rules/swift/C014-dependency-injection.md +69 -0
  143. package/skill-assets/sunlint-code-quality/rules/swift/C017-no-constructor-logic.md +66 -0
  144. package/skill-assets/sunlint-code-quality/rules/swift/C018-generic-errors.md +64 -0
  145. package/skill-assets/sunlint-code-quality/rules/swift/C019-error-log-level.md +64 -0
  146. package/skill-assets/sunlint-code-quality/rules/swift/C020-no-unused-imports.md +47 -0
  147. package/skill-assets/sunlint-code-quality/rules/swift/C022-no-unused-variables.md +46 -0
  148. package/skill-assets/sunlint-code-quality/rules/swift/C023-no-duplicate-names.md +55 -0
  149. package/skill-assets/sunlint-code-quality/rules/swift/C024-centralize-constants.md +68 -0
  150. package/skill-assets/sunlint-code-quality/rules/swift/C029-catch-log-root-cause.md +69 -0
  151. package/skill-assets/sunlint-code-quality/rules/swift/C030-custom-error-classes.md +77 -0
  152. package/skill-assets/sunlint-code-quality/rules/swift/C033-separate-data-access.md +89 -0
  153. package/skill-assets/sunlint-code-quality/rules/swift/C035-error-context-logging.md +66 -0
  154. package/skill-assets/sunlint-code-quality/rules/swift/C041-no-hardcoded-secrets.md +65 -0
  155. package/skill-assets/sunlint-code-quality/rules/swift/C042-boolean-naming.md +60 -0
  156. package/skill-assets/sunlint-code-quality/rules/swift/C052-controller-parsing.md +67 -0
  157. package/skill-assets/sunlint-code-quality/rules/swift/C060-superclass-logic.md +95 -0
  158. package/skill-assets/sunlint-code-quality/rules/swift/C067-no-hardcoded-config.md +80 -0
  159. package/skill-assets/sunlint-code-quality/rules/swift/S003-sql-injection.md +65 -0
  160. package/skill-assets/sunlint-code-quality/rules/swift/S004-no-log-credentials.md +67 -0
  161. package/skill-assets/sunlint-code-quality/rules/swift/S005-server-authorization.md +73 -0
  162. package/skill-assets/sunlint-code-quality/rules/swift/S006-default-credentials.md +76 -0
  163. package/skill-assets/sunlint-code-quality/rules/swift/S007-output-encoding.md +96 -0
  164. package/skill-assets/sunlint-code-quality/rules/swift/S009-approved-crypto.md +86 -0
  165. package/skill-assets/sunlint-code-quality/rules/swift/S010-csprng.md +71 -0
  166. package/skill-assets/sunlint-code-quality/rules/swift/S011-insecure-deserialization.md +74 -0
  167. package/skill-assets/sunlint-code-quality/rules/swift/S012-secrets-management.md +81 -0
  168. package/skill-assets/sunlint-code-quality/rules/swift/S013-tls-connections.md +67 -0
  169. package/skill-assets/sunlint-code-quality/rules/swift/S017-parameterized-queries.md +86 -0
  170. package/skill-assets/sunlint-code-quality/rules/swift/S019-session-management.md +131 -0
  171. package/skill-assets/sunlint-code-quality/rules/swift/S020-kvc-injection.md +91 -0
  172. package/skill-assets/sunlint-code-quality/rules/swift/S025-input-validation.md +125 -0
  173. package/skill-assets/sunlint-code-quality/rules/swift/S029-brute-force-protection.md +120 -0
  174. package/skill-assets/sunlint-code-quality/rules/swift/S036-path-traversal.md +102 -0
  175. package/skill-assets/sunlint-code-quality/rules/swift/S039-tls-certificate-validation.md +109 -0
  176. package/skill-assets/sunlint-code-quality/rules/swift/S041-logout-invalidation.md +103 -0
  177. package/skill-assets/sunlint-code-quality/rules/swift/S043-password-hashing.md +116 -0
  178. package/skill-assets/sunlint-code-quality/rules/swift/S044-critical-changes-reauth.md +145 -0
  179. package/skill-assets/sunlint-code-quality/rules/swift/S045-debug-info-exposure.md +116 -0
  180. package/skill-assets/sunlint-code-quality/rules/swift/S046-unvalidated-redirect.md +140 -0
  181. package/skill-assets/sunlint-code-quality/rules/swift/S051-token-expiry.md +134 -0
  182. package/skill-assets/sunlint-code-quality/rules/swift/S053-jwt-validation.md +139 -0
  183. package/skill-assets/sunlint-code-quality/rules/swift/S059-background-snapshot-protection.md +113 -0
  184. package/skill-assets/sunlint-code-quality/rules/swift/S060-data-protection-api.md +106 -0
  185. package/skill-assets/sunlint-code-quality/rules/swift/S061-jailbreak-detection.md +132 -0
@@ -50,6 +50,7 @@ const rule = (0, base_rule_1.createRule)('PR006', 'Router/Wireframe Layer', 'Xá
50
50
  folderPatterns: ['routers', 'wireframes', 'navigation', 'routing', 'Routers', 'coordinators'],
51
51
  filePatterns: ['*Router.*', '*Wireframe.*', '*Coordinator.*', '*Navigator.*'],
52
52
  codePatterns: [
53
+ // Swift
53
54
  { language: enums_1.Language.SWIFT, type: enums_1.CodePatternType.PATTERN, pattern: /protocol\s+\w+Router/ },
54
55
  { language: enums_1.Language.SWIFT, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+Router/ },
55
56
  {
@@ -59,12 +60,31 @@ const rule = (0, base_rule_1.createRule)('PR006', 'Router/Wireframe Layer', 'Xá
59
60
  },
60
61
  { language: enums_1.Language.SWIFT, type: enums_1.CodePatternType.METHOD, pattern: /func\s+navigate/ },
61
62
  { language: enums_1.Language.SWIFT, type: enums_1.CodePatternType.METHOD, pattern: /func\s+present/ },
63
+ // Kotlin
62
64
  {
63
65
  language: enums_1.Language.KOTLIN,
64
66
  type: enums_1.CodePatternType.PATTERN,
65
67
  pattern: /interface\s+\w+Router/,
66
68
  },
67
69
  { language: enums_1.Language.KOTLIN, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+Router/ },
70
+ // Java
71
+ { language: enums_1.Language.JAVA, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+Router/ },
72
+ // PHP
73
+ {
74
+ language: enums_1.Language.PHP,
75
+ type: enums_1.CodePatternType.PATTERN,
76
+ pattern: /Route::(get|post|put|delete|group)\s*\(/,
77
+ },
78
+ // Python
79
+ { language: enums_1.Language.PYTHON, type: enums_1.CodePatternType.PATTERN, pattern: /urlpatterns\s*=/ },
80
+ {
81
+ language: enums_1.Language.PYTHON,
82
+ type: enums_1.CodePatternType.PATTERN,
83
+ pattern: /router\s*=\s*APIRouter\s*\(/,
84
+ },
85
+ // Go
86
+ { language: enums_1.Language.GO, type: enums_1.CodePatternType.PATTERN, pattern: /func\s+\w+Router\b/ },
87
+ { language: enums_1.Language.GO, type: enums_1.CodePatternType.PATTERN, pattern: /r\.Group\s*\(/ },
68
88
  ],
69
89
  }, {
70
90
  isKeyIndicator: true,
@@ -110,10 +130,10 @@ let PR006RouterLayer = (() => {
110
130
  }
111
131
  }
112
132
  // Check for router files - match any file containing Router/Wireframe/WireFrame/Coordinator
113
- const routerFiles = context.files.filter((f) => /Router\.(swift|kt|java|ts|js)$/i.test(f.fileName) ||
133
+ const routerFiles = context.files.filter((f) => /Router\.(swift|kt|java|ts|js|php|py|go|rb|cs)$/i.test(f.fileName) ||
114
134
  /WireFrame\.(swift|kt|java)$/i.test(f.fileName) ||
115
135
  /Wireframe\.(swift|kt|java)$/i.test(f.fileName) ||
116
- /Coordinator\.(swift|kt|java|ts)$/i.test(f.fileName));
136
+ /Coordinator\.(swift|kt|java|ts|php|py|go)$/i.test(f.fileName));
117
137
  if (routerFiles.length > 0) {
118
138
  for (const file of routerFiles.slice(0, 5)) {
119
139
  matches.push({
@@ -129,12 +149,17 @@ let PR006RouterLayer = (() => {
129
149
  .filter((f) => !f.isTest && !f.isConfig && f.language)
130
150
  .slice(0, 50);
131
151
  const codePatterns = [
132
- /protocol\s+\w+Router/,
133
- /class\s+\w+Router/,
134
- /class\s+\w+Coordinator/,
135
- /func\s+navigateTo/,
136
- /func\s+presentModule/,
137
- /interface\s+\w+Router/,
152
+ /protocol\s+\w+Router/, // Swift
153
+ /class\s+\w+Router/, // Swift/Kotlin/Java
154
+ /class\s+\w+Coordinator/, // Swift
155
+ /func\s+navigateTo/, // Swift/Kotlin
156
+ /func\s+presentModule/, // Swift
157
+ /interface\s+\w+Router/, // Kotlin/Java
158
+ /Route::(get|post|put|delete|group)\s*\(/, // PHP Laravel
159
+ /urlpatterns\s*=/, // Python Django
160
+ /router\s*=\s*APIRouter\s*\(/, // Python FastAPI
161
+ /func\s+\w+Router\b/, // Go
162
+ /r\.Group\s*\(/, // Go Gin
138
163
  ];
139
164
  for (const file of sourceFiles) {
140
165
  try {
@@ -50,12 +50,14 @@ const rule = (0, base_rule_1.createRule)('PR007', 'Interactor Layer', 'Xác nh
50
50
  folderPatterns: ['interactors', 'usecases', 'Interactors', 'UseCases'],
51
51
  filePatterns: ['*Interactor.*', '*UseCase.*', '*BusinessLogic.*'],
52
52
  codePatterns: [
53
+ // Swift
53
54
  {
54
55
  language: enums_1.Language.SWIFT,
55
56
  type: enums_1.CodePatternType.PATTERN,
56
57
  pattern: /protocol\s+\w+Interactor/,
57
58
  },
58
59
  { language: enums_1.Language.SWIFT, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+Interactor/ },
60
+ // Kotlin
59
61
  {
60
62
  language: enums_1.Language.KOTLIN,
61
63
  type: enums_1.CodePatternType.PATTERN,
@@ -67,6 +69,31 @@ const rule = (0, base_rule_1.createRule)('PR007', 'Interactor Layer', 'Xác nh
67
69
  pattern: /class\s+\w+Interactor/,
68
70
  },
69
71
  { language: enums_1.Language.KOTLIN, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+UseCase/ },
72
+ // Java
73
+ { language: enums_1.Language.JAVA, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+Interactor/ },
74
+ { language: enums_1.Language.JAVA, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+UseCase/ },
75
+ // TypeScript
76
+ {
77
+ language: enums_1.Language.TYPESCRIPT,
78
+ type: enums_1.CodePatternType.PATTERN,
79
+ pattern: /class\s+\w+UseCase/,
80
+ },
81
+ // PHP
82
+ { language: enums_1.Language.PHP, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+UseCase/ },
83
+ { language: enums_1.Language.PHP, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+Interactor/ },
84
+ // Python
85
+ { language: enums_1.Language.PYTHON, type: enums_1.CodePatternType.PATTERN, pattern: /class\s+\w+UseCase/ },
86
+ // Go
87
+ {
88
+ language: enums_1.Language.GO,
89
+ type: enums_1.CodePatternType.PATTERN,
90
+ pattern: /type\s+\w+UseCase\s+struct/,
91
+ },
92
+ {
93
+ language: enums_1.Language.GO,
94
+ type: enums_1.CodePatternType.PATTERN,
95
+ pattern: /type\s+\w+Interactor\s+struct/,
96
+ },
70
97
  ],
71
98
  }, {
72
99
  isKeyIndicator: true,
@@ -110,8 +137,8 @@ let PR007InteractorLayer = (() => {
110
137
  }
111
138
  }
112
139
  // Check for interactor files
113
- const interactorFiles = context.files.filter((f) => /Interactor\.(swift|kt|java|ts|js)$/i.test(f.fileName) ||
114
- /UseCase\.(swift|kt|java|ts|js)$/i.test(f.fileName));
140
+ const interactorFiles = context.files.filter((f) => /Interactor\.(swift|kt|java|ts|js|php|py|go|rb|cs)$/i.test(f.fileName) ||
141
+ /UseCase\.(swift|kt|java|ts|js|php|py|go|rb|cs)$/i.test(f.fileName));
115
142
  if (interactorFiles.length > 0) {
116
143
  for (const file of interactorFiles.slice(0, 5)) {
117
144
  matches.push({
@@ -127,10 +154,12 @@ let PR007InteractorLayer = (() => {
127
154
  .filter((f) => !f.isTest && !f.isConfig && f.language)
128
155
  .slice(0, 50);
129
156
  const codePatterns = [
130
- /protocol\s+\w+Interactor/,
131
- /class\s+\w+Interactor/,
132
- /interface\s+\w+Interactor/,
133
- /class\s+\w+UseCase/,
157
+ /protocol\s+\w+Interactor/, // Swift
158
+ /class\s+\w+Interactor/, // Swift/Kotlin/Java/PHP
159
+ /interface\s+\w+Interactor/, // Kotlin/Java
160
+ /class\s+\w+UseCase/, // Kotlin/Java/TS/PHP/Python
161
+ /type\s+\w+UseCase\s+struct/, // Go
162
+ /type\s+\w+Interactor\s+struct/, // Go
134
163
  ];
135
164
  for (const file of sourceFiles) {
136
165
  try {
@@ -86,9 +86,20 @@ const FRAMEWORK_DETECTORS = [
86
86
  dependencies: [
87
87
  { file: 'pom.xml', packages: ['spring-boot-starter'] },
88
88
  { file: 'build.gradle', packages: ['spring-boot'] },
89
+ { file: 'build.gradle.kts', packages: ['spring-boot'] },
89
90
  ],
90
91
  codePatterns: [/@SpringBootApplication/],
91
92
  },
93
+ // Ktor
94
+ {
95
+ framework: enums_1.Framework.KTOR,
96
+ configFiles: [],
97
+ dependencies: [
98
+ { file: 'build.gradle.kts', packages: ['io.ktor'] },
99
+ { file: 'build.gradle', packages: ['io.ktor'] },
100
+ ],
101
+ codePatterns: [/routing\s*\{/, /embeddedServer\s*\(/],
102
+ },
92
103
  // Angular
93
104
  {
94
105
  framework: enums_1.Framework.ANGULAR,
@@ -133,15 +144,26 @@ const FRAMEWORK_DETECTORS = [
133
144
  // Laravel
134
145
  {
135
146
  framework: enums_1.Framework.LARAVEL,
136
- configFiles: ['artisan'],
147
+ configFiles: ['artisan', 'config/app.php'],
137
148
  dependencies: [{ file: 'composer.json', packages: ['laravel/framework'] }],
138
- codePatterns: [/extends\s+Controller/],
149
+ codePatterns: [/extends\s+Controller/, /use\s+Illuminate\\/],
150
+ },
151
+ // Symfony
152
+ {
153
+ framework: enums_1.Framework.SYMFONY,
154
+ configFiles: ['config/bundles.php', 'symfony.lock'],
155
+ dependencies: [{ file: 'composer.json', packages: ['symfony/framework-bundle'] }],
156
+ codePatterns: [/#\[Route\s*\(/, /extends\s+AbstractController/],
139
157
  },
140
158
  // Django
141
159
  {
142
160
  framework: enums_1.Framework.DJANGO,
143
161
  configFiles: ['manage.py'],
144
- dependencies: [{ file: 'requirements.txt', packages: ['django', 'Django'] }],
162
+ dependencies: [
163
+ { file: 'requirements.txt', packages: ['django', 'Django'] },
164
+ { file: 'pyproject.toml', packages: ['django', 'Django'] },
165
+ { file: 'Pipfile', packages: ['django', 'Django'] },
166
+ ],
145
167
  codePatterns: [/from\s+django/, /INSTALLED_APPS/],
146
168
  },
147
169
  // Rails
@@ -186,15 +208,23 @@ const FRAMEWORK_DETECTORS = [
186
208
  {
187
209
  framework: enums_1.Framework.FASTAPI,
188
210
  configFiles: [],
189
- dependencies: [{ file: 'requirements.txt', packages: ['fastapi'] }],
211
+ dependencies: [
212
+ { file: 'requirements.txt', packages: ['fastapi'] },
213
+ { file: 'pyproject.toml', packages: ['fastapi'] },
214
+ { file: 'Pipfile', packages: ['fastapi'] },
215
+ ],
190
216
  codePatterns: [/from\s+fastapi/, /@app\.(get|post|put|delete)\s*\(/],
191
217
  },
192
218
  // Flask
193
219
  {
194
220
  framework: enums_1.Framework.FLASK,
195
221
  configFiles: [],
196
- dependencies: [{ file: 'requirements.txt', packages: ['flask', 'Flask'] }],
197
- codePatterns: [/from\s+flask/, /Flask\s*\(__name__\)/],
222
+ dependencies: [
223
+ { file: 'requirements.txt', packages: ['flask', 'Flask'] },
224
+ { file: 'pyproject.toml', packages: ['flask', 'Flask'] },
225
+ { file: 'Pipfile', packages: ['flask', 'Flask'] },
226
+ ],
227
+ codePatterns: [/from\s+flask/, /Flask\s*\(\s*__name__\s*\)/],
198
228
  },
199
229
  // Gin (Go)
200
230
  {
@@ -247,7 +277,7 @@ let PS003FrameworkDetection = (() => {
247
277
  // Check dependencies
248
278
  if (detector.dependencies) {
249
279
  for (const dep of detector.dependencies) {
250
- const depDetected = await this.checkDependency(projectRoot, dep.file, dep.packages);
280
+ const depDetected = await this.checkDependency(projectRoot, dep.file, dep.packages, context.files);
251
281
  if (depDetected) {
252
282
  matches.push({
253
283
  type: enums_1.MatchType.CONFIG,
@@ -289,10 +319,26 @@ let PS003FrameworkDetection = (() => {
289
319
  score,
290
320
  };
291
321
  }
292
- async checkDependency(projectRoot, depFile, packages) {
293
- // Handle glob patterns
322
+ async checkDependency(projectRoot, depFile, packages, files) {
323
+ // Handle glob patterns by searching matching files
294
324
  if (depFile.includes('*')) {
295
- // Skip glob for now, check exact files
325
+ if (!files)
326
+ return false;
327
+ const globRegex = new RegExp(`${depFile.replace(/\./g, '\\.').replace(/\*/g, '.*')}$`, 'i');
328
+ const matchingFiles = files.filter((f) => globRegex.test(f.fileName));
329
+ for (const file of matchingFiles) {
330
+ try {
331
+ const content = await file_scanner_1.FileScanner.readFileContent(file.absolutePath);
332
+ for (const pkg of packages) {
333
+ if (content.includes(pkg)) {
334
+ return true;
335
+ }
336
+ }
337
+ }
338
+ catch {
339
+ // Skip unreadable files
340
+ }
341
+ }
296
342
  return false;
297
343
  }
298
344
  const filePath = `${projectRoot}/${depFile}`;
@@ -50,57 +50,72 @@ DESCRIPTION:
50
50
  components in TypeScript/JavaScript projects.
51
51
 
52
52
  OPTIONS:
53
- --input=<path> Source directory to analyze
54
- Default: src
55
53
 
56
- --base=<ref> Base git reference for comparison
57
- Default: HEAD~1 (previous commit)
58
- Examples: origin/main, HEAD~5, abc123
54
+ Input & Source:
55
+ --input=<path> Source directory to analyze (default: src)
56
+
57
+ Git References:
58
+ --base=<ref> Base git reference (default: HEAD~1)
59
+ --head=<ref> Head git reference (default: current working dir)
60
+
61
+ Exclusions:
62
+ --exclude=<paths> Comma-separated paths to exclude
63
+ Example: --exclude=test,examples,mocks
64
+ --use-gitignore Use .gitignore patterns for exclusions (recommended)
65
+ This automatically excludes files ignored by git
66
+
67
+ Analysis Options:
68
+ --max-depth=<n> Maximum call chain depth (default: 3)
69
+ --include-tests Include test files in analysis
70
+ --verbose Show detailed analysis output
71
+
72
+ Output:
73
+ --format=<type> Output format: markdown, json, csv (default: markdown)
74
+ --output=<file> Output file path (default: impact-report.md)
75
+
76
+ Other:
77
+ --help Show this help message
59
78
 
60
- --head=<ref> Head git reference (optional)
61
- Default: (working directory)
62
-
63
- --exclude=<paths> Comma-separated paths to exclude
64
- Default: node_modules,dist,build,specs,coverage
65
-
66
- --output=<file> Output markdown report file
67
- Default: impact-report.md
68
-
69
- --json=<file> Output JSON report file (optional)
70
- Example: --json=impact-report.json
71
-
72
- --max-depth=<n> Maximum call graph depth
73
- Default: 3
74
-
75
- --include-tests Include test files in analysis
79
+ EXAMPLES:
76
80
 
77
- --verbose Show verbose output and stack traces
81
+ Basic usage (analyze changes from last commit):
82
+ $ impact-analyzer --input=src --verbose
78
83
 
79
- --no-fail Don't exit with error on critical impact
84
+ Use .gitignore patterns (recommended for large projects):
85
+ $ impact-analyzer --input=src --use-gitignore --verbose
80
86
 
81
- --help, -h Show this help message
87
+ Analyze specific git range:
88
+ $ impact-analyzer --base=main --head=feature-branch
82
89
 
83
- EXAMPLES:
84
- # Analyze changes in last commit (default)
85
- impact-analyzer
90
+ Custom exclusions:
91
+ $ impact-analyzer --exclude=test,examples,fixtures
86
92
 
87
- # Analyze changes between main branch and current
88
- impact-analyzer --base=origin/main
93
+ Include tests in analysis:
94
+ $ impact-analyzer --include-tests
89
95
 
90
- # Analyze specific directory with JSON output
91
- impact-analyzer --input=backend/src --json=report.json
96
+ Generate JSON report:
97
+ $ impact-analyzer --format=json --output=impact.json
92
98
 
93
- # Analyze last 3 commits with verbose output
94
- impact-analyzer --base=HEAD~3 --verbose
99
+ PERFORMANCE TIPS:
95
100
 
96
- # Compare two specific commits
97
- impact-analyzer --base=abc123 --head=def456
101
+ Use --use-gitignore for automatic exclusion of unnecessary files
102
+ This can reduce analysis time by 60-90% on large projects!
103
+
104
+ ⚡ Exclude test/example directories if not needed:
105
+ --exclude=test,tests,examples,samples,mocks
106
+
107
+ ⚡ For large codebases, limit max-depth:
108
+ --max-depth=2
98
109
 
99
- REFERENCE:
100
- Architecture: .specify/plans/architecture.md
101
- Coding Rules: .github/copilot-instructions.md
110
+ DEFAULT EXCLUSIONS (when not using --use-gitignore):
102
111
 
103
- For more information, visit: https://github.com/sun-asterisk/impact-analyzer
112
+ node_modules, dist, build, out
113
+ • test files: *.test.ts, *.spec.ts
114
+ • examples, samples, fixtures, mocks
115
+ • coverage, reports
116
+ • .cache, .temp, .history
117
+ • IDE: .idea, .vscode
118
+ • Type definitions: *.d.ts
104
119
  `);
105
120
  }
106
121
  }
@@ -3,15 +3,49 @@
3
3
  * Loads and validates configuration from CLI args and defaults
4
4
  */
5
5
 
6
+ import { GitignoreParser, gitignoreToExcludePaths } from '../core/utils/gitignore-parser.js';
7
+ import path from 'path';
8
+
6
9
  export function loadConfig(cli) {
10
+ const sourceDir = cli.getArg('input', 'src');
11
+ const rootDir = path.resolve(sourceDir, '..');
12
+
13
+ // Load patterns from .gitignore
14
+ const gitignorePatterns = cli.hasArg('use-gitignore')
15
+ ? gitignoreToExcludePaths(rootDir)
16
+ : [];
17
+
18
+ // Custom exclude paths from CLI
19
+ const customExcludes = cli.getArg('exclude', '')
20
+ .split(',')
21
+ .map(p => p.trim())
22
+ .filter(p => p);
23
+
24
+ // Merge: gitignore + custom + defaults
25
+ const allExcludes = [
26
+ ...new Set([
27
+ ...gitignorePatterns,
28
+ ...customExcludes,
29
+ ...DEFAULT_EXCLUDE_PATHS,
30
+ ])
31
+ ];
32
+
7
33
  const config = {
8
34
  // Source directory to analyze
9
- sourceDir: cli.getArg('input', 'src'),
35
+ sourceDir: sourceDir,
36
+ rootDir: rootDir,
10
37
 
11
38
  // Paths to exclude from analysis
12
- excludePaths: cli.getArg('exclude', 'node_modules,dist,build,specs,coverage')
13
- .split(',')
14
- .map(p => p.trim()),
39
+ excludePaths: allExcludes,
40
+
41
+ // Gitignore parser for advanced pattern matching
42
+ gitignoreParser: cli.hasArg('use-gitignore')
43
+ ? (() => {
44
+ const parser = new GitignoreParser(rootDir);
45
+ parser.loadGitignore();
46
+ return parser;
47
+ })()
48
+ : null,
15
49
 
16
50
  // Git references - defaults to HEAD~1 (previous commit)
17
51
  baseRef: cli.getArg('base', 'HEAD~1'),
@@ -41,9 +75,75 @@ function validateConfig(config) {
41
75
  // baseRef and headRef are optional, have sensible defaults
42
76
  }
43
77
 
78
+ // Default exclude paths - comprehensive list
79
+ const DEFAULT_EXCLUDE_PATHS = [
80
+ // Build & Dependencies
81
+ 'node_modules',
82
+ 'dist',
83
+ 'build',
84
+ 'out',
85
+ '.turbo',
86
+ '.next',
87
+
88
+ // Tests (unless --include-tests)
89
+ '__tests__',
90
+ 'test',
91
+ 'tests',
92
+ '.test.ts',
93
+ '.spec.ts',
94
+ '.test.js',
95
+ '.spec.js',
96
+ '.test.tsx',
97
+ '.spec.tsx',
98
+
99
+ // Examples & Samples
100
+ 'examples',
101
+ 'samples',
102
+ 'fixtures',
103
+ 'mocks',
104
+ '__mocks__',
105
+ 'project-samples',
106
+
107
+ // Coverage & Reports
108
+ 'coverage',
109
+ 'reports',
110
+ '.nyc_output',
111
+
112
+ // Cache & Temp
113
+ '.cache',
114
+ '.temp',
115
+ '.tmp',
116
+ '.history',
117
+
118
+ // Documentation Sites
119
+ 'pages/_site',
120
+ '_site',
121
+ '.jekyll-cache',
122
+ '.jekyll-metadata',
123
+ 'vendor',
124
+ '.bundle',
125
+
126
+ // IDE & Tools
127
+ '.idea',
128
+ '.vscode',
129
+ '.github',
130
+ '.agent',
131
+ '.claude',
132
+ '.serena',
133
+
134
+ // Config-specific
135
+ 'specs',
136
+ '.sunlint-cache',
137
+ 'origin-rules',
138
+ 'arch-detect',
139
+
140
+ // Type definitions (no runtime code to analyze)
141
+ '.d.ts',
142
+ ];
143
+
44
144
  export const DEFAULT_CONFIG = {
45
145
  sourceDir: 'src',
46
- excludePaths: ['node_modules', 'dist', 'build', 'specs', 'coverage'],
146
+ excludePaths: DEFAULT_EXCLUDE_PATHS,
47
147
  baseRef: 'HEAD~1', // Previous commit
48
148
  headRef: 'HEAD', // Current commit
49
149
  maxDepth: 3,
@@ -20,7 +20,8 @@ export class ImpactAnalyzer {
20
20
  await this.methodCallGraph.initialize(
21
21
  this.absoluteSourceDir,
22
22
  this.config.excludePaths,
23
- this.config.verbose
23
+ this.config.verbose,
24
+ this.config.gitignoreParser || null
24
25
  );
25
26
 
26
27
  this.endpointDetector = new EndpointDetector(this.methodCallGraph, this.config);
@@ -69,28 +70,24 @@ export class ImpactAnalyzer {
69
70
 
70
71
  detectLogicImpact(changedSymbols) {
71
72
  const directCallers = new Set();
72
- const indirectCallers = new Set();
73
+ const allCallers = new Set();
73
74
 
74
75
  for (const symbol of changedSymbols) {
75
76
  const methodName = `${symbol.className || 'global'}.${symbol.name}`;
76
- const callers = this.methodCallGraph.findAllCallers(methodName);
77
77
 
78
- callers.forEach(caller => {
79
- directCallers.add(caller);
80
-
81
- const indirectCallersOfCaller = this.methodCallGraph.findAllCallers(caller);
82
- indirectCallersOfCaller.forEach(ic => {
83
- if (!callers.includes(ic)) {
84
- indirectCallers.add(ic);
85
- }
86
- });
87
- });
78
+ const immediateCallers = this.methodCallGraph.getCallers(methodName);
79
+ immediateCallers.forEach(caller => directCallers.add(caller));
80
+
81
+ const recursiveCallers = this.methodCallGraph.findAllCallers(methodName);
82
+ recursiveCallers.forEach(caller => allCallers.add(caller));
88
83
  }
89
84
 
85
+ const indirectCallers = Array.from(allCallers).filter(caller => !directCallers.has(caller));
86
+
90
87
  return {
91
88
  directCallers: Array.from(directCallers),
92
- indirectCallers: Array.from(indirectCallers),
93
- riskLevel: this.calculateRiskLevel(directCallers.size, indirectCallers.size),
89
+ indirectCallers: indirectCallers,
90
+ riskLevel: this.calculateRiskLevel(directCallers.size, indirectCallers.length),
94
91
  };
95
92
  }
96
93
 
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Gitignore Parser
3
+ * Reads .gitignore files and converts patterns to exclude paths
4
+ */
5
+
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import { minimatch } from 'minimatch';
9
+
10
+ export class GitignoreParser {
11
+ constructor(rootDir) {
12
+ this.rootDir = rootDir;
13
+ this.patterns = [];
14
+ this.negationPatterns = [];
15
+ }
16
+
17
+ /**
18
+ * Load patterns from .gitignore file
19
+ */
20
+ loadGitignore(gitignorePath = null) {
21
+ const ignoreFile = gitignorePath || path.join(this.rootDir, '.gitignore');
22
+
23
+ if (!fs.existsSync(ignoreFile)) {
24
+ return [];
25
+ }
26
+
27
+ const content = fs.readFileSync(ignoreFile, 'utf8');
28
+ const lines = content.split('\n');
29
+
30
+ const patterns = [];
31
+
32
+ for (const line of lines) {
33
+ const trimmed = line.trim();
34
+
35
+ // Skip empty lines and comments
36
+ if (!trimmed || trimmed.startsWith('#')) continue;
37
+
38
+ // Handle negation patterns (!)
39
+ if (trimmed.startsWith('!')) {
40
+ this.negationPatterns.push(trimmed.substring(1));
41
+ continue;
42
+ }
43
+
44
+ patterns.push(trimmed);
45
+ }
46
+
47
+ this.patterns = patterns;
48
+ return patterns;
49
+ }
50
+
51
+ /**
52
+ * Check if a file path should be excluded based on gitignore patterns
53
+ */
54
+ shouldExclude(filePath) {
55
+ // Normalize path separators
56
+ const normalizedPath = filePath.replace(/\\/g, '/');
57
+
58
+ // Check if path matches any pattern
59
+ for (const pattern of this.patterns) {
60
+ if (this.matchesPattern(normalizedPath, pattern)) {
61
+ // Check if there's a negation pattern that overrides
62
+ const isNegated = this.negationPatterns.some(negPattern =>
63
+ this.matchesPattern(normalizedPath, negPattern)
64
+ );
65
+
66
+ if (!isNegated) {
67
+ return true;
68
+ }
69
+ }
70
+ }
71
+
72
+ return false;
73
+ }
74
+
75
+ /**
76
+ * Match a file path against a gitignore pattern
77
+ */
78
+ matchesPattern(filePath, pattern) {
79
+ // Handle directory-only patterns (ending with /)
80
+ if (pattern.endsWith('/')) {
81
+ const dirPattern = pattern.slice(0, -1);
82
+ return filePath.includes(`/${dirPattern}/`) || filePath.includes(`${dirPattern}/`);
83
+ }
84
+
85
+ // Handle patterns starting with /
86
+ if (pattern.startsWith('/')) {
87
+ return minimatch(filePath, pattern.substring(1), { matchBase: true });
88
+ }
89
+
90
+ // Handle wildcards and glob patterns
91
+ if (pattern.includes('*') || pattern.includes('?') || pattern.includes('[')) {
92
+ return minimatch(filePath, pattern, { matchBase: true, dot: true });
93
+ }
94
+
95
+ // Simple substring match for paths
96
+ return filePath.includes(pattern) ||
97
+ filePath.includes(`/${pattern}/`) ||
98
+ filePath.endsWith(`/${pattern}`);
99
+ }
100
+
101
+ /**
102
+ * Get all patterns
103
+ */
104
+ getPatterns() {
105
+ return this.patterns;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Convert gitignore patterns to simple exclude path list
111
+ * for backward compatibility
112
+ */
113
+ export function gitignoreToExcludePaths(rootDir) {
114
+ const parser = new GitignoreParser(rootDir);
115
+ parser.loadGitignore();
116
+
117
+ // Extract simple path patterns (non-glob)
118
+ const simplePaths = parser.getPatterns().filter(p =>
119
+ !p.includes('*') && !p.includes('?') && !p.includes('[')
120
+ );
121
+
122
+ return simplePaths.map(p => p.replace(/^\//, '').replace(/\/$/, ''));
123
+ }