pumuki 6.3.199 → 6.3.200
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 +18 -0
- package/core/facts/detectors/text/ios.ts +17 -0
- package/core/facts/extractHeuristicFacts.ts +1 -0
- package/core/rules/presets/heuristics/ios.test.ts +6 -1
- package/core/rules/presets/heuristics/ios.ts +18 -0
- package/docs/codex-skills/ios-enterprise-rules.md +5 -0
- package/integrations/config/skillsDetectorRegistry.ts +4 -0
- package/integrations/config/skillsMarkdownRules.ts +3 -0
- package/package.json +1 -1
- package/skills.lock.json +3 -3
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
hasSwiftForceUnwrap,
|
|
23
23
|
hasSwiftGeometryReaderUsage,
|
|
24
24
|
hasSwiftHardcodedUiStringUsage,
|
|
25
|
+
hasSwiftLooseAssetResourceUsage,
|
|
25
26
|
hasSwiftLegacyOnChangeUsage,
|
|
26
27
|
hasSwiftLegacyExpectationDescriptionUsage,
|
|
27
28
|
hasSwiftLegacySwiftUiObservableWrapperUsage,
|
|
@@ -299,6 +300,23 @@ struct OrdersView: View {
|
|
|
299
300
|
assert.equal(hasSwiftHardcodedUiStringUsage(ignored), false);
|
|
300
301
|
});
|
|
301
302
|
|
|
303
|
+
test('detector iOS de assets detecta recursos sueltos sin confundir asset catalogs', () => {
|
|
304
|
+
const source = `
|
|
305
|
+
let path = Bundle.main.path(forResource: "hero", withExtension: "png")
|
|
306
|
+
let url = Bundle.main.url(forResource: "logo", withExtension: "pdf")
|
|
307
|
+
let image = UIImage(contentsOfFile: path)
|
|
308
|
+
`;
|
|
309
|
+
const ignored = `
|
|
310
|
+
Image("hero")
|
|
311
|
+
UIImage(named: "hero")
|
|
312
|
+
let text = "UIImage(contentsOfFile: path)"
|
|
313
|
+
// Bundle.main.path(forResource: "hero", withExtension: "png")
|
|
314
|
+
`;
|
|
315
|
+
|
|
316
|
+
assert.equal(hasSwiftLooseAssetResourceUsage(source), true);
|
|
317
|
+
assert.equal(hasSwiftLooseAssetResourceUsage(ignored), false);
|
|
318
|
+
});
|
|
319
|
+
|
|
302
320
|
test('hasSwiftUncheckedSendableUsage detecta @unchecked Sendable', () => {
|
|
303
321
|
const source = `
|
|
304
322
|
final class LegacyBox: @unchecked Sendable {}
|
|
@@ -543,6 +543,23 @@ export const hasSwiftHardcodedUiStringUsage = (source: string): boolean => {
|
|
|
543
543
|
});
|
|
544
544
|
};
|
|
545
545
|
|
|
546
|
+
export const hasSwiftLooseAssetResourceUsage = (source: string): boolean => {
|
|
547
|
+
const withoutBlockComments = source.replace(/\/\*[\s\S]*?\*\//g, '\n');
|
|
548
|
+
return withoutBlockComments.split(/\r?\n/).some((line) => {
|
|
549
|
+
if (/^\s*\/\//.test(line)) {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
const sanitized = stripSwiftLineForSemanticScan(line);
|
|
553
|
+
return (
|
|
554
|
+
/\bUIImage\s*\(\s*contentsOfFile\s*:/.test(sanitized) ||
|
|
555
|
+
/\bNSImage\s*\(\s*contentsOfFile\s*:/.test(sanitized) ||
|
|
556
|
+
/\bBundle\s*\.\s*main\s*\.\s*(?:path|url)\s*\(\s*forResource\s*:\s*""\s*,\s*withExtension\s*:\s*""/.test(
|
|
557
|
+
sanitized
|
|
558
|
+
)
|
|
559
|
+
);
|
|
560
|
+
});
|
|
561
|
+
};
|
|
562
|
+
|
|
546
563
|
export const hasSwiftUncheckedSendableUsage = (source: string): boolean => {
|
|
547
564
|
return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
|
|
548
565
|
if (current !== '@' || !swiftSource.startsWith('@unchecked', index)) {
|
|
@@ -655,6 +655,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
655
655
|
{ platform: 'ios', pathCheck: isIOSInfoPlistPath, excludePaths: [], detect: TextIOS.hasSwiftInsecureTransportUsage, ruleId: 'heuristics.ios.security.insecure-transport.ast', code: 'HEURISTICS_IOS_SECURITY_INSECURE_TRANSPORT_AST', message: 'AST heuristic detected permissive App Transport Security configuration; HTTPS and ATS remain the preferred baseline.' },
|
|
656
656
|
{ platform: 'ios', pathCheck: isIOSLocalizableStringsPath, excludePaths: [], detect: detectsTrackedFilePresence, ruleId: 'heuristics.ios.localization.localizable-strings.ast', code: 'HEURISTICS_IOS_LOCALIZATION_LOCALIZABLE_STRINGS_AST', message: 'AST heuristic detected Localizable.strings usage; String Catalogs (.xcstrings) remain the preferred baseline for new localization work.' },
|
|
657
657
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftHardcodedUiStringUsage, ruleId: 'heuristics.ios.localization.hardcoded-ui-string.ast', code: 'HEURISTICS_IOS_LOCALIZATION_HARDCODED_UI_STRING_AST', message: 'AST heuristic detected hardcoded user-facing SwiftUI text; String(localized:) and String Catalogs remain the preferred baseline.' },
|
|
658
|
+
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftLooseAssetResourceUsage, ruleId: 'heuristics.ios.assets.loose-resource.ast', code: 'HEURISTICS_IOS_ASSETS_LOOSE_RESOURCE_AST', message: 'AST heuristic detected loose image resource loading in iOS production code; Asset Catalogs remain the preferred baseline.' },
|
|
658
659
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftUncheckedSendableUsage, ruleId: 'heuristics.ios.unchecked-sendable.ast', code: 'HEURISTICS_IOS_UNCHECKED_SENDABLE_AST', message: 'AST heuristic detected @unchecked Sendable usage.' },
|
|
659
660
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftPreconcurrencyUsage, ruleId: 'heuristics.ios.preconcurrency.ast', code: 'HEURISTICS_IOS_PRECONCURRENCY_AST', message: 'AST heuristic detected @preconcurrency usage.' },
|
|
660
661
|
{ platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftNonisolatedUnsafeUsage, ruleId: 'heuristics.ios.nonisolated-unsafe.ast', code: 'HEURISTICS_IOS_NONISOLATED_UNSAFE_AST', message: 'AST heuristic detected nonisolated(unsafe) usage.' },
|
|
@@ -3,7 +3,7 @@ import test from 'node:test';
|
|
|
3
3
|
import { iosRules } from './ios';
|
|
4
4
|
|
|
5
5
|
test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
6
|
-
assert.equal(iosRules.length,
|
|
6
|
+
assert.equal(iosRules.length, 54);
|
|
7
7
|
|
|
8
8
|
const ids = iosRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -27,6 +27,7 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
27
27
|
'heuristics.ios.security.insecure-transport.ast',
|
|
28
28
|
'heuristics.ios.localization.localizable-strings.ast',
|
|
29
29
|
'heuristics.ios.localization.hardcoded-ui-string.ast',
|
|
30
|
+
'heuristics.ios.assets.loose-resource.ast',
|
|
30
31
|
'heuristics.ios.unchecked-sendable.ast',
|
|
31
32
|
'heuristics.ios.preconcurrency.ast',
|
|
32
33
|
'heuristics.ios.nonisolated-unsafe.ast',
|
|
@@ -111,6 +112,10 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
|
|
|
111
112
|
byId.get('heuristics.ios.localization.hardcoded-ui-string.ast')?.then.code,
|
|
112
113
|
'HEURISTICS_IOS_LOCALIZATION_HARDCODED_UI_STRING_AST'
|
|
113
114
|
);
|
|
115
|
+
assert.equal(
|
|
116
|
+
byId.get('heuristics.ios.assets.loose-resource.ast')?.then.code,
|
|
117
|
+
'HEURISTICS_IOS_ASSETS_LOOSE_RESOURCE_AST'
|
|
118
|
+
);
|
|
114
119
|
assert.equal(
|
|
115
120
|
byId.get('heuristics.ios.preconcurrency.ast')?.then.code,
|
|
116
121
|
'HEURISTICS_IOS_PRECONCURRENCY_AST'
|
|
@@ -361,6 +361,24 @@ export const iosRules: RuleSet = [
|
|
|
361
361
|
code: 'HEURISTICS_IOS_LOCALIZATION_HARDCODED_UI_STRING_AST',
|
|
362
362
|
},
|
|
363
363
|
},
|
|
364
|
+
{
|
|
365
|
+
id: 'heuristics.ios.assets.loose-resource.ast',
|
|
366
|
+
description: 'Detects loose image resource loading where Asset Catalogs are the preferred iOS baseline.',
|
|
367
|
+
severity: 'WARN',
|
|
368
|
+
platform: 'ios',
|
|
369
|
+
locked: true,
|
|
370
|
+
when: {
|
|
371
|
+
kind: 'Heuristic',
|
|
372
|
+
where: {
|
|
373
|
+
ruleId: 'heuristics.ios.assets.loose-resource.ast',
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
then: {
|
|
377
|
+
kind: 'Finding',
|
|
378
|
+
message: 'AST heuristic detected loose image resource loading in iOS production code; Asset Catalogs remain the preferred baseline.',
|
|
379
|
+
code: 'HEURISTICS_IOS_ASSETS_LOOSE_RESOURCE_AST',
|
|
380
|
+
},
|
|
381
|
+
},
|
|
364
382
|
{
|
|
365
383
|
id: 'heuristics.ios.unchecked-sendable.ast',
|
|
366
384
|
description: 'Detects @unchecked Sendable usage in iOS production code.',
|
|
@@ -617,6 +617,11 @@ struct APIEndpoint: Sendable {
|
|
|
617
617
|
- En `PROJECT MODE: brownfield`, este hallazgo detecta `Localizable.strings` bajo `apps/ios/**` como señal de baseline/adopción sin bloquear deuda histórica salvo promoción explícita de policy. String Catalogs (`.xcstrings`) permanece como baseline preferente.
|
|
618
618
|
- También detecta literales de texto visibles en SwiftUI (`Text`, `Button`, `Label`, `TextField`, `SecureField`, `navigationTitle`, `accessibilityLabel`) como señal de adopción hacia `String(localized:)` y String Catalogs, ignorando keys como `orders.title`.
|
|
619
619
|
|
|
620
|
+
### Enforcement AST inicial de assets iOS
|
|
621
|
+
|
|
622
|
+
- `skills.ios.guideline.ios.assets-en-asset-catalogs-con-soporte-para-todos-los-taman-os` se mapea a `heuristics.ios.assets.loose-resource.ast`.
|
|
623
|
+
- En `PROJECT MODE: brownfield`, este hallazgo detecta carga de imágenes sueltas desde bundle/filesystem (`UIImage(contentsOfFile:)`, `NSImage(contentsOfFile:)`, `Bundle.main.path/url(...png|jpg|jpeg|pdf|svg|webp)`) como señal de adopción hacia Asset Catalogs. No marca `Image("asset")` ni `UIImage(named:)`.
|
|
624
|
+
|
|
620
625
|
### Combine (Reactive):
|
|
621
626
|
✅ **Publishers** - AsyncSequence para async, Combine para streams complejos
|
|
622
627
|
✅ **@Published** - En ViewModels para binding con Views
|
|
@@ -111,6 +111,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
111
111
|
'ios.localization.hardcoded-ui-string',
|
|
112
112
|
['heuristics.ios.localization.hardcoded-ui-string.ast']
|
|
113
113
|
),
|
|
114
|
+
'skills.ios.guideline.ios.assets-en-asset-catalogs-con-soporte-para-todos-los-taman-os': heuristicDetector(
|
|
115
|
+
'ios.assets.loose-resource',
|
|
116
|
+
['heuristics.ios.assets.loose-resource.ast']
|
|
117
|
+
),
|
|
114
118
|
'skills.ios.no-unchecked-sendable': heuristicDetector('ios.unchecked-sendable', [
|
|
115
119
|
'heuristics.ios.unchecked-sendable.ast',
|
|
116
120
|
]),
|
|
@@ -398,6 +398,9 @@ const normalizeKnownRuleTarget = (
|
|
|
398
398
|
if (includes('strings hardcodeadas') || includes('string localized')) {
|
|
399
399
|
return 'skills.ios.guideline.ios.cero-strings-hardcodeadas-en-ui';
|
|
400
400
|
}
|
|
401
|
+
if (includes('assets en asset catalogs') || includes('asset catalogs')) {
|
|
402
|
+
return 'skills.ios.guideline.ios.assets-en-asset-catalogs-con-soporte-para-todos-los-taman-os';
|
|
403
|
+
}
|
|
401
404
|
if (
|
|
402
405
|
includes('mixing legacy xctest style') ||
|
|
403
406
|
includes('mixed xctest and swift testing') ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.200",
|
|
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-13T11:
|
|
4
|
+
"generatedAt": "2026-05-13T11:35:11.674Z",
|
|
5
5
|
"bundles": [
|
|
6
6
|
{
|
|
7
7
|
"name": "android-guidelines",
|
|
@@ -5764,7 +5764,7 @@
|
|
|
5764
5764
|
"name": "ios-guidelines",
|
|
5765
5765
|
"version": "1.0.0",
|
|
5766
5766
|
"source": "file:vendor/skills/ios-enterprise-rules/SKILL.md",
|
|
5767
|
-
"hash": "
|
|
5767
|
+
"hash": "b364080b578fc2de919d0d7d16c4075167415c3449f2d42a1cd2c6fabec18233",
|
|
5768
5768
|
"rules": [
|
|
5769
5769
|
{
|
|
5770
5770
|
"id": "skills.ios.guideline.ios.accessibility-identifiers-para-localizar-elementos",
|
|
@@ -5895,7 +5895,7 @@
|
|
|
5895
5895
|
"sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
|
|
5896
5896
|
"confidence": "MEDIUM",
|
|
5897
5897
|
"locked": true,
|
|
5898
|
-
"evaluationMode": "
|
|
5898
|
+
"evaluationMode": "AUTO",
|
|
5899
5899
|
"origin": "core"
|
|
5900
5900
|
},
|
|
5901
5901
|
{
|