pumuki 6.3.225 → 6.3.227
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 +65 -0
- package/core/facts/detectors/text/ios.ts +15 -0
- package/core/facts/extractHeuristicFacts.ts +2 -0
- package/core/rules/presets/heuristics/ios.ts +38 -0
- package/integrations/config/skillsDetectorRegistry.ts +8 -0
- package/integrations/config/skillsMarkdownRules.ts +15 -1
- package/package.json +1 -1
- package/skills.lock.json +50 -2
|
@@ -37,6 +37,8 @@ import {
|
|
|
37
37
|
hasSwiftModernizableXCTestSuiteUsage,
|
|
38
38
|
hasSwiftNonLazyScrollForEachUsage,
|
|
39
39
|
hasSwiftViewBodyObjectCreationUsage,
|
|
40
|
+
hasSwiftUiImageDataDecodingUsage,
|
|
41
|
+
hasSwiftUiInlineActionLogicUsage,
|
|
40
42
|
hasSwiftAssumeIsolatedUsage,
|
|
41
43
|
hasSwiftCoreDataLayerLeakUsage,
|
|
42
44
|
hasSwiftSwiftDataLayerLeakUsage,
|
|
@@ -191,6 +193,69 @@ struct PriceView: View {
|
|
|
191
193
|
assert.equal(hasSwiftViewBodyObjectCreationUsage(safe), false);
|
|
192
194
|
});
|
|
193
195
|
|
|
196
|
+
test('hasSwiftUiImageDataDecodingUsage detecta UIImage(data:) y preserva strings y comentarios', () => {
|
|
197
|
+
const source = `
|
|
198
|
+
struct AvatarView: View {
|
|
199
|
+
let imageData: Data
|
|
200
|
+
|
|
201
|
+
var body: some View {
|
|
202
|
+
if let image = UIImage(data: imageData) {
|
|
203
|
+
Image(uiImage: image)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
`;
|
|
208
|
+
const safe = `
|
|
209
|
+
struct AvatarView: View {
|
|
210
|
+
let image: UIImage
|
|
211
|
+
|
|
212
|
+
var body: some View {
|
|
213
|
+
Image(uiImage: image)
|
|
214
|
+
let sample = "UIImage(data: imageData)"
|
|
215
|
+
// UIImage(data: imageData)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
`;
|
|
219
|
+
|
|
220
|
+
assert.equal(hasSwiftUiImageDataDecodingUsage(source), true);
|
|
221
|
+
assert.equal(hasSwiftUiImageDataDecodingUsage(safe), false);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('hasSwiftUiInlineActionLogicUsage detecta lógica inline en Button y preserva método referenciado', () => {
|
|
225
|
+
const source = `
|
|
226
|
+
struct CheckoutView: View {
|
|
227
|
+
@State private var isLoading = false
|
|
228
|
+
|
|
229
|
+
var body: some View {
|
|
230
|
+
Button {
|
|
231
|
+
if isLoading {
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
Task {
|
|
235
|
+
await submit()
|
|
236
|
+
}
|
|
237
|
+
} label: {
|
|
238
|
+
Text("Pay")
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
`;
|
|
243
|
+
const safe = `
|
|
244
|
+
struct CheckoutView: View {
|
|
245
|
+
var body: some View {
|
|
246
|
+
Button(action: submit) {
|
|
247
|
+
Text("Pay")
|
|
248
|
+
}
|
|
249
|
+
let sample = "Button { if loading { return } } label:"
|
|
250
|
+
// Button { if loading { return } } label:
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
`;
|
|
254
|
+
|
|
255
|
+
assert.equal(hasSwiftUiInlineActionLogicUsage(source), true);
|
|
256
|
+
assert.equal(hasSwiftUiInlineActionLogicUsage(safe), false);
|
|
257
|
+
});
|
|
258
|
+
|
|
194
259
|
test('hasSwiftForceTryUsage detecta try! y descarta try?', () => {
|
|
195
260
|
const positive = `
|
|
196
261
|
func load() {
|
|
@@ -396,6 +396,21 @@ export const hasSwiftViewBodyObjectCreationUsage = (source: string): boolean =>
|
|
|
396
396
|
return viewBodyObjectCreationPattern.test(swiftSource);
|
|
397
397
|
};
|
|
398
398
|
|
|
399
|
+
export const hasSwiftUiImageDataDecodingUsage = (source: string): boolean => {
|
|
400
|
+
const swiftSource = sanitizeSwiftSourceForMultilineRegex(source);
|
|
401
|
+
return /\bUIImage\s*\(\s*data\s*:/.test(swiftSource);
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
export const hasSwiftUiInlineActionLogicUsage = (source: string): boolean => {
|
|
405
|
+
const swiftSource = sanitizeSwiftSourceForMultilineRegex(source);
|
|
406
|
+
const inlineButtonActionPattern =
|
|
407
|
+
/\bButton\s*\{[\s\S]{0,900}\b(?:if|guard|switch|for|while|Task)\b[\s\S]{0,900}\}\s*label\s*:/;
|
|
408
|
+
const inlineActionParameterPattern =
|
|
409
|
+
/\bButton\s*\([^)]*\baction\s*:\s*\{[\s\S]{0,900}\b(?:if|guard|switch|for|while|Task)\b[\s\S]{0,900}\}/;
|
|
410
|
+
|
|
411
|
+
return inlineButtonActionPattern.test(swiftSource) || inlineActionParameterPattern.test(swiftSource);
|
|
412
|
+
};
|
|
413
|
+
|
|
399
414
|
export const hasSwiftDispatchQueueUsage = (source: string): boolean => {
|
|
400
415
|
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
401
416
|
if (current !== 'D' || !hasIdentifierAt(swiftSource, index, 'DispatchQueue')) {
|
|
@@ -687,6 +687,8 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
687
687
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftRedundantReactiveStateAssignmentUsage, ruleId: 'heuristics.ios.swiftui.redundant-reactive-state-assignment.ast', code: 'HEURISTICS_IOS_SWIFTUI_REDUNDANT_REACTIVE_STATE_ASSIGNMENT_AST', message: 'AST heuristic detected reactive state assignment without a value-change guard; check for value changes before assigning state in hot paths.' },
|
|
688
688
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNonLazyScrollForEachUsage, ruleId: 'heuristics.ios.swiftui.non-lazy-scroll-foreach.ast', code: 'HEURISTICS_IOS_SWIFTUI_NON_LAZY_SCROLL_FOREACH_AST', message: 'AST heuristic detected ScrollView with a non-lazy stack feeding ForEach; LazyVStack/LazyHStack remain the preferred baseline for large scrollable collections.' },
|
|
689
689
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftViewBodyObjectCreationUsage, ruleId: 'heuristics.ios.swiftui.body-object-creation.ast', code: 'HEURISTICS_IOS_SWIFTUI_BODY_OBJECT_CREATION_AST', message: 'AST heuristic detected formatter object creation inside SwiftUI body; keep body simple and move expensive objects out of render paths.' },
|
|
690
|
+
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUiImageDataDecodingUsage, ruleId: 'heuristics.ios.swiftui.image-data-decoding.ast', code: 'HEURISTICS_IOS_SWIFTUI_IMAGE_DATA_DECODING_AST', message: 'AST heuristic detected UIImage(data:) in SwiftUI presentation; downsample image data before rendering large images.' },
|
|
691
|
+
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUiInlineActionLogicUsage, ruleId: 'heuristics.ios.swiftui.inline-action-logic.ast', code: 'HEURISTICS_IOS_SWIFTUI_INLINE_ACTION_LOGIC_AST', message: 'AST heuristic detected inline logic inside a SwiftUI action handler; action handlers should reference methods and keep view declarations focused.' },
|
|
690
692
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNavigationViewUsage, ruleId: 'heuristics.ios.navigation-view.ast', code: 'HEURISTICS_IOS_NAVIGATION_VIEW_AST', message: 'AST heuristic detected NavigationView usage.' },
|
|
691
693
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForegroundColorUsage, ruleId: 'heuristics.ios.foreground-color.ast', code: 'HEURISTICS_IOS_FOREGROUND_COLOR_AST', message: 'AST heuristic detected foregroundColor usage.' },
|
|
692
694
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftCornerRadiusUsage, ruleId: 'heuristics.ios.corner-radius.ast', code: 'HEURISTICS_IOS_CORNER_RADIUS_AST', message: 'AST heuristic detected cornerRadius usage.' },
|
|
@@ -949,6 +949,44 @@ export const iosRules: RuleSet = [
|
|
|
949
949
|
code: 'HEURISTICS_IOS_SWIFTUI_BODY_OBJECT_CREATION_AST',
|
|
950
950
|
},
|
|
951
951
|
},
|
|
952
|
+
{
|
|
953
|
+
id: 'heuristics.ios.swiftui.image-data-decoding.ast',
|
|
954
|
+
description: 'Detects UIImage(data:) decoding in SwiftUI presentation paths.',
|
|
955
|
+
severity: 'WARN',
|
|
956
|
+
platform: 'ios',
|
|
957
|
+
locked: true,
|
|
958
|
+
when: {
|
|
959
|
+
kind: 'Heuristic',
|
|
960
|
+
where: {
|
|
961
|
+
ruleId: 'heuristics.ios.swiftui.image-data-decoding.ast',
|
|
962
|
+
},
|
|
963
|
+
},
|
|
964
|
+
then: {
|
|
965
|
+
kind: 'Finding',
|
|
966
|
+
message:
|
|
967
|
+
'AST heuristic detected UIImage(data:) in SwiftUI presentation; downsample image data before rendering large images.',
|
|
968
|
+
code: 'HEURISTICS_IOS_SWIFTUI_IMAGE_DATA_DECODING_AST',
|
|
969
|
+
},
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
id: 'heuristics.ios.swiftui.inline-action-logic.ast',
|
|
973
|
+
description: 'Detects inline control-flow logic inside SwiftUI action handlers.',
|
|
974
|
+
severity: 'WARN',
|
|
975
|
+
platform: 'ios',
|
|
976
|
+
locked: true,
|
|
977
|
+
when: {
|
|
978
|
+
kind: 'Heuristic',
|
|
979
|
+
where: {
|
|
980
|
+
ruleId: 'heuristics.ios.swiftui.inline-action-logic.ast',
|
|
981
|
+
},
|
|
982
|
+
},
|
|
983
|
+
then: {
|
|
984
|
+
kind: 'Finding',
|
|
985
|
+
message:
|
|
986
|
+
'AST heuristic detected inline logic inside a SwiftUI action handler; action handlers should reference methods and keep view declarations focused.',
|
|
987
|
+
code: 'HEURISTICS_IOS_SWIFTUI_INLINE_ACTION_LOGIC_AST',
|
|
988
|
+
},
|
|
989
|
+
},
|
|
952
990
|
{
|
|
953
991
|
id: 'heuristics.ios.navigation-view.ast',
|
|
954
992
|
description: 'Detects NavigationView usage in iOS production code.',
|
|
@@ -262,6 +262,14 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
262
262
|
heuristicDetector('ios.swiftui.body-object-creation', [
|
|
263
263
|
'heuristics.ios.swiftui.body-object-creation.ast',
|
|
264
264
|
]),
|
|
265
|
+
'skills.ios.guideline.ios-swiftui-expert.suggest-image-downsampling-when-uiimage-data-is-encountered':
|
|
266
|
+
heuristicDetector('ios.swiftui.image-data-decoding', [
|
|
267
|
+
'heuristics.ios.swiftui.image-data-decoding.ast',
|
|
268
|
+
]),
|
|
269
|
+
'skills.ios.guideline.ios-swiftui-expert.action-handlers-should-reference-methods-not-contain-inline-logic':
|
|
270
|
+
heuristicDetector('ios.swiftui.inline-action-logic', [
|
|
271
|
+
'heuristics.ios.swiftui.inline-action-logic.ast',
|
|
272
|
+
]),
|
|
265
273
|
'skills.ios.no-scrollview-shows-indicators': heuristicDetector(
|
|
266
274
|
'ios.scrollview-shows-indicators',
|
|
267
275
|
['heuristics.ios.scrollview-shows-indicators.ast']
|
|
@@ -16,7 +16,7 @@ const MARKDOWN_BOLD_PATTERN = /[*_]{1,3}/g;
|
|
|
16
16
|
const MULTISPACE_PATTERN = /\s+/g;
|
|
17
17
|
const AST_NODE_ID_PATTERN = /\bheuristics\.[a-z0-9._-]+\.ast\b/gi;
|
|
18
18
|
const RULE_KEYWORDS =
|
|
19
|
-
/\b(always|siempre|prefer|use|usar|avoid|evitar|never|nunca|must|obligatorio|required|disallow|do not|no)\b/i;
|
|
19
|
+
/\b(always|siempre|prefer|use|usar|avoid|evitar|never|nunca|must|obligatorio|required|disallow|do not|no|suggest|should)\b/i;
|
|
20
20
|
|
|
21
21
|
const normalizeForLookup = (value: string): string => {
|
|
22
22
|
return value
|
|
@@ -374,6 +374,20 @@ const normalizeKnownRuleTarget = (
|
|
|
374
374
|
) {
|
|
375
375
|
return 'skills.ios.guideline.ios-swiftui-expert.no-object-creation-in-body';
|
|
376
376
|
}
|
|
377
|
+
if (
|
|
378
|
+
includes('image downsampling') ||
|
|
379
|
+
(includes('uiimage data') && includes('downsampling')) ||
|
|
380
|
+
(includes('uiimage data') && includes('encountered'))
|
|
381
|
+
) {
|
|
382
|
+
return 'skills.ios.guideline.ios-swiftui-expert.suggest-image-downsampling-when-uiimage-data-is-encountered';
|
|
383
|
+
}
|
|
384
|
+
if (
|
|
385
|
+
(includes('action handlers') && includes('reference methods')) ||
|
|
386
|
+
(includes('action handlers') && includes('inline logic')) ||
|
|
387
|
+
(includes('handlers should reference methods') && includes('inline logic'))
|
|
388
|
+
) {
|
|
389
|
+
return 'skills.ios.guideline.ios-swiftui-expert.action-handlers-should-reference-methods-not-contain-inline-logic';
|
|
390
|
+
}
|
|
377
391
|
if (
|
|
378
392
|
includes('scrollindicators hidden') ||
|
|
379
393
|
includes('scroll indicators hidden') ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.227",
|
|
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-13T16:
|
|
4
|
+
"generatedAt": "2026-05-13T16:27:11.446Z",
|
|
5
5
|
"bundles": [
|
|
6
6
|
{
|
|
7
7
|
"name": "android-guidelines",
|
|
@@ -8632,8 +8632,20 @@
|
|
|
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": "a69126588892428f136f55666d721346015d4684ded6dfa07509045db95f7f48",
|
|
8636
8636
|
"rules": [
|
|
8637
|
+
{
|
|
8638
|
+
"id": "skills.ios.guideline.ios-swiftui-expert.action-handlers-should-reference-methods-not-contain-inline-logic",
|
|
8639
|
+
"description": "Action handlers should reference methods, not contain inline logic",
|
|
8640
|
+
"severity": "WARN",
|
|
8641
|
+
"platform": "ios",
|
|
8642
|
+
"sourceSkill": "ios-swiftui-expert-guidelines",
|
|
8643
|
+
"sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8644
|
+
"confidence": "MEDIUM",
|
|
8645
|
+
"locked": true,
|
|
8646
|
+
"evaluationMode": "AUTO",
|
|
8647
|
+
"origin": "core"
|
|
8648
|
+
},
|
|
8637
8649
|
{
|
|
8638
8650
|
"id": "skills.ios.guideline.ios-swiftui-expert.always-mark-state-and-stateobject-as-private-makes-dependencies-clear",
|
|
8639
8651
|
"description": "Always mark @State and @StateObject as private (makes dependencies clear)",
|
|
@@ -8742,6 +8754,30 @@
|
|
|
8742
8754
|
"evaluationMode": "DECLARATIVE",
|
|
8743
8755
|
"origin": "core"
|
|
8744
8756
|
},
|
|
8757
|
+
{
|
|
8758
|
+
"id": "skills.ios.guideline.ios-swiftui-expert.sheets-should-own-their-actions-and-call-dismiss-internally",
|
|
8759
|
+
"description": "Sheets should own their actions and call dismiss() internally",
|
|
8760
|
+
"severity": "WARN",
|
|
8761
|
+
"platform": "ios",
|
|
8762
|
+
"sourceSkill": "ios-swiftui-expert-guidelines",
|
|
8763
|
+
"sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8764
|
+
"confidence": "MEDIUM",
|
|
8765
|
+
"locked": true,
|
|
8766
|
+
"evaluationMode": "DECLARATIVE",
|
|
8767
|
+
"origin": "core"
|
|
8768
|
+
},
|
|
8769
|
+
{
|
|
8770
|
+
"id": "skills.ios.guideline.ios-swiftui-expert.suggest-image-downsampling-when-uiimage-data-is-encountered",
|
|
8771
|
+
"description": "Suggest image downsampling when UIImage(data:) is used (as optional optimization, see references/image-optimization.md)",
|
|
8772
|
+
"severity": "WARN",
|
|
8773
|
+
"platform": "ios",
|
|
8774
|
+
"sourceSkill": "ios-swiftui-expert-guidelines",
|
|
8775
|
+
"sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8776
|
+
"confidence": "MEDIUM",
|
|
8777
|
+
"locked": true,
|
|
8778
|
+
"evaluationMode": "AUTO",
|
|
8779
|
+
"origin": "core"
|
|
8780
|
+
},
|
|
8745
8781
|
{
|
|
8746
8782
|
"id": "skills.ios.guideline.ios-swiftui-expert.use-glasseffectid-with-namespace-for-morphing-transitions",
|
|
8747
8783
|
"description": "Use glassEffectID with @Namespace for morphing transitions",
|
|
@@ -8934,6 +8970,18 @@
|
|
|
8934
8970
|
"evaluationMode": "DECLARATIVE",
|
|
8935
8971
|
"origin": "core"
|
|
8936
8972
|
},
|
|
8973
|
+
{
|
|
8974
|
+
"id": "skills.ios.guideline.ios-swiftui-expert.views-should-work-in-any-context-don-t-assume-screen-size-or-presentat",
|
|
8975
|
+
"description": "Views should work in any context (don't assume screen size or presentation style)",
|
|
8976
|
+
"severity": "WARN",
|
|
8977
|
+
"platform": "ios",
|
|
8978
|
+
"sourceSkill": "ios-swiftui-expert-guidelines",
|
|
8979
|
+
"sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8980
|
+
"confidence": "MEDIUM",
|
|
8981
|
+
"locked": true,
|
|
8982
|
+
"evaluationMode": "DECLARATIVE",
|
|
8983
|
+
"origin": "core"
|
|
8984
|
+
},
|
|
8937
8985
|
{
|
|
8938
8986
|
"id": "skills.ios.no-anyview",
|
|
8939
8987
|
"description": "Avoid AnyView in list rows",
|