pumuki-ast-hooks 5.5.50 → 5.5.52

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 (30) hide show
  1. package/docs/MCP_SERVERS.md +16 -2
  2. package/docs/RELEASE_NOTES.md +34 -0
  3. package/hooks/git-status-monitor.ts +0 -5
  4. package/hooks/notify-macos.ts +0 -1
  5. package/hooks/pre-tool-use-evidence-validator.ts +0 -1
  6. package/package.json +2 -2
  7. package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +96 -0
  8. package/scripts/hooks-system/application/services/guard/GuardConfig.js +2 -4
  9. package/scripts/hooks-system/application/services/installation/GitEnvironmentService.js +2 -20
  10. package/scripts/hooks-system/application/services/installation/McpConfigurator.js +9 -139
  11. package/scripts/hooks-system/application/services/installation/mcp/McpGlobalConfigCleaner.js +49 -0
  12. package/scripts/hooks-system/application/services/installation/mcp/McpProjectConfigWriter.js +59 -0
  13. package/scripts/hooks-system/application/services/installation/mcp/McpServerConfigBuilder.js +103 -0
  14. package/scripts/hooks-system/infrastructure/ast/ast-core.js +1 -13
  15. package/scripts/hooks-system/infrastructure/ast/ast-intelligence.js +3 -2
  16. package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +17 -9
  17. package/scripts/hooks-system/infrastructure/ast/backend/detectors/god-class-detector.js +2 -1
  18. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSCICDChecks.js +385 -0
  19. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSCICDRules.js +38 -408
  20. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSEnterpriseAnalyzer.js +397 -34
  21. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSSPMChecks.js +408 -0
  22. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSSPMRules.js +36 -442
  23. package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenExtractor.js +146 -0
  24. package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenParser.js +22 -190
  25. package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenRunner.js +62 -0
  26. package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +398 -1
  27. package/scripts/hooks-system/infrastructure/orchestration/__tests__/intelligent-audit.spec.js +0 -16
  28. package/scripts/hooks-system/infrastructure/orchestration/intelligent-audit.js +14 -25
  29. package/scripts/hooks-system/infrastructure/shell/gitflow/gitflow-enforcer.sh +20 -76
  30. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSEnterpriseChecks.js +0 -350
@@ -25,7 +25,6 @@ AUTO_MERGE_PR=${GITFLOW_AUTO_MERGE:-false}
25
25
  PR_BASE_BRANCH=${GITFLOW_PR_BASE:-develop}
26
26
  STRICT_ATOMIC=${GITFLOW_STRICT_ATOMIC:-true}
27
27
  REQUIRE_TEST_RELATIONS=${GITFLOW_REQUIRE_TESTS:-true}
28
- STRICT_CHECK=${GITFLOW_STRICT_CHECK:-false}
29
28
 
