pumuki 6.3.203 → 6.3.204

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.
@@ -23,6 +23,7 @@ import {
23
23
  hasSwiftForceUnwrap,
24
24
  hasSwiftGeometryReaderUsage,
25
25
  hasSwiftHardcodedUiStringUsage,
26
+ hasSwiftIconOnlyControlWithoutAccessibilityLabelUsage,
26
27
  hasSwiftLooseAssetResourceUsage,
27
28
  hasSwiftLegacyOnChangeUsage,
28
29
  hasSwiftLegacyExpectationDescriptionUsage,
@@ -377,6 +378,38 @@ func wait() async throws {
377
378
  assert.equal(hasSwiftMainThreadBlockingSleepUsage(ignored), false);
378
379
  });
379
380
 
381
+ test('detector iOS de accesibilidad detecta botones icon-only sin label explicita', () => {
382
+ const source = `
383
+ struct ToolbarView: View {
384
+ var body: some View {
385
+ Button {
386
+ delete()
387
+ } label: {
388
+ Image(systemName: "trash")
389
+ }
390
+ }
391
+ }
392
+ `;
393
+ const ignored = `
394
+ struct ToolbarView: View {
395
+ var body: some View {
396
+ Button {
397
+ delete()
398
+ } label: {
399
+ Image(systemName: "trash")
400
+ }
401
+ .accessibilityLabel(Text("Delete item"))
402
+ Button("Delete") { delete() }
403
+ let text = "Button { Image(systemName: \\"trash\\") }"
404
+ // Button { Image(systemName: "trash") }
405
+ }
406
+ }
407
+ `;
408
+
409
+ assert.equal(hasSwiftIconOnlyControlWithoutAccessibilityLabelUsage(source), true);
410
+ assert.equal(hasSwiftIconOnlyControlWithoutAccessibilityLabelUsage(ignored), false);
411
+ });
412
+
380
413
  test('hasSwiftUncheckedSendableUsage detecta @unchecked Sendable', () => {
381
414
  const source = `
382
415
  final class LegacyBox: @unchecked Sendable {}
@@ -608,6 +608,20 @@ export const hasSwiftMainThreadBlockingSleepUsage = (source: string): boolean =>
608
608
  });
609
609
  };
610
610
 
611
+ export const hasSwiftIconOnlyControlWithoutAccessibilityLabelUsage = (source: string): boolean => {
612
+ const sanitized = sanitizeSwiftSourceForMultilineRegex(source);
613
+ const iconOnlyButtonPattern =
614
+ /\bButton\s*(?:\([^)]*\))?\s*\{[\s\S]{0,240}?\bImage\s*\(\s*systemName\s*:\s*""\s*\)[\s\S]{0,240}?\}/g;
615
+ for (const match of sanitized.matchAll(iconOnlyButtonPattern)) {
616
+ const segment = match[0] ?? '';
617
+ const following = sanitized.slice(match.index ?? 0, (match.index ?? 0) + segment.length + 160);
618
+ if (!/\.\s*accessibilityLabel\s*\(/.test(following)) {
619
+ return true;
620
+ }
621
+ }
622
+ return false;
623
+ };
624
+
611
625
  export const hasSwiftUncheckedSendableUsage = (source: string): boolean => {
612
626
  return scanCodeLikeSource(source, ({ source: swiftSource, index, current }) => {
613
627
  if (current !== '@' || !swiftSource.startsWith('@unchecked', index)) {
@@ -659,6 +659,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
659
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
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.' },
661
661
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftMainThreadBlockingSleepUsage, ruleId: 'heuristics.ios.performance.blocking-sleep.ast', code: 'HEURISTICS_IOS_PERFORMANCE_BLOCKING_SLEEP_AST', message: 'AST heuristic detected blocking sleep usage in iOS production code; async clocks, suspension or cancellable scheduling remain the preferred baseline.' },
662
+ { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftIconOnlyControlWithoutAccessibilityLabelUsage, ruleId: 'heuristics.ios.accessibility.icon-only-control-label.ast', code: 'HEURISTICS_IOS_ACCESSIBILITY_ICON_ONLY_CONTROL_LABEL_AST', message: 'AST heuristic detected an icon-only SwiftUI control without accessibilityLabel; explicit accessible labels remain the preferred baseline.' },
662
663
  { 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.' },
663
664
  { 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.' },
664
665
  { 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, 57);
6
+ assert.equal(iosRules.length, 58);
7
7
 
8
8
  const ids = iosRules.map((rule) => rule.id);
9
9
  assert.deepEqual(ids, [
@@ -31,6 +31,7 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
31
31
  'heuristics.ios.accessibility.fixed-font-size.ast',
32
32
  'heuristics.ios.localization.physical-text-alignment.ast',
33
33
  'heuristics.ios.performance.blocking-sleep.ast',
34
+ 'heuristics.ios.accessibility.icon-only-control-label.ast',
34
35
  'heuristics.ios.unchecked-sendable.ast',
35
36
  'heuristics.ios.preconcurrency.ast',
36
37
  'heuristics.ios.nonisolated-unsafe.ast',
@@ -131,6 +132,10 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
131
132
  byId.get('heuristics.ios.performance.blocking-sleep.ast')?.then.code,
132
133
  'HEURISTICS_IOS_PERFORMANCE_BLOCKING_SLEEP_AST'
133
134
  );
135
+ assert.equal(
136
+ byId.get('heuristics.ios.accessibility.icon-only-control-label.ast')?.then.code,
137
+ 'HEURISTICS_IOS_ACCESSIBILITY_ICON_ONLY_CONTROL_LABEL_AST'
138
+ );
134
139
  assert.equal(
135
140
  byId.get('heuristics.ios.preconcurrency.ast')?.then.code,
136
141
  'HEURISTICS_IOS_PRECONCURRENCY_AST'
@@ -433,6 +433,24 @@ export const iosRules: RuleSet = [
433
433
  code: 'HEURISTICS_IOS_PERFORMANCE_BLOCKING_SLEEP_AST',
434
434
  },
435
435
  },
436
+ {
437
+ id: 'heuristics.ios.accessibility.icon-only-control-label.ast',
438
+ description: 'Detects icon-only SwiftUI controls without explicit accessibility labels.',
439
+ severity: 'WARN',
440
+ platform: 'ios',
441
+ locked: true,
442
+ when: {
443
+ kind: 'Heuristic',
444
+ where: {
445
+ ruleId: 'heuristics.ios.accessibility.icon-only-control-label.ast',
446
+ },
447
+ },
448
+ then: {
449
+ kind: 'Finding',
450
+ message: 'AST heuristic detected an icon-only SwiftUI control without accessibilityLabel; explicit accessible labels remain the preferred baseline.',
451
+ code: 'HEURISTICS_IOS_ACCESSIBILITY_ICON_ONLY_CONTROL_LABEL_AST',
452
+ },
453
+ },
436
454
  {
437
455
  id: 'heuristics.ios.unchecked-sendable.ast',
438
456
  description: 'Detects @unchecked Sendable usage in iOS production code.',
@@ -626,7 +626,9 @@ struct APIEndpoint: Sendable {
626
626
 
627
627
  - `skills.ios.guideline.ios.dynamic-type-font-scaling-automa-tico` se mapea a `heuristics.ios.accessibility.fixed-font-size.ast`.
628
628
  - `skills.ios.guideline.ios.dynamic-type-fuentes-escalables-y-layouts-adaptativos` se mapea a `heuristics.ios.accessibility.fixed-font-size.ast`.
629
+ - `skills.ios.guideline.ios.accessibility-labels-accessibilitylabel` se mapea a `heuristics.ios.accessibility.icon-only-control-label.ast`.
629
630
  - 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.
631
+ - En `PROJECT MODE: brownfield`, este hallazgo detecta controles SwiftUI icon-only con `Button` + `Image(systemName:)` sin `.accessibilityLabel` cercano como señal de adopción hacia labels accesibles explícitas. No intenta inferir todos los casos de accesibilidad ni marca botones con texto visible.
630
632
 
631
633
  ### Enforcement AST inicial de RTL iOS
632
634
 
@@ -131,6 +131,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
131
131
  'ios.performance.blocking-sleep',
132
132
  ['heuristics.ios.performance.blocking-sleep.ast']
133
133
  ),
134
+ 'skills.ios.guideline.ios.accessibility-labels-accessibilitylabel': heuristicDetector(
135
+ 'ios.accessibility.icon-only-control-label',
136
+ ['heuristics.ios.accessibility.icon-only-control-label.ast']
137
+ ),
134
138
  'skills.ios.no-unchecked-sendable': heuristicDetector('ios.unchecked-sendable', [
135
139
  'heuristics.ios.unchecked-sendable.ast',
136
140
  ]),
@@ -416,6 +416,13 @@ const normalizeKnownRuleTarget = (
416
416
  ) {
417
417
  return 'skills.ios.guideline.ios.background-threads-no-bloquear-main-thread';
418
418
  }
419
+ if (
420
+ includes('accessibility labels') ||
421
+ includes('accessibilitylabel') ||
422
+ includes('accessibility label')
423
+ ) {
424
+ return 'skills.ios.guideline.ios.accessibility-labels-accessibilitylabel';
425
+ }
419
426
  if (
420
427
  includes('mixing legacy xctest style') ||
421
428
  includes('mixed xctest and swift testing') ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.203",
3
+ "version": "6.3.204",
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:57:22.186Z",
4
+ "generatedAt": "2026-05-13T12:03:23.736Z",
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": "54d70e010210d043da474f55a1031af4f0d897b30082dd522be5d85c44ba27ab",
5767
+ "hash": "41ef368c744a0b772fa408e83fe7edf4621fe2766ba3ea47d6753339690dbbd2",
5768
5768
  "rules": [
5769
5769
  {
5770
5770
  "id": "skills.ios.guideline.ios.accessibility-identifiers-para-localizar-elementos",
@@ -5787,7 +5787,7 @@
5787
5787
  "sourcePath": "vendor/skills/ios-enterprise-rules/SKILL.md",
5788
5788
  "confidence": "MEDIUM",
5789
5789
  "locked": true,
5790
- "evaluationMode": "DECLARATIVE",
5790
+ "evaluationMode": "AUTO",
5791
5791
  "origin": "core"
5792
5792
  },
5793
5793
  {