pumuki 6.3.229 → 6.3.231
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 +55 -0
- package/core/facts/detectors/text/ios.ts +8 -0
- package/core/facts/extractHeuristicFacts.ts +2 -0
- package/core/rules/presets/heuristics/ios.ts +38 -0
- package/integrations/config/skillsDetectorRegistry.ts +12 -0
- package/integrations/config/skillsMarkdownRules.ts +12 -0
- package/package.json +1 -1
- package/skills.lock.json +5 -5
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
hasSwiftAdHocLoggingUsage,
|
|
16
16
|
hasSwiftAlamofireUsage,
|
|
17
17
|
hasSwiftForEachIndicesUsage,
|
|
18
|
+
hasSwiftForEachSelfIdentityUsage,
|
|
18
19
|
hasSwiftForceCastUsage,
|
|
19
20
|
hasSwiftFontWeightBoldUsage,
|
|
20
21
|
hasSwiftFixedFontSizeUsage,
|
|
@@ -64,6 +65,7 @@ import {
|
|
|
64
65
|
hasSwiftSheetIsPresentedUsage,
|
|
65
66
|
hasSwiftScrollViewShowsIndicatorsUsage,
|
|
66
67
|
hasSwiftSensitiveLoggingUsage,
|
|
68
|
+
hasSwiftSelfPrintChangesUsage,
|
|
67
69
|
hasSwiftSensitiveUserDefaultsStorageUsage,
|
|
68
70
|
hasSwiftInsecureTransportUsage,
|
|
69
71
|
hasSwiftJSONSerializationUsage,
|
|
@@ -292,6 +294,59 @@ struct CheckoutView: View {
|
|
|
292
294
|
assert.equal(hasSwiftUiInlineActionLogicUsage(safe), false);
|
|
293
295
|
});
|
|
294
296
|
|
|
297
|
+
test('hasSwiftForEachSelfIdentityUsage detecta id self y preserva ids estables', () => {
|
|
298
|
+
const source = `
|
|
299
|
+
struct FeedView: View {
|
|
300
|
+
let items: [Item]
|
|
301
|
+
|
|
302
|
+
var body: some View {
|
|
303
|
+
ForEach(items, id: \\.self) { item in
|
|
304
|
+
FeedRow(item: item)
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
`;
|
|
309
|
+
const safe = `
|
|
310
|
+
struct FeedView: View {
|
|
311
|
+
let items: [Item]
|
|
312
|
+
|
|
313
|
+
var body: some View {
|
|
314
|
+
ForEach(items, id: \\.id) { item in
|
|
315
|
+
FeedRow(item: item)
|
|
316
|
+
}
|
|
317
|
+
let sample = "ForEach(items, id: \\.self) { item in FeedRow(item: item) }"
|
|
318
|
+
// ForEach(items, id: \.self) { item in FeedRow(item: item) }
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
`;
|
|
322
|
+
|
|
323
|
+
assert.equal(hasSwiftForEachSelfIdentityUsage(source), true);
|
|
324
|
+
assert.equal(hasSwiftForEachSelfIdentityUsage(safe), false);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test('hasSwiftSelfPrintChangesUsage detecta Self._printChanges y preserva strings y comentarios', () => {
|
|
328
|
+
const source = `
|
|
329
|
+
struct FeedView: View {
|
|
330
|
+
var body: some View {
|
|
331
|
+
Self._printChanges()
|
|
332
|
+
Text("Feed")
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
`;
|
|
336
|
+
const safe = `
|
|
337
|
+
struct FeedView: View {
|
|
338
|
+
var body: some View {
|
|
339
|
+
Text("Feed")
|
|
340
|
+
let sample = "Self._printChanges()"
|
|
341
|
+
// Self._printChanges()
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
`;
|
|
345
|
+
|
|
346
|
+
assert.equal(hasSwiftSelfPrintChangesUsage(source), true);
|
|
347
|
+
assert.equal(hasSwiftSelfPrintChangesUsage(safe), false);
|
|
348
|
+
});
|
|
349
|
+
|
|
295
350
|
test('hasSwiftUntypedNavigationLinkDestinationUsage detecta NavigationLink no tipado y preserva value navigation', () => {
|
|
296
351
|
const source = `
|
|
297
352
|
struct FeedView: View {
|
|
@@ -849,6 +849,14 @@ export const hasSwiftForEachIndicesUsage = (source: string): boolean => {
|
|
|
849
849
|
);
|
|
850
850
|
};
|
|
851
851
|
|
|
852
|
+
export const hasSwiftForEachSelfIdentityUsage = (source: string): boolean => {
|
|
853
|
+
return hasSwiftSanitizedRegexMatch(source, /\bForEach\s*\([^)]*\bid\s*:\s*\\\.self\b/g);
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
export const hasSwiftSelfPrintChangesUsage = (source: string): boolean => {
|
|
857
|
+
return hasSwiftSanitizedRegexMatch(source, /\bSelf\s*\.\s*_printChanges\s*\(/g);
|
|
858
|
+
};
|
|
859
|
+
|
|
852
860
|
export const hasSwiftInlineForEachTransformUsage = (source: string): boolean => {
|
|
853
861
|
const sanitized = sanitizeSwiftSourceForMultilineRegex(source);
|
|
854
862
|
return /\bForEach\s*\(\s*(?:Array\s*\(\s*)?[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*\s*\.\s*(?:filter|map|compactMap|sorted)\s*(?:\{|\()/g.test(
|
|
@@ -678,6 +678,8 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
678
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.' },
|
|
679
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.' },
|
|
680
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.' },
|
|
681
|
+
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForEachSelfIdentityUsage, ruleId: 'heuristics.ios.swiftui.foreach-self-identity.ast', code: 'HEURISTICS_IOS_SWIFTUI_FOREACH_SELF_IDENTITY_AST', message: 'AST heuristic detected ForEach(..., id: \.self) usage; prefer a stable domain identity such as id: \.id or Identifiable models.' },
|
|
682
|
+
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftSelfPrintChangesUsage, ruleId: 'heuristics.ios.swiftui.self-print-changes.ast', code: 'HEURISTICS_IOS_SWIFTUI_SELF_PRINT_CHANGES_AST', message: 'AST heuristic detected Self._printChanges() in SwiftUI presentation; keep this debugging helper out of production view code.' },
|
|
681
683
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftInlineForEachTransformUsage, ruleId: 'heuristics.ios.swiftui.inline-foreach-transform.ast', code: 'HEURISTICS_IOS_SWIFTUI_INLINE_FOREACH_TRANSFORM_AST', message: 'AST heuristic detected inline filter/map/sort work inside ForEach; prefiltered or cached collections remain the preferred baseline.' },
|
|
682
684
|
{ platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUiForEachConditionalViewCountUsage, ruleId: 'heuristics.ios.swiftui.foreach-conditional-view-count.ast', code: 'HEURISTICS_IOS_SWIFTUI_FOREACH_CONDITIONAL_VIEW_COUNT_AST', message: 'AST heuristic detected conditional view count inside ForEach; keep a constant number of views per element by moving branching into row views or modifiers.' },
|
|
683
685
|
{ 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.' },
|
|
@@ -785,6 +785,25 @@ export const iosRules: RuleSet = [
|
|
|
785
785
|
code: 'HEURISTICS_IOS_FOREACH_INDICES_AST',
|
|
786
786
|
},
|
|
787
787
|
},
|
|
788
|
+
{
|
|
789
|
+
id: 'heuristics.ios.swiftui.foreach-self-identity.ast',
|
|
790
|
+
description: 'Detects SwiftUI ForEach usage with id: \.self instead of stable domain identity.',
|
|
791
|
+
severity: 'WARN',
|
|
792
|
+
platform: 'ios',
|
|
793
|
+
locked: true,
|
|
794
|
+
when: {
|
|
795
|
+
kind: 'Heuristic',
|
|
796
|
+
where: {
|
|
797
|
+
ruleId: 'heuristics.ios.swiftui.foreach-self-identity.ast',
|
|
798
|
+
},
|
|
799
|
+
},
|
|
800
|
+
then: {
|
|
801
|
+
kind: 'Finding',
|
|
802
|
+
message:
|
|
803
|
+
'AST heuristic detected ForEach(..., id: \.self) usage; prefer a stable domain identity such as id: \.id or Identifiable models.',
|
|
804
|
+
code: 'HEURISTICS_IOS_SWIFTUI_FOREACH_SELF_IDENTITY_AST',
|
|
805
|
+
},
|
|
806
|
+
},
|
|
788
807
|
{
|
|
789
808
|
id: 'heuristics.ios.swiftui.inline-foreach-transform.ast',
|
|
790
809
|
description: 'Detects inline filter/map/sort transformations inside SwiftUI ForEach calls.',
|
|
@@ -803,6 +822,25 @@ export const iosRules: RuleSet = [
|
|
|
803
822
|
code: 'HEURISTICS_IOS_SWIFTUI_INLINE_FOREACH_TRANSFORM_AST',
|
|
804
823
|
},
|
|
805
824
|
},
|
|
825
|
+
{
|
|
826
|
+
id: 'heuristics.ios.swiftui.self-print-changes.ast',
|
|
827
|
+
description: 'Detects SwiftUI Self._printChanges() debugging helpers in production views.',
|
|
828
|
+
severity: 'WARN',
|
|
829
|
+
platform: 'ios',
|
|
830
|
+
locked: true,
|
|
831
|
+
when: {
|
|
832
|
+
kind: 'Heuristic',
|
|
833
|
+
where: {
|
|
834
|
+
ruleId: 'heuristics.ios.swiftui.self-print-changes.ast',
|
|
835
|
+
},
|
|
836
|
+
},
|
|
837
|
+
then: {
|
|
838
|
+
kind: 'Finding',
|
|
839
|
+
message:
|
|
840
|
+
'AST heuristic detected Self._printChanges() in SwiftUI presentation code; remove debug-only render diagnostics from production views.',
|
|
841
|
+
code: 'HEURISTICS_IOS_SWIFTUI_SELF_PRINT_CHANGES_AST',
|
|
842
|
+
},
|
|
843
|
+
},
|
|
806
844
|
{
|
|
807
845
|
id: 'heuristics.ios.swiftui.foreach-conditional-view-count.ast',
|
|
808
846
|
description: 'Detects conditional view counts inside SwiftUI ForEach rows.',
|
|
@@ -233,6 +233,18 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
233
233
|
'skills.ios.no-foreach-indices': heuristicDetector('ios.foreach-indices', [
|
|
234
234
|
'heuristics.ios.foreach-indices.ast',
|
|
235
235
|
]),
|
|
236
|
+
'skills.ios.guideline.ios-swiftui-expert.ensure-foreach-uses-stable-identity-see-references-list-patterns-md':
|
|
237
|
+
heuristicDetector('ios.swiftui.foreach-self-identity', [
|
|
238
|
+
'heuristics.ios.swiftui.foreach-self-identity.ast',
|
|
239
|
+
]),
|
|
240
|
+
'skills.ios.guideline.ios-swiftui-expert.verify-list-patterns-use-stable-identity-see-references-list-patterns-':
|
|
241
|
+
heuristicDetector('ios.swiftui.foreach-self-identity', [
|
|
242
|
+
'heuristics.ios.swiftui.foreach-self-identity.ast',
|
|
243
|
+
]),
|
|
244
|
+
'skills.ios.guideline.ios-swiftui-expert.use-self-printchanges-to-debug-unexpected-view-updates':
|
|
245
|
+
heuristicDetector('ios.swiftui.self-print-changes', [
|
|
246
|
+
'heuristics.ios.swiftui.self-print-changes.ast',
|
|
247
|
+
]),
|
|
236
248
|
'skills.ios.guideline.ios-swiftui-expert.avoid-inline-filtering-in-foreach-prefilter-and-cache':
|
|
237
249
|
heuristicDetector('ios.swiftui.inline-foreach-transform', [
|
|
238
250
|
'heuristics.ios.swiftui.inline-foreach-transform.ast',
|
|
@@ -316,6 +316,18 @@ const normalizeKnownRuleTarget = (
|
|
|
316
316
|
) {
|
|
317
317
|
return 'skills.ios.no-foreach-indices';
|
|
318
318
|
}
|
|
319
|
+
if (
|
|
320
|
+
(includes('foreach') && includes('stable identity')) ||
|
|
321
|
+
(includes('list patterns') && includes('stable identity'))
|
|
322
|
+
) {
|
|
323
|
+
if (includes('verify list patterns')) {
|
|
324
|
+
return 'skills.ios.guideline.ios-swiftui-expert.verify-list-patterns-use-stable-identity-see-references-list-patterns-';
|
|
325
|
+
}
|
|
326
|
+
return 'skills.ios.guideline.ios-swiftui-expert.ensure-foreach-uses-stable-identity-see-references-list-patterns-md';
|
|
327
|
+
}
|
|
328
|
+
if (includes('self.printchanges') || includes('unexpected view updates')) {
|
|
329
|
+
return 'skills.ios.guideline.ios-swiftui-expert.use-self-printchanges-to-debug-unexpected-view-updates';
|
|
330
|
+
}
|
|
319
331
|
if (
|
|
320
332
|
(includes('inline filtering') && includes('foreach')) ||
|
|
321
333
|
(includes('no inline filtering') && includes('foreach')) ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.231",
|
|
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:57:01.364Z",
|
|
5
5
|
"bundles": [
|
|
6
6
|
{
|
|
7
7
|
"name": "android-guidelines",
|
|
@@ -8620,7 +8620,7 @@
|
|
|
8620
8620
|
"name": "ios-swiftui-expert-guidelines",
|
|
8621
8621
|
"version": "1.0.0",
|
|
8622
8622
|
"source": "file:vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8623
|
-
"hash": "
|
|
8623
|
+
"hash": "4ea50faf9594602eefa6711b818cf4badeb2014ca64eaf00cb2f98f3e11a55f0",
|
|
8624
8624
|
"rules": [
|
|
8625
8625
|
{
|
|
8626
8626
|
"id": "skills.ios.guideline.ios-swiftui-expert.action-handlers-should-reference-methods-not-contain-inline-logic",
|
|
@@ -8691,7 +8691,7 @@
|
|
|
8691
8691
|
"sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8692
8692
|
"confidence": "MEDIUM",
|
|
8693
8693
|
"locked": true,
|
|
8694
|
-
"evaluationMode": "
|
|
8694
|
+
"evaluationMode": "AUTO",
|
|
8695
8695
|
"origin": "core"
|
|
8696
8696
|
},
|
|
8697
8697
|
{
|
|
@@ -8931,7 +8931,7 @@
|
|
|
8931
8931
|
"sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8932
8932
|
"confidence": "MEDIUM",
|
|
8933
8933
|
"locked": true,
|
|
8934
|
-
"evaluationMode": "
|
|
8934
|
+
"evaluationMode": "AUTO",
|
|
8935
8935
|
"origin": "core"
|
|
8936
8936
|
},
|
|
8937
8937
|
{
|
|
@@ -8979,7 +8979,7 @@
|
|
|
8979
8979
|
"sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
|
|
8980
8980
|
"confidence": "MEDIUM",
|
|
8981
8981
|
"locked": true,
|
|
8982
|
-
"evaluationMode": "
|
|
8982
|
+
"evaluationMode": "AUTO",
|
|
8983
8983
|
"origin": "core"
|
|
8984
8984
|
},
|
|
8985
8985
|
{
|