pumuki 6.3.293 → 6.3.295

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
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [6.3.295] - 2026-05-19
4
+
5
+ - `PUMUKI-INC-156`: staged PRE_WRITE/PRE_COMMIT no longer blocks an atomic iOS slice with broad file-level SwiftUI skill findings that expose dozens of lines without a precise AST node introduced by the diff. Precise AST/skills findings still block in every severity when they intersect changed lines; broad brownfield file debt is retained as advisory with remediation demanding line/node-level evidence or a dedicated remediation slice.
6
+
3
7
  ## [6.3.283] - 2026-05-19
4
8
 
5
9
  - `PUMUKI-INC-150`: brownfield XCTest specs that satisfy `makeSUT()` and `trackForMemoryLeaks()` are also exempt from Swift Testing migration assertion findings (`skills.ios.no-xctassert` / `skills.ios.no-xctunwrap`). These rules remain active for modern Swift Testing suites and XCTest specs without an explicit brownfield quality contract.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 6.3.291
1
+ 6.3.294
@@ -31,6 +31,10 @@ import {
31
31
  hasSwiftAdHocLoggingUsage,
32
32
  hasSwiftAlamofireUsage,
33
33
  hasSwiftEmptyCatchUsage,
34
+ collectSwiftNSErrorThrowLines,
35
+ hasSwiftNSErrorThrowUsage,
36
+ collectSwiftPackageBranchDependencyLines,
37
+ hasSwiftPackageBranchDependencyUsage,
34
38
  hasSwiftForEachIndicesUsage,
35
39
  hasSwiftForEachSelfIdentityUsage,
36
40
  collectSwiftForceCastLines,
@@ -98,9 +102,11 @@ import {
98
102
  hasSwiftNonIBOutletImplicitlyUnwrappedOptionalUsage,
99
103
  hasSwiftObservableObjectUsage,
100
104
  hasSwiftOnChangeTaskUsage,
105
+ collectSwiftOnChangeTaskLines,
101
106
  collectSwiftOnChangeReadonlyVarLines,
102
107
  hasSwiftOnChangeReadonlyVarUsage,
103
108
  hasSwiftOnAppearTaskUsage,
109
+ collectSwiftOnAppearTaskLines,
104
110
  hasSwiftOnTapGestureUsage,
105
111
  hasSwiftOperationQueueUsage,
106
112
  hasSwiftContainsUserFilterUsage,
@@ -382,6 +388,61 @@ do {
382
388
  assert.equal(hasSwiftEmptyCatchUsage(safe), false);
383
389
  });
384
390
 
391
+ test('hasSwiftNSErrorThrowUsage detecta NSError lanzado directamente y preserva errores tipados', () => {
392
+ const source = `
393
+ func map(response: HTTPURLResponse) throws {
394
+ if response.statusCode >= 500 {
395
+ throw NSError(domain: "Network", code: response.statusCode)
396
+ }
397
+ }
398
+ `;
399
+ const safe = `
400
+ enum NetworkError: Error {
401
+ case server(statusCode: Int)
402
+ }
403
+
404
+ func map(response: HTTPURLResponse) throws {
405
+ if response.statusCode >= 500 {
406
+ throw NetworkError.server(statusCode: response.statusCode)
407
+ }
408
+ }
409
+
410
+ let sample = "throw NSError(domain: code:)"
411
+ // throw NSError(domain: "Sample", code: 1)
412
+ `;
413
+
414
+ assert.equal(hasSwiftNSErrorThrowUsage(source), true);
415
+ assert.deepEqual(collectSwiftNSErrorThrowLines(source), [4]);
416
+ assert.equal(hasSwiftNSErrorThrowUsage(safe), false);
417
+ assert.deepEqual(collectSwiftNSErrorThrowLines(safe), []);
418
+ });
419
+
420
+ test('hasSwiftPackageBranchDependencyUsage detecta dependencias SwiftPM por branch y preserva versiones', () => {
421
+ const source = `
422
+ let package = Package(
423
+ dependencies: [
424
+ .package(url: "https://example.com/ui-kit.git", branch: "main"),
425
+ ]
426
+ )
427
+ `;
428
+ const safe = `
429
+ let package = Package(
430
+ dependencies: [
431
+ .package(url: "https://example.com/ui-kit.git", exact: "1.2.3"),
432
+ .package(url: "https://example.com/domain.git", from: "2.0.0"),
433
+ ]
434
+ )
435
+
436
+ let sample = ".package(url: branch:)"
437
+ // .package(url: "https://example.com/debug.git", branch: "develop")
438
+ `;
439
+
440
+ assert.equal(hasSwiftPackageBranchDependencyUsage(source), true);
441
+ assert.deepEqual(collectSwiftPackageBranchDependencyLines(source), [4]);
442
+ assert.equal(hasSwiftPackageBranchDependencyUsage(safe), false);
443
+ assert.deepEqual(collectSwiftPackageBranchDependencyLines(safe), []);
444
+ });
445
+
385
446
  test('hasSwiftNonLazyScrollForEachUsage detecta ScrollView con stack no lazy y preserva LazyVStack', () => {
386
447
  const source = `
387
448
  struct FeedView: View {
@@ -766,7 +827,9 @@ struct FeedView: View {
766
827
  `;
767
828
 
768
829
  assert.equal(hasSwiftOnAppearTaskUsage(source), true);
830
+ assert.deepEqual(collectSwiftOnAppearTaskLines(source), [8]);
769
831
  assert.equal(hasSwiftOnAppearTaskUsage(safe), false);
832
+ assert.deepEqual(collectSwiftOnAppearTaskLines(safe), []);
770
833
  });
771
834
 
772
835
  test('hasSwiftOnChangeTaskUsage detecta Task dentro de onChange y preserva task id', () => {
@@ -803,7 +866,9 @@ struct SearchView: View {
803
866
  `;
804
867
 
805
868
  assert.equal(hasSwiftOnChangeTaskUsage(source), true);
869
+ assert.deepEqual(collectSwiftOnChangeTaskLines(source), [8]);
806
870
  assert.equal(hasSwiftOnChangeTaskUsage(safe), false);
871
+ assert.deepEqual(collectSwiftOnChangeTaskLines(safe), []);
807
872
  });
808
873
 
809
874
  test('hasSwiftOnChangeReadonlyVarUsage detecta var local dentro de onChange y preserva let', () => {
@@ -698,18 +698,78 @@ export const hasSwiftEmptyCatchUsage = (source: string): boolean => {
698
698
  return /\bcatch(?:\s+(?:let|var)\s+[A-Za-z_][A-Za-z0-9_]*)?\s*\{\s*\}/.test(sanitized);
699
699
  };
700
700
 
701
+ export const collectSwiftNSErrorThrowLines = (source: string): readonly number[] => {
702
+ return collectSwiftRegexLines(source, /\bthrow\s+NSError\s*\(/g);
703
+ };
704
+
705
+ export const hasSwiftNSErrorThrowUsage = (source: string): boolean => {
706
+ return collectSwiftNSErrorThrowLines(source).length > 0;
707
+ };
708
+
709
+ export const collectSwiftPackageBranchDependencyLines = (source: string): readonly number[] => {
710
+ const matches: number[] = [];
711
+ source.split(/\r?\n/).forEach((line, index) => {
712
+ const sanitized = line
713
+ .replace(/"(?:\\.|[^"\\])*"/g, '""')
714
+ .replace(/\/\/.*$/, '');
715
+ if (/\.\s*package\s*\([^)]*\bbranch\s*:/.test(sanitized)) {
716
+ matches.push(index + 1);
717
+ }
718
+ });
719
+ return matches;
720
+ };
721
+
722
+ export const hasSwiftPackageBranchDependencyUsage = (source: string): boolean => {
723
+ return collectSwiftPackageBranchDependencyLines(source).length > 0;
724
+ };
725
+
701
726
  export const hasSwiftOnAppearTaskUsage = (source: string): boolean => {
702
- const sanitized = sanitizeSwiftSourceForMultilineRegex(source);
703
- return /\.onAppear\s*\{[\s\S]{0,500}?\bTask\s*(?:\([^)]*\))?\s*\{/.test(sanitized);
727
+ return collectSwiftOnAppearTaskLines(source).length > 0;
704
728
  };
705
729
 
706
730
  export const hasSwiftOnChangeTaskUsage = (source: string): boolean => {
707
- const sanitized = sanitizeSwiftSourceForMultilineRegex(source);
708
- return /\.onChange\s*\([^)]*\)\s*\{[\s\S]{0,500}?\bTask\s*(?:\([^)]*\))?\s*\{/.test(
709
- sanitized
710
- );
731
+ return collectSwiftOnChangeTaskLines(source).length > 0;
711
732
  };
712
733
 
734
+ const collectSwiftLifecycleTaskLines = (
735
+ source: string,
736
+ lifecyclePattern: RegExp
737
+ ): readonly number[] => {
738
+ const lines: number[] = [];
739
+ let insideLifecycle = false;
740
+ let braceDepth = 0;
741
+
742
+ source.split(/\r?\n/).forEach((rawLine, index) => {
743
+ const line = stripSwiftLineForSemanticScan(rawLine);
744
+
745
+ if (!insideLifecycle && lifecyclePattern.test(line)) {
746
+ insideLifecycle = true;
747
+ braceDepth = 0;
748
+ }
749
+
750
+ if (insideLifecycle && /\bTask\s*(?:\([^)]*\))?\s*\{/.test(line)) {
751
+ lines.push(index + 1);
752
+ }
753
+
754
+ if (insideLifecycle) {
755
+ const opened = (line.match(/\{/g) ?? []).length;
756
+ const closed = (line.match(/\}/g) ?? []).length;
757
+ braceDepth += opened - closed;
758
+ if (braceDepth <= 0) {
759
+ insideLifecycle = false;
760
+ }
761
+ }
762
+ });
763
+
764
+ return sortedUniqueLines(lines);
765
+ };
766
+
767
+ export const collectSwiftOnAppearTaskLines = (source: string): readonly number[] =>
768
+ collectSwiftLifecycleTaskLines(source, /\.onAppear\s*\{/);
769
+
770
+ export const collectSwiftOnChangeTaskLines = (source: string): readonly number[] =>
771
+ collectSwiftLifecycleTaskLines(source, /\.onChange\s*\([^)]*\)\s*\{/);
772
+
713
773
  export const collectSwiftOnChangeReadonlyVarLines = (source: string): readonly number[] => {
714
774
  const lines: number[] = [];
715
775
  let insideOnChange = false;