pumuki 6.3.325 → 6.3.327

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.
@@ -150,6 +150,7 @@ import {
150
150
  hasSwiftOnChangeTaskUsage,
151
151
  collectSwiftOnChangeTaskLines,
152
152
  collectSwiftOnChangeReadonlyVarLines,
153
+ collectSwiftStrongDelegateReferenceLines,
153
154
  hasSwiftOnChangeReadonlyVarUsage,
154
155
  hasSwiftOnAppearTaskUsage,
155
156
  collectSwiftOnAppearTaskLines,
@@ -1638,7 +1639,9 @@ final class CheckoutCoordinator {
1638
1639
  `;
1639
1640
 
1640
1641
  assert.equal(hasSwiftStrongDelegateReferenceUsage(positive), true);
1642
+ assert.deepEqual(collectSwiftStrongDelegateReferenceLines(positive), [3, 4]);
1641
1643
  assert.equal(hasSwiftStrongDelegateReferenceUsage(negative), false);
1644
+ assert.deepEqual(collectSwiftStrongDelegateReferenceLines(negative), []);
1642
1645
  });
1643
1646
 
1644
1647
  test('hasSwiftStrongDelegateReferenceUsage no marca propiedades no delegate', () => {
@@ -1474,6 +1474,25 @@ export const hasSwiftStrongDelegateReferenceUsage = (source: string): boolean =>
1474
1474
  });
1475
1475
  };
1476
1476
 
1477
+ export const collectSwiftStrongDelegateReferenceLines = (source: string): readonly number[] => {
1478
+ const delegatePropertyPattern =
1479
+ /\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/;
1480
+ const lines: number[] = [];
1481
+
1482
+ source.split(/\r?\n/).forEach((line, index) => {
1483
+ const sanitizedLine = stripSwiftLineForSemanticScan(line);
1484
+ if (!delegatePropertyPattern.test(sanitizedLine)) {
1485
+ return;
1486
+ }
1487
+ if (/\bweak\s+var\b/.test(sanitizedLine)) {
1488
+ return;
1489
+ }
1490
+ lines.push(index + 1);
1491
+ });
1492
+
1493
+ return sortedUniqueLines(lines);
1494
+ };
1495
+
1477
1496
  const swiftStrongSelfEscapingClosurePatterns = [
1478
1497
  /\bTask\s*(?:\([^)]*\))?\s*\{/g,
1479
1498
  /\bDispatchQueue\s*\.\s*[A-Za-z0-9_.]+\s*\.\s*async(?:After)?\s*(?:\([^)]*\))?\s*\{/g,
@@ -788,7 +788,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
788
788
  { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftOnAppearTaskUsage, locateLines: TextIOS.collectSwiftOnAppearTaskLines, primaryNode: (lines) => ({ kind: 'call', name: 'Task launched inside SwiftUI onAppear', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: .task { ... } on the view', lines }], why: 'A Task launched from onAppear is not owned by the SwiftUI view lifecycle in the same way as .task.', impact: 'Async work can outlive view disappearance or require manual cancellation, and the gate must point to the exact Task line instead of blocking the whole file.', expected_fix: 'Move the async work from .onAppear { Task { ... } } into .task { ... } so SwiftUI owns automatic cancellation. Keep onAppear only for synchronous side effects such as analytics.', 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.' },
789
789
  { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftOnChangeTaskUsage, locateLines: TextIOS.collectSwiftOnChangeTaskLines, primaryNode: (lines) => ({ kind: 'call', name: 'Task launched inside SwiftUI onChange', lines }), relatedNodes: (lines) => [{ kind: 'call', name: 'replacement: .task(id:) for value-dependent async work', lines }], why: 'A Task launched from onChange makes value-dependent async work manual instead of tying cancellation to the changing value.', impact: 'Search, load or refresh work can race after state changes because cancellation is not expressed through SwiftUI .task(id:).', expected_fix: 'Move value-dependent async work from .onChange { Task { ... } } into .task(id: value) { ... }. Keep onChange only for synchronous derivations or analytics.', 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.' },
790
790
  { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftOnChangeReadonlyVarUsage, locateLines: TextIOS.collectSwiftOnChangeReadonlyVarLines, primaryNode: (lines) => ({ kind: 'property', name: 'var declared inside SwiftUI onChange closure', lines }), relatedNodes: (lines) => [{ kind: 'property', name: 'replacement: let for read-only derived value inside onChange', lines }], why: 'A local var inside onChange hides whether the closure is deriving a read-only value or mutating state as part of a reactive update.', impact: 'Reactive closures become harder to audit because unnecessary mutability can mask accidental state changes and value-dependent side effects.', expected_fix: 'Use let for read-only derived values inside onChange. Keep var only when the local value is intentionally mutated and extract complex mutation out of the view closure.', ruleId: 'heuristics.ios.swiftui.onchange-readonly-var.ast', code: 'HEURISTICS_IOS_SWIFTUI_ONCHANGE_READONLY_VAR_AST', message: 'AST heuristic detected local var inside SwiftUI onChange; prefer let for read-only derived values.' },
791
- { 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.' },
791
+ { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftStrongDelegateReferenceUsage, locateLines: TextIOS.collectSwiftStrongDelegateReferenceLines, primaryNode: (lines) => ({ kind: 'property', name: 'strong delegate or dataSource property', lines }), relatedNodes: (lines) => [{ kind: 'property', name: 'replacement: weak var delegate/dataSource', lines }], why: 'Delegate and dataSource references usually point back to an owner or coordinator and should not be retained strongly by the callee.', impact: 'A strong delegate/dataSource property can create retain cycles between services, coordinators, view controllers or adapters, causing leaks that tests may not catch.', expected_fix: 'Declare delegate/dataSource references as weak var delegate or weak var dataSource, and keep the protocol class-bound with AnyObject when Swift requires weak storage.', 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.' },
792
792
  { 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.' },
793
793
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUnownedSelfCaptureUsage, ruleId: 'heuristics.ios.memory.unowned-self-capture.ast', code: 'HEURISTICS_IOS_MEMORY_UNOWNED_SELF_CAPTURE_AST', message: 'AST heuristic detected unowned capture in an iOS closure; use weak capture unless lifetime is explicitly guaranteed.' },
794
794
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftManualMemoryManagementUsage, locateLines: TextIOS.collectSwiftManualMemoryManagementLines, primaryNode: (lines) => ({ kind: 'call', name: 'manual ARC bypass / Core Foundation retain-release', lines }), relatedNodes: (lines) => [{ kind: 'class', name: 'replacement: ARC-owned Swift object lifetime', lines }], why: 'Manual Unmanaged or Core Foundation retain/release bypasses normal Swift ARC ownership and needs an explicit bridge boundary.', impact: 'Manual memory ownership can leak or over-release objects, and without line evidence the gate cannot identify the unsafe bridge call.', expected_fix: 'Prefer ARC-owned Swift references. If a Core Foundation bridge is unavoidable, isolate it in infrastructure with documented ownership invariants and typed wrappers.', ruleId: 'heuristics.ios.memory.manual-management.ast', code: 'HEURISTICS_IOS_MEMORY_MANUAL_MANAGEMENT_AST', message: 'AST heuristic detected manual memory management that bypasses Swift ARC.' },
@@ -70,3 +70,33 @@ test('facts barrel expone extractHeuristicFacts y permite retorno vacio sin plat
70
70
 
71
71
  assert.deepEqual(extractedFacts, []);
72
72
  });
73
+
74
+ test('extractHeuristicFacts emite strong delegate iOS con linea y remediation accionable', () => {
75
+ const params: HeuristicExtractionParams = {
76
+ facts: [
77
+ {
78
+ kind: 'FileContent',
79
+ source: 'repo',
80
+ path: 'apps/ios/Infrastructure/Permissions/SystemPermissionsService.swift',
81
+ content: `
82
+ final class SystemPermissionsService {
83
+ var delegate: PermissionsServiceDelegate?
84
+ }
85
+ `,
86
+ },
87
+ ],
88
+ detectedPlatforms: {
89
+ ios: { detected: true, confidence: 'HIGH' },
90
+ },
91
+ };
92
+
93
+ const extractedFacts = extractHeuristicFacts(params);
94
+ const match = extractedFacts.find(
95
+ (fact) => fact.ruleId === 'heuristics.ios.memory.strong-delegate.ast'
96
+ );
97
+
98
+ assert.ok(match);
99
+ assert.deepEqual(match.lines, [3]);
100
+ assert.deepEqual(match.primary_node?.lines, [3]);
101
+ assert.match(match.expected_fix ?? '', /weak var delegate/i);
102
+ });
@@ -490,14 +490,21 @@ const isPlatformPath = (platform: PreWriteSkillsPlatform, filePath: string): boo
490
490
  const normalized = normalizeChangedPath(filePath).toLowerCase();
491
491
  if (platform === 'ios') {
492
492
  return normalized.endsWith('.swift')
493
- || normalized.startsWith('apps/ios/')
494
- || normalized.startsWith('ios/');
493
+ || normalized.endsWith('.m')
494
+ || normalized.endsWith('.mm')
495
+ || normalized.endsWith('.h')
496
+ || normalized.endsWith('.xib')
497
+ || normalized.endsWith('.storyboard')
498
+ || normalized.endsWith('.xcstrings')
499
+ || normalized.endsWith('.plist');
495
500
  }
496
501
  if (platform === 'android') {
497
502
  return normalized.endsWith('.kt')
498
503
  || normalized.endsWith('.kts')
499
- || normalized.startsWith('apps/android/')
500
- || normalized.startsWith('android/');
504
+ || normalized.endsWith('.java')
505
+ || normalized.endsWith('.xml')
506
+ || normalized.endsWith('.gradle')
507
+ || normalized.endsWith('.gradle.kts');
501
508
  }
502
509
  if (platform === 'backend') {
503
510
  const isTypeScriptOrJavaScript =
@@ -669,6 +676,16 @@ const collectPreWritePlatformSkillsViolations = (params: {
669
676
  if (detectedPlatforms.length === 0) {
670
677
  return [];
671
678
  }
679
+ const effectiveChangedPaths = collectPreWriteEffectiveChangedPaths(params.repoRoot);
680
+ const isNoCodeEffectiveScope =
681
+ effectiveChangedPaths.length > 0
682
+ && !hasEffectiveChangedCodePlatforms({
683
+ changedPaths: effectiveChangedPaths,
684
+ requiredPlatforms: detectedPlatforms,
685
+ });
686
+ if (isNoCodeEffectiveScope) {
687
+ return [];
688
+ }
672
689
 
673
690
  const violations: AiGateViolation[] = [];
674
691
  const missingScopeCoverage: string[] = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.325",
3
+ "version": "6.3.327",
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": {