pumuki 6.3.200 → 6.3.202

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.
@@ -17,6 +17,7 @@ import {
17
17
  hasSwiftForEachIndicesUsage,
18
18
  hasSwiftForceCastUsage,
19
19
  hasSwiftFontWeightBoldUsage,
20
+ hasSwiftFixedFontSizeUsage,
20
21
  hasSwiftForegroundColorUsage,
21
22
  hasSwiftForceTryUsage,
22
23
  hasSwiftForceUnwrap,
@@ -42,6 +43,7 @@ import {
42
43
  hasSwiftOperationQueueUsage,
43
44
  hasSwiftContainsUserFilterUsage,
44
45
  hasSwiftPassedValueStateWrapperUsage,
46
+ hasSwiftPhysicalTextAlignmentUsage,
45
47
  hasSwiftPreconcurrencyUsage,
46
48
  hasSwiftSheetIsPresentedUsage,
47
49
  hasSwiftScrollViewShowsIndicatorsUsage,
@@ -317,6 +319,41 @@ let text = "UIImage(contentsOfFile: path)"
317
319
  assert.equal(hasSwiftLooseAssetResourceUsage(ignored), false);
318
320
  });
319
321
 
322
+ test('detector iOS de accesibilidad detecta tamaños de fuente fijos sin confundir estilos semánticos', () => {
323
+ const source = `
324
+ Text("Total").font(.system(size: 18))
325
+ let title = Font.system(size: 24, weight: .bold)
326
+ label.font = UIFont.systemFont(ofSize: 16)
327
+ `;
328
+ const ignored = `
329
+ Text("Total").font(.headline)
330
+ Text("Body").font(.body)
331
+ let text = "UIFont.systemFont(ofSize: 16)"
332
+ // Text("Total").font(.system(size: 18))
333
+ `;
334
+
335
+ assert.equal(hasSwiftFixedFontSizeUsage(source), true);
336
+ assert.equal(hasSwiftFixedFontSizeUsage(ignored), false);
337
+ });
338
+
339
+ test('detector iOS de localización detecta alineación física sin confundir leading/trailing', () => {
340
+ const source = `
341
+ Text("Name").multilineTextAlignment(.left)
342
+ Text("Price").frame(maxWidth: .infinity, alignment: .right)
343
+ let textAlignment = TextAlignment.right
344
+ label.textAlignment = NSTextAlignment.left
345
+ `;
346
+ const ignored = `
347
+ Text("Name").multilineTextAlignment(.leading)
348
+ Text("Price").frame(maxWidth: .infinity, alignment: .trailing)
349
+ let sample = "TextAlignment.right"
350
+ // Text("Name").multilineTextAlignment(.left)
351
+ `;
352
+
353
+ assert.equal(hasSwiftPhysicalTextAlignmentUsage(source), true);
354
+ assert.equal(hasSwiftPhysicalTextAlignmentUsage(ignored), false);
355
+ });
356
+
320
357
  test('hasSwiftUncheckedSendableUsage detecta @unchecked Sendable', () => {
321
358
  const source = `
322
359
  final class LegacyBox: @unchecked Sendable {}
@@ -560,6 +560,39 @@ export const hasSwiftLooseAssetResourceUsage = (source: string): boolean => {
560
560
  });
561
561
  };
562
562
 
563
+ export const hasSwiftFixedFontSizeUsage = (source: string): boolean => {
564
+ const withoutBlockComments = source.replace(/\/\*[\s\S]*?\*\//g, '\n');
565
+ return withoutBlockComments.split(/\r?\n/).some((line) => {
566
+ if (/^\s*\/\//.test(line)) {
567
+ return false;
568
+ }
569
+ const sanitized = stripSwiftLineForSemanticScan(line);
570
+ return (
571
+ /\.\s*font\s*\(\s*\.\s*system\s*\(\s*size\s*:/.test(sanitized) ||
572
+ /\bFont\s*\.\s*system\s*\(\s*size\s*:/.test(sanitized) ||
573
+ /\bUIFont\s*\.\s*(?:systemFont|boldSystemFont|italicSystemFont)\s*\(\s*ofSize\s*:/.test(
574
+ sanitized
575
+ )
576
+ );
577
+ });
578
+ };
579
+
580
+ export const hasSwiftPhysicalTextAlignmentUsage = (source: string): boolean => {
581
+ const withoutBlockComments = source.replace(/\/\*[\s\S]*?\*\//g, '\n');
582
+ return withoutBlockComments.split(/\r?\n/).some((line) => {
583
+ if (/^\s*\/\//.test(line)) {
584
+ return false;
585
+ }
586
+ const sanitized = stripSwiftLineForSemanticScan(line);
587
+ return (
588
+ /\.\s*multilineTextAlignment\s*\(\s*\.\s*(?:left|right)\s*\)/.test(sanitized) ||
589
+ /\.\s*frame\s*\([^)]*alignment\s*:\s*\.\s*(?:left|right)\b/.test(sanitized) ||
590
+ /\bTextAlignment\s*\.\s*(?:left|right)\b/.test(sanitized) ||
591
+ /\bNSTextAlignment\s*\.\s*(?:left|right)\b/.test(sanitized)
592
+ );
593
+ });
594
+ };
595
+
563
596
  export const hasSwiftUncheckedSendableUsage = (source: string): boolean => {
564
597
  return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
565
598
  if (current !== '@' || !swiftSource.startsWith('@unchecked', index)) {
@@ -656,6 +656,8 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
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
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.' },
659
+ { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftFixedFontSizeUsage, ruleId: 'heuristics.ios.accessibility.fixed-font-size.ast', code: 'HEURISTICS_IOS_ACCESSIBILITY_FIXED_FONT_SIZE_AST', message: 'AST heuristic detected fixed font sizing in iOS production code; Dynamic Type semantic text styles remain the preferred baseline.' },
660
+ { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftPhysicalTextAlignmentUsage, ruleId: 'heuristics.ios.localization.physical-text-alignment.ast', code: 'HEURISTICS_IOS_LOCALIZATION_PHYSICAL_TEXT_ALIGNMENT_AST', message: 'AST heuristic detected physical left/right text alignment in iOS production code; leading/trailing remain the preferred RTL-safe baseline.' },
659
661
  { 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.' },
660
662
  { 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.' },
661
663
  { 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, 54);
6
+ assert.equal(iosRules.length, 56);
7
7
 
8
8
  const ids = iosRules.map((rule) => rule.id);
9
9
  assert.deepEqual(ids, [
@@ -28,6 +28,8 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
28
28
  'heuristics.ios.localization.localizable-strings.ast',
29
29
  'heuristics.ios.localization.hardcoded-ui-string.ast',
30
30
  'heuristics.ios.assets.loose-resource.ast',
31
+ 'heuristics.ios.accessibility.fixed-font-size.ast',
32
+ 'heuristics.ios.localization.physical-text-alignment.ast',
31
33
  'heuristics.ios.unchecked-sendable.ast',
32
34
  'heuristics.ios.preconcurrency.ast',
33
35
  'heuristics.ios.nonisolated-unsafe.ast',
@@ -116,6 +118,14 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
116
118
  byId.get('heuristics.ios.assets.loose-resource.ast')?.then.code,
117
119
  'HEURISTICS_IOS_ASSETS_LOOSE_RESOURCE_AST'
118
120
  );
121
+ assert.equal(
122
+ byId.get('heuristics.ios.accessibility.fixed-font-size.ast')?.then.code,
123
+ 'HEURISTICS_IOS_ACCESSIBILITY_FIXED_FONT_SIZE_AST'
124
+ );
125
+ assert.equal(
126
+ byId.get('heuristics.ios.localization.physical-text-alignment.ast')?.then.code,
127
+ 'HEURISTICS_IOS_LOCALIZATION_PHYSICAL_TEXT_ALIGNMENT_AST'
128
+ );
119
129
  assert.equal(
120
130
  byId.get('heuristics.ios.preconcurrency.ast')?.then.code,
121
131
  'HEURISTICS_IOS_PRECONCURRENCY_AST'
@@ -379,6 +379,42 @@ export const iosRules: RuleSet = [
379
379
  code: 'HEURISTICS_IOS_ASSETS_LOOSE_RESOURCE_AST',
380
380
  },
381
381
  },
382
+ {
383
+ id: 'heuristics.ios.accessibility.fixed-font-size.ast',
384
+ description: 'Detects fixed font sizes where Dynamic Type semantic styles are the preferred iOS baseline.',
385
+ severity: 'WARN',
386
+ platform: 'ios',
387
+ locked: true,
388
+ when: {
389
+ kind: 'Heuristic',
390
+ where: {
391
+ ruleId: 'heuristics.ios.accessibility.fixed-font-size.ast',
392
+ },
393
+ },
394
+ then: {
395
+ kind: 'Finding',
396
+ message: 'AST heuristic detected fixed font sizing in iOS production code; Dynamic Type semantic text styles remain the preferred baseline.',
397
+ code: 'HEURISTICS_IOS_ACCESSIBILITY_FIXED_FONT_SIZE_AST',
398
+ },
399
+ },
400
+ {
401
+ id: 'heuristics.ios.localization.physical-text-alignment.ast',
402
+ description: 'Detects physical left/right text alignment where leading/trailing are the preferred RTL-safe iOS baseline.',
403
+ severity: 'WARN',
404
+ platform: 'ios',
405
+ locked: true,
406
+ when: {
407
+ kind: 'Heuristic',
408
+ where: {
409
+ ruleId: 'heuristics.ios.localization.physical-text-alignment.ast',
410
+ },
411
+ },
412
+ then: {
413
+ kind: 'Finding',
414
+ message: 'AST heuristic detected physical left/right text alignment in iOS production code; leading/trailing remain the preferred RTL-safe baseline.',
415
+ code: 'HEURISTICS_IOS_LOCALIZATION_PHYSICAL_TEXT_ALIGNMENT_AST',
416
+ },
417
+ },
382
418
  {
383
419
  id: 'heuristics.ios.unchecked-sendable.ast',
384
420
  description: 'Detects @unchecked Sendable usage in iOS production code.',
@@ -622,6 +622,17 @@ struct APIEndpoint: Sendable {
622
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
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
624
 
625
+ ### Enforcement AST inicial de accesibilidad iOS
626
+
627
+ - `skills.ios.guideline.ios.dynamic-type-font-scaling-automa-tico` se mapea a `heuristics.ios.accessibility.fixed-font-size.ast`.
628
+ - `skills.ios.guideline.ios.dynamic-type-fuentes-escalables-y-layouts-adaptativos` se mapea a `heuristics.ios.accessibility.fixed-font-size.ast`.
629
+ - En `PROJECT MODE: brownfield`, este hallazgo detecta tamaños de fuente fijos (`.font(.system(size:))`, `Font.system(size:)`, `UIFont.systemFont(ofSize:)`) como señal de adopción hacia Dynamic Type y estilos semánticos. No marca `.font(.headline)`, `.font(.body)` ni otros estilos semánticos.
630
+
631
+ ### Enforcement AST inicial de RTL iOS
632
+
633
+ - `skills.ios.guideline.ios.rtl-support-right-to-left-para-a-rabe-hebreo` se mapea a `heuristics.ios.localization.physical-text-alignment.ast`.
634
+ - En `PROJECT MODE: brownfield`, este hallazgo detecta alineaciones físicas `.left`/`.right` en texto y frames (`multilineTextAlignment`, `frame(alignment:)`, `TextAlignment`, `NSTextAlignment`) como señal de adopción hacia `.leading`/`.trailing`. No marca `.leading` ni `.trailing`.
635
+
625
636
  ### Combine (Reactive):
626
637
  ✅ **Publishers** - AsyncSequence para async, Combine para streams complejos
627
638
  ✅ **@Published** - En ViewModels para binding con Views
@@ -115,6 +115,18 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
115
115
  'ios.assets.loose-resource',
116
116
  ['heuristics.ios.assets.loose-resource.ast']
117
117
  ),
118
+ 'skills.ios.guideline.ios.dynamic-type-font-scaling-automa-tico': heuristicDetector(
119
+ 'ios.accessibility.fixed-font-size',
120
+ ['heuristics.ios.accessibility.fixed-font-size.ast']
121
+ ),
122
+ 'skills.ios.guideline.ios.dynamic-type-fuentes-escalables-y-layouts-adaptativos': heuristicDetector(
123
+ 'ios.accessibility.fixed-font-size',
124
+ ['heuristics.ios.accessibility.fixed-font-size.ast']
125
+ ),
126
+ 'skills.ios.guideline.ios.rtl-support-right-to-left-para-a-rabe-hebreo': heuristicDetector(
127
+ 'ios.localization.physical-text-alignment',
128
+ ['heuristics.ios.localization.physical-text-alignment.ast']
129
+ ),
118
130
  'skills.ios.no-unchecked-sendable': heuristicDetector('ios.unchecked-sendable', [
119
131
  'heuristics.ios.unchecked-sendable.ast',
120
132
  ]),
@@ -401,6 +401,12 @@ const normalizeKnownRuleTarget = (
401
401
  if (includes('assets en asset catalogs') || includes('asset catalogs')) {
402
402
  return 'skills.ios.guideline.ios.assets-en-asset-catalogs-con-soporte-para-todos-los-taman-os';
403
403
  }
404
+ if (includes('dynamic type')) {
405
+ return 'skills.ios.guideline.ios.dynamic-type-font-scaling-automa-tico';
406
+ }
407
+ if (includes('rtl support') || includes('right to left')) {
408
+ return 'skills.ios.guideline.ios.rtl-support-right-to-left-para-a-rabe-hebreo';
409
+ }
404
410
  if (
405
411
  includes('mixing legacy xctest style') ||
406
412
  includes('mixed xctest and swift testing') ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.200",
3
+ "version": "6.3.202",
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:35:11.674Z",
4
+ "generatedAt": "2026-05-13T11:47:21.054Z",
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": "b364080b578fc2de919d0d7d16c4075167415c3449f2d42a1cd2c6fabec18233",
5767
+ "hash": "4689c3cc0d9f813517c298e0cddac9fffa9c029951fbc1c6f58d95994938f2d9",
5768
5768
  "rules": [
5769
5769
  {
5770
5770
  "id": "skills.ios.guideline.ios.accessibility-identifiers-para-localizar-elementos",
@@ -6380,18 +6380,6 @@
6380
6380
  },
6381
6381
  {
6382
6382
  "id": "skills.ios.guideline.ios.dynamic-type-font-scaling-automa-tico",
6383
- "description": "Dynamic Type - Font scaling automático",
6384
- "severity": "WARN",
6385
- "platform": "ios",
6386
- "sourceSkill": "ios-guidelines",
6387
- "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
6388
- "confidence": "MEDIUM",
6389
- "locked": true,
6390
- "evaluationMode": "DECLARATIVE",
6391
- "origin": "core"
6392
- },
6393
- {
6394
- "id": "skills.ios.guideline.ios.dynamic-type-fuentes-escalables-y-layouts-adaptativos",
6395
6383
  "description": "Dynamic Type - fuentes escalables y layouts adaptativos",
6396
6384
  "severity": "WARN",
6397
6385
  "platform": "ios",
@@ -6399,7 +6387,7 @@
6399
6387
  "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
6400
6388
  "confidence": "MEDIUM",
6401
6389
  "locked": true,
6402
- "evaluationMode": "DECLARATIVE",
6390
+ "evaluationMode": "AUTO",
6403
6391
  "origin": "core"
6404
6392
  },
6405
6393
  {
@@ -7636,7 +7624,7 @@
7636
7624
  "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
7637
7625
  "confidence": "MEDIUM",
7638
7626
  "locked": true,
7639
- "evaluationMode": "DECLARATIVE",
7627
+ "evaluationMode": "AUTO",
7640
7628
  "origin": "core"
7641
7629
  },
7642
7630
  {