pumuki 6.3.218 → 6.3.219
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/core/facts/detectors/text/ios.test.ts +21 -0
- package/core/facts/detectors/text/ios.ts +11 -0
- package/core/facts/extractHeuristicFacts.ts +1 -0
- package/core/rules/presets/heuristics/ios.ts +19 -0
- package/integrations/config/skillsDetectorRegistry.ts +4 -0
- package/integrations/config/skillsMarkdownRules.ts +7 -0
- package/package.json +1 -1
- package/skills.lock.json +3 -3
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
hasSwiftNSManagedObjectBoundaryUsage,
|
|
44
44
|
hasSwiftNSManagedObjectStateLeakUsage,
|
|
45
45
|
hasSwiftNavigationViewUsage,
|
|
46
|
+
hasSwiftNonPrivateStateOwnershipUsage,
|
|
46
47
|
hasSwiftNonIBOutletImplicitlyUnwrappedOptionalUsage,
|
|
47
48
|
hasSwiftObservableObjectUsage,
|
|
48
49
|
hasSwiftOnAppearTaskUsage,
|
|
@@ -928,6 +929,26 @@ struct ContentView: View {
|
|
|
928
929
|
assert.equal(hasSwiftLegacySwiftUiObservableWrapperUsage(modernWrapper), false);
|
|
929
930
|
});
|
|
930
931
|
|
|
932
|
+
test('hasSwiftNonPrivateStateOwnershipUsage detecta @State y @StateObject no privados', () => {
|
|
933
|
+
const source = `
|
|
934
|
+
struct DashboardView: View {
|
|
935
|
+
@State var query = ""
|
|
936
|
+
@StateObject var viewModel = DashboardViewModel()
|
|
937
|
+
}
|
|
938
|
+
`;
|
|
939
|
+
const safe = `
|
|
940
|
+
struct DashboardView: View {
|
|
941
|
+
@State private var query = ""
|
|
942
|
+
@StateObject private var viewModel = DashboardViewModel()
|
|
943
|
+
let text = "@State var query = \\"\\""
|
|
944
|
+
// @State var query = ""
|
|
945
|
+
}
|
|
946
|
+
`;
|
|
947
|
+
|
|
948
|
+
assert.equal(hasSwiftNonPrivateStateOwnershipUsage(source), true);
|
|
949
|
+
assert.equal(hasSwiftNonPrivateStateOwnershipUsage(safe), false);
|
|
950
|
+
});
|
|
951
|
+
|
|
931
952
|
test('hasSwiftPassedValueStateWrapperUsage detecta valores inyectados guardados como @State o @StateObject', () => {
|
|
932
953
|
const invalidOwnership = `
|
|
933
954
|
struct DetailView: View {
|
|
@@ -860,6 +860,17 @@ export const hasSwiftLegacySwiftUiObservableWrapperUsage = (source: string): boo
|
|
|
860
860
|
return hasSwiftSanitizedRegexMatch(source, /@\s*(?:StateObject|ObservedObject)\b/);
|
|
861
861
|
};
|
|
862
862
|
|
|
863
|
+
export const hasSwiftNonPrivateStateOwnershipUsage = (source: string): boolean => {
|
|
864
|
+
return source.split(/\r?\n/).some((line) => {
|
|
865
|
+
const sanitizedLine = stripSwiftLineForSemanticScan(line);
|
|
866
|
+
return (
|
|
867
|
+
/@\s*(?:State|StateObject)\b/.test(sanitizedLine) &&
|
|
868
|
+
/\bvar\b/.test(sanitizedLine) &&
|
|
869
|
+
!/\bprivate\b/.test(sanitizedLine)
|
|
870
|
+
);
|
|
871
|
+
});
|
|
872
|
+
};
|
|
873
|
+
|
|
863
874
|
const hasSwiftPassedValueWrapperInitialization = (
|
|
864
875
|
source: string,
|
|
865
876
|
options: {
|
|
@@ -675,6 +675,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
675
675
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftAssumeIsolatedUsage, ruleId: 'heuristics.ios.assume-isolated.ast', code: 'HEURISTICS_IOS_ASSUME_ISOLATED_AST', message: 'AST heuristic detected assumeIsolated usage.' },
|
|
676
676
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftObservableObjectUsage, ruleId: 'heuristics.ios.observable-object.ast', code: 'HEURISTICS_IOS_OBSERVABLE_OBJECT_AST', message: 'AST heuristic detected ObservableObject usage.' },
|
|
677
677
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftLegacySwiftUiObservableWrapperUsage, ruleId: 'heuristics.ios.legacy-swiftui-observable-wrapper.ast', code: 'HEURISTICS_IOS_LEGACY_SWIFTUI_OBSERVABLE_WRAPPER_AST', message: 'AST heuristic detected @StateObject/@ObservedObject usage in a modern SwiftUI path.' },
|
|
678
|
+
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNonPrivateStateOwnershipUsage, ruleId: 'heuristics.ios.swiftui.non-private-state-ownership.ast', code: 'HEURISTICS_IOS_SWIFTUI_NON_PRIVATE_STATE_OWNERSHIP_AST', message: 'AST heuristic detected @State/@StateObject without private visibility; SwiftUI owned state should be private.' },
|
|
678
679
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftPassedValueStateWrapperUsage, ruleId: 'heuristics.ios.passed-value-state-wrapper.ast', code: 'HEURISTICS_IOS_PASSED_VALUE_STATE_WRAPPER_AST', message: 'AST heuristic detected a passed value stored as @State/@StateObject via init wrapper ownership.' },
|
|
679
680
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForEachIndicesUsage, ruleId: 'heuristics.ios.foreach-indices.ast', code: 'HEURISTICS_IOS_FOREACH_INDICES_AST', message: 'AST heuristic detected ForEach(...indices...) usage where stable element identity may be preferred.' },
|
|
680
681
|
{ platform: 'ios', pathCheck: isIOSApplicationOrPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftContainsUserFilterUsage, ruleId: 'heuristics.ios.contains-user-filter.ast', code: 'HEURISTICS_IOS_CONTAINS_USER_FILTER_AST', message: 'AST heuristic detected contains() in a user-facing filter where localizedStandardContains() may be preferred.' },
|
|
@@ -730,6 +730,25 @@ export const iosRules: RuleSet = [
|
|
|
730
730
|
code: 'HEURISTICS_IOS_LEGACY_SWIFTUI_OBSERVABLE_WRAPPER_AST',
|
|
731
731
|
},
|
|
732
732
|
},
|
|
733
|
+
{
|
|
734
|
+
id: 'heuristics.ios.swiftui.non-private-state-ownership.ast',
|
|
735
|
+
description: 'Detects @State/@StateObject declarations without private visibility in SwiftUI presentation code.',
|
|
736
|
+
severity: 'WARN',
|
|
737
|
+
platform: 'ios',
|
|
738
|
+
locked: true,
|
|
739
|
+
when: {
|
|
740
|
+
kind: 'Heuristic',
|
|
741
|
+
where: {
|
|
742
|
+
ruleId: 'heuristics.ios.swiftui.non-private-state-ownership.ast',
|
|
743
|
+
},
|
|
744
|
+
},
|
|
745
|
+
then: {
|
|
746
|
+
kind: 'Finding',
|
|
747
|
+
message:
|
|
748
|
+
'AST heuristic detected @State/@StateObject without private visibility; SwiftUI owned state should be private.',
|
|
749
|
+
code: 'HEURISTICS_IOS_SWIFTUI_NON_PRIVATE_STATE_OWNERSHIP_AST',
|
|
750
|
+
},
|
|
751
|
+
},
|
|
733
752
|
{
|
|
734
753
|
id: 'heuristics.ios.passed-value-state-wrapper.ast',
|
|
735
754
|
description: 'Detects passed values stored as @State or @StateObject through init ownership in SwiftUI production code.',
|
|
@@ -201,6 +201,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
201
201
|
'ios.legacy-swiftui-observable-wrapper',
|
|
202
202
|
['heuristics.ios.legacy-swiftui-observable-wrapper.ast']
|
|
203
203
|
),
|
|
204
|
+
'skills.ios.guideline.ios-swiftui-expert.always-mark-state-and-stateobject-as-private-makes-dependencies-clear': heuristicDetector(
|
|
205
|
+
'ios.swiftui.non-private-state-ownership',
|
|
206
|
+
['heuristics.ios.swiftui.non-private-state-ownership.ast']
|
|
207
|
+
),
|
|
204
208
|
'skills.ios.no-passed-value-state-wrapper': heuristicDetector('ios.passed-value-state-wrapper', [
|
|
205
209
|
'heuristics.ios.passed-value-state-wrapper.ast',
|
|
206
210
|
]),
|
|
@@ -260,6 +260,13 @@ const normalizeKnownRuleTarget = (
|
|
|
260
260
|
) {
|
|
261
261
|
return 'skills.ios.no-legacy-swiftui-observable-wrapper';
|
|
262
262
|
}
|
|
263
|
+
if (
|
|
264
|
+
includes('state and stateobject as private') ||
|
|
265
|
+
includes('stateobject as private') ||
|
|
266
|
+
(includes('mark state') && includes('private'))
|
|
267
|
+
) {
|
|
268
|
+
return 'skills.ios.guideline.ios-swiftui-expert.always-mark-state-and-stateobject-as-private-makes-dependencies-clear';
|
|
269
|
+
}
|
|
263
270
|
if (
|
|
264
271
|
includes('passed values as state') ||
|
|
265
272
|
includes('passed values as state or stateobject') ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.219",
|
|
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-
|
|
4
|
+
"generatedAt": "2026-05-13T15:50:45.972Z",
|
|
5
5
|
"bundles": [
|
|
6
6
|
{
|
|
7
7
|
"name": "android-guidelines",
|
|
@@ -8632,7 +8632,7 @@
|
|
|
8632
8632
|
"name": "ios-swiftui-expert-guidelines",
|
|
8633
8633
|
"version": "1.0.0",
|
|
8634
8634
|
"source": "file:vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8635
|
-
"hash": "
|
|
8635
|
+
"hash": "b936508c5fea766c83bca01087374a8c0281ba86c4ee022d43db316b89c07cd6",
|
|
8636
8636
|
"rules": [
|
|
8637
8637
|
{
|
|
8638
8638
|
"id": "skills.ios.guideline.ios-swiftui-expert.always-mark-state-and-stateobject-as-private-makes-dependencies-clear",
|
|
@@ -8643,7 +8643,7 @@
|
|
|
8643
8643
|
"sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8644
8644
|
"confidence": "MEDIUM",
|
|
8645
8645
|
"locked": true,
|
|
8646
|
-
"evaluationMode": "
|
|
8646
|
+
"evaluationMode": "AUTO",
|
|
8647
8647
|
"origin": "core"
|
|
8648
8648
|
},
|
|
8649
8649
|
{
|