30
29
  print_section() {
31
30
  printf "${BLUE}═══════════════════════════════════════════════════════════════${NC}\n"
@@ -168,17 +167,16 @@ verify_atomic_commit() {
168
167
  return 0
169
168
  fi
170
169
 
171
- local files=()
170
+ local -a files=()
172
171
  while IFS= read -r file; do
173
- [[ -z "$file" ]] && continue
174
- files+=("$file")
172
+ [[ -n "$file" ]] && files+=("$file")
175
173
  done < <($GIT_BIN diff --name-only "${commit}^..${commit}")
176
174
  if [[ "${#files[@]}" -eq 0 ]]; then
177
175
  return 0
178
176
  fi
179
177
 
180
- local roots_list
181
- roots_list=()
178
+ local roots_list=""
179
+ local root_count=0
182
180
  for file in "${files[@]}"; do
183
181
  local root="${file%%/*}"
184
182
  if [[ "$root" == "$file" ]]; then
@@ -193,36 +191,14 @@ verify_atomic_commit() {
193
191
  ;;
194
192
  esac
195
193
 
196
- local seen=0
197
- local existing
198
- if (( ${#roots_list[@]:-0} > 0 )); then
199
- for existing in "${roots_list[@]}"; do
200
- if [[ "$existing" == "$root" ]]; then
201
- seen=1
202
- break
203
- fi
204
- done
205
- fi
206
- if [[ "$seen" -eq 0 ]]; then
207
- roots_list+=("$root")
194
+ if [[ " $roots_list " != *" $root "* ]]; then
195
+ roots_list="${roots_list}${root} "
196
+ root_count=$((root_count + 1))
208
197
  fi
209
198
  done
210
199
 
211
- local root_count=${#roots_list[@]}
212
200
  if (( root_count > 1 )); then
213
- local has_scripts=0
214
- local has_tests=0
215
- for root in "${roots_list[@]}"; do
216
- [[ "$root" == "scripts" ]] && has_scripts=1
217
- [[ "$root" == "tests" ]] && has_tests=1
218
- done
219
-
220
- if [[ $has_scripts -eq 1 && $has_tests -eq 1 && $root_count -eq 2 ]]; then
221
- printf "${GREEN}✅ Commit %s toca scripts + tests (permitido para bugfixes/features con tests).${NC}\n" "$commit"
222
- return 0
223
- fi
224
-
225
- printf "${RED}❌ Commit %s toca múltiples raíces (%s). Divide los cambios en commits atómicos.${NC}\n" "$commit" "$(printf "%s " "${roots_list[@]}")"
201
+ printf "${RED}❌ Commit %s toca múltiples raíces (%s). Divide los cambios en commits atómicos.${NC}\n" "$commit" "$roots_list"
226
202
  return 1
227
203
  fi
228
204
  if (( root_count == 0 )); then
@@ -230,7 +206,7 @@ verify_atomic_commit() {
230
206
  return 0
231
207
  fi
232
208
  local root_name
233
- for root_name in "${roots_list[@]}"; do
209
+ for root_name in $roots_list; do
234
210
  printf "${GREEN}✅ Commit %s cumple atomicidad (raíz %s).${NC}\n" "$commit" "$root_name"
235
211
  done
236
212
  return 0
@@ -250,10 +226,9 @@ verify_pending_commits_atomic() {
250
226
  return $?
251
227
  fi
252
228
 
253
- local commits=()
229
+ local -a commits=()
254
230
  while IFS= read -r commit; do
255
- [[ -z "$commit" ]] && continue
256
- commits+=("$commit")
231
+ [[ -n "$commit" ]] && commits+=("$commit")
257
232
  done < <($GIT_BIN rev-list "${base_ref}..${branch}")
258
233
  local failed=0
259
234
  for commit in "${commits[@]}"; do
@@ -384,50 +359,22 @@ cmd_check() {
384
359
  local branch
385
360
  branch=$(current_branch)
386
361
  printf "${CYAN}📍 Rama actual: %s${NC}\n" "$branch"
387
- local failed=0
388
-
389
- if [[ "${STRICT_CHECK}" == "true" ]]; then
390
- ensure_evidence_fresh || failed=1
391
- lint_hooks_system || failed=1
392
- run_mobile_checks || failed=1
393
- else
394
- ensure_evidence_fresh || true
395
- lint_hooks_system || true
396
- run_mobile_checks || true
397
- fi
362
+ ensure_evidence_fresh || true
363
+ lint_hooks_system || true
364
+ run_mobile_checks || true
398
365
  print_sync_table
399
366
  print_cleanup_candidates
400
- if [[ "${STRICT_CHECK}" == "true" ]]; then
401
- verify_atomic_commit "HEAD" || failed=1
402
- if [[ "$REQUIRE_TEST_RELATIONS" == "true" ]]; then
403
- verify_related_files_commit "HEAD" || failed=1
404
- fi
405
- if ! verify_pending_commits_atomic "$branch"; then
406
- failed=1
407
- fi
408
- if [[ "$REQUIRE_TEST_RELATIONS" == "true" ]]; then
409
- if ! verify_pending_commits_related "$branch"; then
410
- failed=1
411
- fi
412
- fi
413
- else
414
- verify_atomic_commit "HEAD" || true
415
- if [[ "$REQUIRE_TEST_RELATIONS" == "true" ]]; then
416
- verify_related_files_commit "HEAD" || true
417
- fi
367
+ verify_atomic_commit "HEAD" || true
368
+ if [[ "$REQUIRE_TEST_RELATIONS" == "true" ]]; then
369
+ verify_related_files_commit "HEAD" || true
418
370
  fi
419
371
  local pending
420
372
  pending=$(unpushed_commits "$branch")
421
373
  if [[ "$pending" != "0" ]]; then
422
374
  printf "${YELLOW}⚠️ Commits sin subir (${pending}). Ejecuta git push.${NC}\n"
423
- if [[ "${STRICT_CHECK}" == "true" ]]; then
424
- failed=1
425
- fi
426
375
  else
427
376
  printf "${GREEN}✅ No hay commits pendientes de push.${NC}\n"
428
377
  fi
429
-
430
- return $failed
431
378
  }
432
379
 
433
380
  cmd_cycle() {
@@ -580,7 +527,6 @@ main() {
580
527
  ;;
581
528
  esac
582
529
  }
583
-
584
530
  is_test_file() {
585
531
  local file="$1"
586
532
  case "$file" in
@@ -681,14 +627,12 @@ verify_pending_commits_related() {
681
627
  local base_ref="origin/${branch}"
682
628
 
683
629
  if ! $GIT_BIN show-ref --verify --quiet "refs/remotes/origin/${branch}"; then
684
- verify_related_files_commit "HEAD"
685
- return $?
630
+ return verify_related_files_commit "HEAD"
686
631
  fi
687
632
 
688
- local commits=()
633
+ local -a commits=()
689
634
  while IFS= read -r commit; do
690
- [[ -z "$commit" ]] && continue
691
- commits+=("$commit")
635
+ [[ -n "$commit" ]] && commits+=("$commit")
692
636
  done < <($GIT_BIN rev-list "${base_ref}..${branch}")
693
637
  local failed=0
694
638
  local commit
@@ -1,350 +0,0 @@
1
- function analyzeSwiftModerno({ content, functions = [], filePath, addFinding }) {
2
- if (content.includes('completion:') && !content.includes('async ')) {
3
- addFinding('ios.async_await_missing', 'medium', filePath, 1,
4
- 'Using completion handlers instead of async/await (Swift 5.9+ required)');
5
- }
6
-
7
- const taskCount = (content.match(/\bTask\s*\{/g) || []).length;
8
- if (taskCount > 3 && !content.includes('TaskGroup')) {
9
- addFinding('ios.structured_concurrency_missing', 'medium', filePath, 1,
10
- `Multiple Task blocks (${taskCount}) without TaskGroup - use structured concurrency`);
11
- }
12
-
13
- if (content.includes('actor ') && !content.includes(': Sendable')) {
14
- addFinding('ios.sendable_missing', 'low', filePath, 1,
15
- 'Actor should conform to Sendable protocol for thread-safe types');
16
- }
17
-
18
- if (content.includes('func ') && content.includes('-> View') && !content.includes('some View')) {
19
- addFinding('ios.opaque_types_missing', 'low', filePath, 1,
20
- 'Use "some View" instead of explicit View protocol return');
21
- }
22
-
23
- if (content.includes('UIViewController') && !content.includes('@State') && !content.includes('@Binding') && !content.includes('@ObservedObject')) {
24
- addFinding('ios.property_wrappers_missing', 'info', filePath, 1,
25
- 'Consider using SwiftUI property wrappers (@State, @Binding, @ObservedObject) for state management');
26
- }
27
-
28
- const extractedFunctions = parser.extractFunctions ? parser.extractFunctions({ parsed: true }) || [] : [];
29
- extractedFunctions.forEach(fn => {
30
- if (fn.name.includes('Array') || fn.name.includes('Collection')) {
31
- if (!content.includes('<T>') && !content.includes('<Element>')) {
32
- addFinding('ios.generics_missing', 'low', filePath, fn.line,
33
- `Function ${fn.name} should use generics for type safety`);
34
- }
35
- }
36
- });
37
- }
38
-
39
- async function analyzeSwiftUI({ usesSwiftUI, usesUIKit, content, classes, filePath, addFinding }) {
40
- if (usesUIKit && !usesSwiftUI) {
41
- addFinding('ios.swiftui_first', 'medium', filePath, 1,
42
- 'Consider migrating to SwiftUI for new views (UIKit only when strictly necessary)');
43
- }
44
-
45
- if (usesSwiftUI) {
46
- if (!content.includes('@State')) {
47
- addFinding('ios.state_local_missing', 'info', filePath, 1,
48
- 'SwiftUI view without @State - consider if local state is needed');
49
- }
50
-
51
- if (content.includes('ObservableObject') && !content.includes('@StateObject')) {
52
- addFinding('ios.stateobject_missing', 'high', filePath, 1,
53
- 'ObservableObject should be owned with @StateObject, not @ObservedObject');
54
- }
55
-
56
- if (content.includes('class') && content.includes('ObservableObject') && !content.includes('@EnvironmentObject')) {
57
- addFinding('ios.environmentobject_missing', 'info', filePath, 1,
58
- 'Consider using @EnvironmentObject for dependency injection in SwiftUI');
59
- }
60
-
61
- if (content.includes('.frame(') && content.includes('CGRect(')) {
62
- addFinding('ios.declarativo_missing', 'medium', filePath, 1,
63
- 'Using imperative CGRect in SwiftUI - use declarative .frame() modifiers');
64
- }
65
-
66
- const geometryReaderCount = (content.match(/GeometryReader/g) || []).length;
67
- if (geometryReaderCount > 2) {
68
- addFinding('ios.geometryreader_moderation', 'medium', filePath, 1,
69
- `Excessive GeometryReader usage (${geometryReaderCount}x) - use only when necessary`);
70
- }
71
- }
72
- }
73
-
74
- async function analyzeUIKit({ classes, content, filePath, addFinding }) {
75
- classes.forEach(cls => {
76
- if (cls.name.includes('ViewController')) {
77
- const linesCount = cls.substructure.length * 10;
78
- if (linesCount > 300) {
79
- addFinding('ios.massive_viewcontrollers', 'high', filePath, cls.line,
80
- `Massive ViewController ${cls.name} (~${linesCount} lines) - break down into smaller components`);
81
- }
82
-
83
- if (!content.includes('ViewModel')) {
84
- addFinding('ios.uikit.viewmodel_delegation', 'medium', filePath, cls.line,
85
- `ViewController ${cls.name} should delegate logic to ViewModel (MVVM pattern)`);
86
- }
87
- }
88
- });
89
-
90
- if (filePath.endsWith('.swift') && !filePath.includes('analyzer') && !filePath.includes('detector')) {
91
- if (content.includes('storyboard') || content.includes('.xib') || content.includes('.nib')) {
92
- addFinding('ios.storyboards', 'high', filePath, 1,
93
- 'Storyboard/XIB detected - use programmatic UI for better version control');
94
- }
95
- }
96
- }
97
-
98
- async function analyzeProtocolOriented({ protocols, content, filePath, addFinding }) {
99
- if (protocols.length > 0 && !content.includes('extension ')) {
100
- addFinding('ios.pop.missing_extensions', 'low', filePath, 1,
101
- 'Protocols detected but no extensions - consider protocol extensions for default implementations');
102
- }
103
-
104
- if (content.includes('class ') && content.includes(': ')) {
105
- const inheritanceCount = (content.match(/class\s+\w+\s*:\s*\w+/g) || []).length;
106
- if (inheritanceCount > 2) {
107
- addFinding('ios.pop.missing_composition_over_inheritance', 'medium', filePath, 1,
108
- `Excessive class inheritance (${inheritanceCount}x) - prefer protocol composition`);
109
- }
110
- }
111
- }
112
-
113
- async function analyzeValueTypes({ classes, content, filePath, addFinding }) {
114
- classes.forEach(cls => {
115
- if (!cls.inheritedTypes.length && !content.includes('ObservableObject')) {
116
- addFinding('ios.values.classes_instead_structs', 'medium', filePath, cls.line,
117
- `Class ${cls.name} without inheritance - consider struct for value semantics`);
118
- }
119
- });
120
-
121
- const varCount = (content.match(/\bvar\s+/g) || []).length;
122
- const letCount = (content.match(/\blet\s+/g) || []).length;
123
- if (varCount > letCount) {
124
- addFinding('ios.values.mutability', 'low', filePath, 1,
125
- `More var (${varCount}) than let (${letCount}) - prefer immutability`);
126
- }
127
- }
128
-
129
- function analyzeMemoryManagement({ content, filePath, addFinding }) {
130
- const closureMatches = content.match(/\{\s*\[/g);
131
- const weakSelfMatches = content.match(/\[weak self\]/g);
132
- if (closureMatches && closureMatches.length > (weakSelfMatches?.length || 0)) {
133
- addFinding('ios.memory.missing_weak_self', 'high', filePath, 1,
134
- 'Closures without [weak self] - potential retain cycles');
135
- }
136
-
137
- if (content.includes('self.') && content.includes('{') && !content.includes('[weak self]')) {
138
- addFinding('ios.memory.retain_cycles', 'high', filePath, 1,
139
- 'Potential retain cycle - closure captures self without [weak self]');
140
- }
141
-
142
- if (content.includes('class ') && !content.includes('deinit')) {
143
- addFinding('ios.memory.missing_deinit', 'low', filePath, 1,
144
- 'Class without deinit - consider adding for cleanup verification');
145
- }
146
- }
147
-
148
- function analyzeOptionals({ content, filePath, addFinding }) {
149
- const forceUnwraps = content.match(/(\w+)\s*!/g);
150
- if (forceUnwraps && forceUnwraps.length > 0) {
151
- const nonIBOutlets = forceUnwraps.filter(match => !content.includes(`@IBOutlet`));
152
- if (nonIBOutlets.length > 0) {
153
- addFinding('ios.force_unwrapping', 'high', filePath, 1,
154
- `Force unwrapping (!) detected ${nonIBOutlets.length}x - use if let or guard let`);
155
- }
156
- }
157
-
158
- const ifLetCount = (content.match(/if\s+let\s+/g) || []).length;
159
- const guardLetCount = (content.match(/guard\s+let\s+/g) || []).length;
160
- if (ifLetCount === 0 && guardLetCount === 0 && content.includes('?')) {
161
- addFinding('ios.optionals.optional_binding', 'medium', filePath, 1,
162
- 'Optionals present but no optional binding - use if let or guard let');
163
- }
164
-
165
- if (content.includes('?') && !content.includes('??')) {
166
- addFinding('ios.optionals.missing_nil_coalescing', 'info', filePath, 1,
167
- 'Consider using nil coalescing operator (??) for default values');
168
- }
169
- }
170
-
171
- async function analyzeDependencyInjection({ classes, content, filePath, addFinding }) {
172
- if (content.includes('.shared') || content.includes('static let shared')) {
173
- addFinding('ios.di.singleton_usage', 'high', filePath, 1,
174
- 'Singleton detected - use dependency injection instead');
175
- }
176
-
177
- classes.forEach(cls => {
178
- if (cls.name.includes('ViewModel') || cls.name.includes('Service')) {
179
- const hasInit = content.includes('init(');
180
- if (!hasInit) {
181
- addFinding('ios.di.missing_protocol_injection', 'medium', filePath, cls.line,
182
- `${cls.name} should inject dependencies via initializer`);
183
- }
184
- }
185
- });
186
-
187
- if (content.includes('init(') && content.match(/init\([^)]{50,}\)/)) {
188
- addFinding('ios.di.missing_factory', 'low', filePath, 1,
189
- 'Complex initialization - consider factory pattern');
190
- }
191
- }
192
-
193
- async function analyzeNetworking({ content, filePath, addFinding }) {
194
- if (String(filePath || '').endsWith('/Package.swift') || String(filePath || '').endsWith('Package.swift')) {
195
- return;
196
- }
197
- if (!content.includes('URLSession') && !content.includes('Alamofire')) {
198
- if (content.includes('http://') || content.includes('https://')) {
199
- addFinding('ios.networking.missing_urlsession', 'high', filePath, 1,
200
- 'Network URLs detected but no URLSession/Alamofire usage');
201
- }
202
- }
203
-
204
- if (content.includes('URLSession') && content.includes('completionHandler:') && !content.includes('async')) {
205
- addFinding('ios.networking.completion_handlers_instead_async', 'medium', filePath, 1,
206
- 'Using completion handlers with URLSession - migrate to async/await');
207
- }
208
-
209
- if (content.includes('JSONSerialization') && !content.includes('Codable')) {
210
- addFinding('ios.networking.missing_codable', 'medium', filePath, 1,
211
- 'Manual JSON parsing - use Codable for type safety');
212
- }
213
-
214
- if (content.includes('URLSession') && !content.includes('NetworkError')) {
215
- addFinding('ios.networking.missing_error_handling', 'high', filePath, 1,
216
- 'Network code without custom NetworkError enum');
217
- }
218
-
219
- const hasSSLPinningImplementation =
220
- content.includes('serverTrustPolicy') ||
221
- content.includes('pinning') ||
222
- (content.includes('URLSessionDelegate') && content.includes('URLAuthenticationChallenge'));
223
-
224
- if (content.includes('URLSession') && !hasSSLPinningImplementation) {
225
- addFinding('ios.networking.missing_ssl_pinning', 'medium', filePath, 1,
226
- 'Consider SSL pinning for high-security apps');
227
- }
228
-
229
- if (content.includes('URLSession') && !content.includes('retry')) {
230
- addFinding('ios.networking.missing_retry', 'low', filePath, 1,
231
- 'Network requests without retry logic');
232
- }
233
- }
234
-
235
- async function analyzePersistence({ content, filePath, addFinding }) {
236
- if (content.includes('UserDefaults') && (content.includes('password') || content.includes('token') || content.includes('auth'))) {
237
- addFinding('ios.persistence.userdefaults_sensitive', 'critical', filePath, 1,
238
- 'Sensitive data in UserDefaults - use Keychain instead');
239
- }
240
-
241
- if ((content.includes('password') || content.includes('token')) && !content.includes('Keychain') && !content.includes('Security')) {
242
- addFinding('ios.persistence.missing_keychain', 'critical', filePath, 1,
243
- 'Sensitive data detected but no Keychain usage');
244
- }
245
-
246
- if (content.includes('NSManagedObjectContext') && content.includes('.main')) {
247
- addFinding('ios.persistence.core_data_on_main', 'high', filePath, 1,
248
- 'Core Data operations on main thread - use background context');
249
- }
250
-
251
- if (content.includes('NSPersistentContainer') && !content.includes('NSMigrationManager')) {
252
- addFinding('ios.persistence.missing_migration', 'medium', filePath, 1,
253
- 'Core Data without migration strategy');
254
- }
255
- }
256
-
257
- function analyzeCombine({ content, filePath, addFinding }) {
258
- if (content.includes('.sink(') && !content.includes('AnyCancellable')) {
259
- addFinding('ios.combine.missing_cancellables', 'high', filePath, 1,
260
- 'Combine sink without storing AnyCancellable - memory leak');
261
- }
262
-
263
- if (content.includes('@Published') && !content.includes('import Combine')) {
264
- addFinding('ios.combine.published_without_combine', 'high', filePath, 1,
265
- '@Published used but Combine not imported');
266
- }
267
-
268
- if (content.includes('.sink(') && !content.includes('receiveCompletion')) {
269
- addFinding('ios.combine.error_handling', 'medium', filePath, 1,
270
- 'Combine subscriber without error handling (receiveCompletion)');
271
- }
272
-
273
- if (content.includes('Future<') && !content.includes('async')) {
274
- addFinding('ios.combine.prefer_async_await', 'low', filePath, 1,
275
- 'Combine Future for single value - consider async/await instead');
276
- }
277
- }
278
-
279
- function analyzeConcurrency({ content, filePath, addFinding }) {
280
- if (content.includes('DispatchQueue') && !content.includes('async func')) {
281
- addFinding('ios.concurrency.dispatchqueue_old', 'medium', filePath, 1,
282
- 'Using DispatchQueue - prefer async/await for new code');
283
- }
284
-
285
- if (content.includes('DispatchQueue.main') && content.includes('UI')) {
286
- addFinding('ios.concurrency.missing_mainactor', 'medium', filePath, 1,
287
- 'Manual main thread dispatch - use @MainActor annotation');
288
- }
289
-
290
- if (content.includes('Task {') && !content.includes('.cancel()') && !content.includes('Task.isCancelled')) {
291
- addFinding('ios.concurrency.task_cancellation', 'low', filePath, 1,
292
- 'Task without cancellation handling');
293
- }
294
-
295
- if (content.includes('var ') && content.includes('queue') && !content.includes('actor')) {
296
- addFinding('ios.concurrency.actor_missing', 'medium', filePath, 1,
297
- 'Manual synchronization with queue - consider actor for thread safety');
298
- }
299
- }
300
-
301
- function analyzeTesting({ content, filePath, addFinding }) {
302
- if (filePath.includes('Test') && !content.includes('XCTest') && !content.includes('Quick')) {
303
- addFinding('ios.testing.missing_xctest', 'high', filePath, 1,
304
- 'Test file without XCTest or Quick import');
305
- }
306
-
307
- if (filePath.includes('Test') && !content.includes('makeSUT') && content.includes('func test')) {
308
- addFinding('ios.testing.missing_makesut', 'medium', filePath, 1,
309
- 'Test without makeSUT pattern - centralize system under test creation');
310
- }
311
-
312
- if (filePath.includes('Test') && !content.includes('trackForMemoryLeaks') && content.includes('class')) {
313
- addFinding('ios.testing.missing_memory_leak_tracking', 'medium', filePath, 1,
314
- 'Test without trackForMemoryLeaks helper');
315
- }
316
-
317
- if (filePath.includes('Test') && content.includes('init(') && !content.includes('Protocol')) {
318
- addFinding('ios.testing.concrete_dependencies', 'medium', filePath, 1,
319
- 'Test using concrete dependencies - inject protocols for testability');
320
- }
321
- }
322
-
323
- function analyzeUITesting({ content, filePath, addFinding }) {
324
- if (filePath.includes('UITest') && !content.includes('XCTest')) {
325
- addFinding('ios.uitesting.missing_xctest', 'medium', filePath, 1,
326
- 'UI Test without XCTest import');
327
- }
328
-
329
- if (filePath.includes('UITest') && !content.includes('XCUIApplication')) {
330
- addFinding('ios.uitesting.missing_application_launch', 'medium', filePath, 1,
331
- 'UI Test missing XCUIApplication launch');
332
- }
333
- }
334
-
335
- module.exports = {
336
- analyzeSwiftModerno,
337
- analyzeSwiftUI,
338
- analyzeUIKit,
339
- analyzeProtocolOriented,
340
- analyzeValueTypes,
341
- analyzeMemoryManagement,
342
- analyzeOptionals,
343
- analyzeDependencyInjection,
344
- analyzeNetworking,
345
- analyzePersistence,
346
- analyzeCombine,
347
- analyzeConcurrency,
348
- analyzeTesting,
349
- analyzeUITesting,
350
- };