pumuki 6.3.240 → 6.3.242

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,18 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [6.3.242] - 2026-05-14
10
+
11
+ ### Added
12
+
13
+ - **SwiftUI Observable shared-state parity:** `skills.ios.guideline.ios-swiftui-expert.use-observable-for-shared-state-with-mainactor-if-not-using-default-ac` now maps to the existing AUTO `ObservableObject` heuristic, converting the shared-state migration guideline into runtime evidence without introducing actor-isolation false positives.
14
+
15
+ ## [6.3.241] - 2026-05-14
16
+
17
+ ### Added
18
+
19
+ - **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.
20
+
9
21
  ## [6.3.127] - 2026-04-28
10
22
 
11
23
  ### 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.',
@@ -201,6 +201,8 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
201
201
  'skills.ios.no-observable-object': heuristicDetector('ios.observable-object', [
202
202
  'heuristics.ios.observable-object.ast',
203
203
  ]),
204
+ 'skills.ios.guideline.ios-swiftui-expert.use-observable-for-shared-state-with-mainactor-if-not-using-default-ac':
205
+ heuristicDetector('ios.observable-object', ['heuristics.ios.observable-object.ast']),
204
206
  'skills.ios.no-legacy-swiftui-observable-wrapper': heuristicDetector(
205
207
  'ios.legacy-swiftui-observable-wrapper',
206
208
  ['heuristics.ios.legacy-swiftui-observable-wrapper.ast']
@@ -312,6 +314,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
312
314
  'ios.swiftui.onappear-task',
313
315
  ['heuristics.ios.swiftui.onappear-task.ast']
314
316
  ),
317
+ 'skills.ios.guideline.ios-swiftui-expert.use-task-id-for-value-dependent-tasks': heuristicDetector(
318
+ 'ios.swiftui.onchange-task',
319
+ ['heuristics.ios.swiftui.onchange-task.ast']
320
+ ),
315
321
  'skills.ios.no-uiscreen-main-bounds': heuristicDetector('ios.uiscreen-main-bounds', [
316
322
  'heuristics.ios.uiscreen-main-bounds.ast',
317
323
  ]),
@@ -253,6 +253,13 @@ const normalizeKnownRuleTarget = (
253
253
  if (includes('observableobject') || includes('observable object')) {
254
254
  return 'skills.ios.no-observable-object';
255
255
  }
256
+ if (
257
+ (includes('@observable') || includes('observable')) &&
258
+ includes('shared state') &&
259
+ includes('mainactor')
260
+ ) {
261
+ return 'skills.ios.guideline.ios-swiftui-expert.use-observable-for-shared-state-with-mainactor-if-not-using-default-ac';
262
+ }
256
263
  if (
257
264
  includes('observedobject') ||
258
265
  (includes('legacy') && includes('stateobject')) ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.240",
3
+ "version": "6.3.242",
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-14T07:00:57.248Z",
5
5
  "bundles": [
6
6
  {
7
7
  "name": "android-guidelines",
@@ -8620,7 +8620,7 @@
8620
8620
  "name": "ios-swiftui-expert-guidelines",
8621
8621
  "version": "1.0.0",
8622
8622
  "source": "file:vendor/skills/swiftui-expert-skill/SKILL.md",
8623
- "hash": "4ea50faf9594602eefa6711b818cf4badeb2014ca64eaf00cb2f98f3e11a55f0",
8623
+ "hash": "eec9633ddf388ee680cf4bd74c4e52f9112d1925bea59f50d482c47d1aba59f4",
8624
8624
  "rules": [
8625
8625
  {
8626
8626
  "id": "skills.ios.guideline.ios-swiftui-expert.action-handlers-should-reference-methods-not-contain-inline-logic",
@@ -8895,7 +8895,7 @@
8895
8895
  "sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
8896
8896
  "confidence": "MEDIUM",
8897
8897
  "locked": true,
8898
- "evaluationMode": "DECLARATIVE",
8898
+ "evaluationMode": "AUTO",
8899
8899
  "origin": "core"
8900
8900
  },
8901
8901
  {