pumuki 6.3.239 → 6.3.241

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [6.3.241] - 2026-05-14
10
+
11
+ ### Added
12
+
13
+ - **SwiftUI `.task(id:)` parity:** `skills.ios.guideline.ios-swiftui-expert.use-task-id-for-value-dependent-tasks` now maps to an AUTO heuristic that detects `Task` launched from SwiftUI `onChange`, while preserving `.task(id:)` as the clean lifecycle-aware pattern.
14
+
9
15
  ## [6.3.127] - 2026-04-28
10
16
 
11
17
  ### Fixed
@@ -54,6 +54,7 @@ import {
54
54
  hasSwiftNonPrivateStateOwnershipUsage,
55
55
  hasSwiftNonIBOutletImplicitlyUnwrappedOptionalUsage,
56
56
  hasSwiftObservableObjectUsage,
57
+ hasSwiftOnChangeTaskUsage,
57
58
  hasSwiftOnAppearTaskUsage,
58
59
  hasSwiftOnTapGestureUsage,
59
60
  hasSwiftOperationQueueUsage,
@@ -507,6 +508,43 @@ struct FeedView: View {
507
508
  assert.equal(hasSwiftOnAppearTaskUsage(safe), false);
508
509
  });
509
510
 
511
+ test('hasSwiftOnChangeTaskUsage detecta Task dentro de onChange y preserva task id', () => {
512
+ const source = `
513
+ struct SearchView: View {
514
+ @State private var query = ""
515
+
516
+ var body: some View {
517
+ TextField("Search", text: $query)
518
+ .onChange(of: query) { _, newValue in
519
+ Task {
520
+ await viewModel.search(newValue)
521
+ }
522
+ }
523
+ }
524
+ }
525
+ `;
526
+ const safe = `
527
+ struct SearchView: View {
528
+ @State private var query = ""
529
+
530
+ var body: some View {
531
+ TextField("Search", text: $query)
532
+ .task(id: query) {
533
+ await viewModel.search(query)
534
+ }
535
+ .onChange(of: query) { _, newValue in
536
+ analytics.trackSearch(newValue)
537
+ }
538
+ let text = ".onChange(of: query) { Task { await search() } }"
539
+ // .onChange(of: query) { Task { await search() } }
540
+ }
541
+ }
542
+ `;
543
+
544
+ assert.equal(hasSwiftOnChangeTaskUsage(source), true);
545
+ assert.equal(hasSwiftOnChangeTaskUsage(safe), false);
546
+ });
547
+
510
548
  test('hasSwiftStrongDelegateReferenceUsage detecta delegates fuertes y preserva weak delegates', () => {
511
549
  const positive = `
512
550
  final class CheckoutCoordinator {
@@ -481,6 +481,13 @@ export const hasSwiftOnAppearTaskUsage = (source: string): boolean => {
481
481
  return /\.onAppear\s*\{[\s\S]{0,500}?\bTask\s*(?:\([^)]*\))?\s*\{/.test(sanitized);
482
482
  };
483
483
 
484
+ export const hasSwiftOnChangeTaskUsage = (source: string): boolean => {
485
+ const sanitized = sanitizeSwiftSourceForMultilineRegex(source);
486
+ return /\.onChange\s*\([^)]*\)\s*\{[\s\S]{0,500}?\bTask\s*(?:\([^)]*\))?\s*\{/.test(
487
+ sanitized
488
+ );
489
+ };
490
+
484
491
  export const hasSwiftStrongDelegateReferenceUsage = (source: string): boolean => {
485
492
  const delegatePropertyPattern =
486
493
  /\b(?:var|let)\s+(?:[A-Za-z_][A-Za-z0-9_]*(?:Delegate|DataSource)|delegate|dataSource)\s*:\s*(?:any\s+)?[A-Za-z_][A-Za-z0-9_]*(?:Delegate|DataSource)\b/;
@@ -647,6 +647,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
647
647
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftOperationQueueUsage, ruleId: 'heuristics.ios.operation-queue.ast', code: 'HEURISTICS_IOS_OPERATION_QUEUE_AST', message: 'AST heuristic detected OperationQueue usage.' },
648
648
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftTaskDetachedUsage, ruleId: 'heuristics.ios.task-detached.ast', code: 'HEURISTICS_IOS_TASK_DETACHED_AST', message: 'AST heuristic detected Task.detached usage.' },
649
649
  { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftOnAppearTaskUsage, ruleId: 'heuristics.ios.swiftui.onappear-task.ast', code: 'HEURISTICS_IOS_SWIFTUI_ONAPPEAR_TASK_AST', message: 'AST heuristic detected Task launched from SwiftUI onAppear; .task/.task(id:) provides lifecycle-aware cancellation.' },
650
+ { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftOnChangeTaskUsage, ruleId: 'heuristics.ios.swiftui.onchange-task.ast', code: 'HEURISTICS_IOS_SWIFTUI_ONCHANGE_TASK_AST', message: 'AST heuristic detected Task launched from SwiftUI onChange; .task(id:) provides lifecycle-aware cancellation for value-dependent async work.' },
650
651
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftStrongDelegateReferenceUsage, ruleId: 'heuristics.ios.memory.strong-delegate.ast', code: 'HEURISTICS_IOS_MEMORY_STRONG_DELEGATE_AST', message: 'AST heuristic detected a strong delegate/dataSource reference; weak delegates remain the preferred baseline to avoid retain cycles.' },
651
652
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftStrongSelfEscapingClosureUsage, ruleId: 'heuristics.ios.memory.strong-self-escaping-closure.ast', code: 'HEURISTICS_IOS_MEMORY_STRONG_SELF_ESCAPING_CLOSURE_AST', message: 'AST heuristic detected strong self capture in an escaping iOS closure; weak or unowned captures remain the preferred baseline when ownership is not explicit.' },
652
653
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftCustomSingletonUsage, ruleId: 'heuristics.ios.architecture.custom-singleton.ast', code: 'HEURISTICS_IOS_ARCHITECTURE_CUSTOM_SINGLETON_AST', message: 'AST heuristic detected a custom static shared singleton in iOS production code; dependency injection remains the preferred baseline for app-owned services.' },
@@ -3,7 +3,7 @@ import test from 'node:test';
3
3
  import { iosRules } from './ios';
4
4
 
5
5
  test('iosRules define reglas heurísticas locked para plataforma ios', () => {
6
- assert.equal(iosRules.length, 82);
6
+ assert.equal(iosRules.length, 83);
7
7
 
8
8
  const ids = iosRules.map((rule) => rule.id);
9
9
  assert.deepEqual(ids, [
@@ -18,6 +18,7 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
18
18
  'heuristics.ios.operation-queue.ast',
19
19
  'heuristics.ios.task-detached.ast',
20
20
  'heuristics.ios.swiftui.onappear-task.ast',
21
+ 'heuristics.ios.swiftui.onchange-task.ast',
21
22
  'heuristics.ios.memory.strong-delegate.ast',
22
23
  'heuristics.ios.memory.strong-self-escaping-closure.ast',
23
24
  'heuristics.ios.architecture.custom-singleton.ast',
@@ -100,6 +101,10 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
100
101
  byId.get('heuristics.ios.task-detached.ast')?.then.code,
101
102
  'HEURISTICS_IOS_TASK_DETACHED_AST'
102
103
  );
104
+ assert.equal(
105
+ byId.get('heuristics.ios.swiftui.onchange-task.ast')?.then.code,
106
+ 'HEURISTICS_IOS_SWIFTUI_ONCHANGE_TASK_AST'
107
+ );
103
108
  assert.equal(
104
109
  byId.get('heuristics.ios.logging.adhoc-print.ast')?.then.code,
105
110
  'HEURISTICS_IOS_LOGGING_ADHOC_PRINT_AST'
@@ -200,6 +200,26 @@ export const iosRules: RuleSet = [
200
200
  code: 'HEURISTICS_IOS_SWIFTUI_ONAPPEAR_TASK_AST',
201
201
  },
202
202
  },
203
+ {
204
+ id: 'heuristics.ios.swiftui.onchange-task.ast',
205
+ description:
206
+ 'Detects Task launches from SwiftUI onChange where .task(id:) should own value-dependent cancellation.',
207
+ severity: 'WARN',
208
+ platform: 'ios',
209
+ locked: true,
210
+ when: {
211
+ kind: 'Heuristic',
212
+ where: {
213
+ ruleId: 'heuristics.ios.swiftui.onchange-task.ast',
214
+ },
215
+ },
216
+ then: {
217
+ kind: 'Finding',
218
+ message:
219
+ 'AST heuristic detected Task launched from SwiftUI onChange; .task(id:) should own value-dependent cancellation.',
220
+ code: 'HEURISTICS_IOS_SWIFTUI_ONCHANGE_TASK_AST',
221
+ },
222
+ },
203
223
  {
204
224
  id: 'heuristics.ios.memory.strong-delegate.ast',
205
225
  description: 'Detects strong delegate/dataSource references in iOS production code.',
@@ -312,6 +312,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
312
312
  'ios.swiftui.onappear-task',
313
313
  ['heuristics.ios.swiftui.onappear-task.ast']
314
314
  ),
315
+ 'skills.ios.guideline.ios-swiftui-expert.use-task-id-for-value-dependent-tasks': heuristicDetector(
316
+ 'ios.swiftui.onchange-task',
317
+ ['heuristics.ios.swiftui.onchange-task.ast']
318
+ ),
315
319
  'skills.ios.no-uiscreen-main-bounds': heuristicDetector('ios.uiscreen-main-bounds', [
316
320
  'heuristics.ios.uiscreen-main-bounds.ast',
317
321
  ]),
@@ -151,7 +151,7 @@ const PLATFORM_REQUIRED_SKILLS_BUNDLES: Readonly<Record<PreWriteSkillsPlatform,
151
151
  frontend: ['frontend-guidelines'],
152
152
  };
153
153
  const PREWRITE_CRITICAL_SKILLS_RULES: Readonly<Record<PreWriteSkillsPlatform, ReadonlyArray<string>>> = {
154
- ios: ['skills.ios.critical-test-quality'],
154
+ ios: [],
155
155
  android: [],
156
156
  backend: [],
157
157
  frontend: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.239",
3
+ "version": "6.3.241",
4
4
  "description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
5
5
  "main": "index.js",
6
6
  "bin": {
package/skills.lock.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": "1.0",
3
3
  "compilerVersion": "1.0.0",
4
- "generatedAt": "2026-05-13T18:33:35.600Z",
4
+ "generatedAt": "2026-05-14T06:45:22.016Z",
5
5
  "bundles": [
6
6
  {
7
7
  "name": "android-guidelines",