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 +4 -0
- package/VERSION +1 -1
- package/core/facts/detectors/text/ios.test.ts +65 -0
- package/core/facts/detectors/text/ios.ts +66 -6
- package/core/facts/detectors/typescript/index.test.ts +1115 -94
- package/core/facts/detectors/typescript/index.ts +653 -1
- package/core/facts/extractHeuristicFacts.ts +33 -2
- package/core/rules/presets/heuristics/ios.test.ts +11 -1
- package/core/rules/presets/heuristics/ios.ts +36 -0
- package/core/rules/presets/heuristics/typescript.test.ts +73 -1
- package/core/rules/presets/heuristics/typescript.ts +264 -0
- package/integrations/config/skillsDetectorRegistry.ts +90 -0
- package/integrations/git/runPlatformGate.ts +41 -3
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
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
|
-
|
|
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;
|