@leejungkiin/awkit 1.7.1 → 1.7.4

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.
Files changed (245) hide show
  1. package/bin/awk.js +576 -84
  2. package/core/CLAUDE.md +1 -1
  3. package/core/GEMINI.md +148 -167
  4. package/core/GEMINI.md.bak +149 -116
  5. package/core/skill-runtime-manifest.json +3 -0
  6. package/docs/Claude Fable 5.md +3826 -0
  7. package/docs/android_kotlin_system_instruction.md +210 -0
  8. package/docs/brainstorm_ponytail_integration.md +146 -0
  9. package/docs/brainstorm_smart_setup.md +113 -0
  10. package/docs/deep-research-report (1).md +293 -0
  11. package/docs/history/GEMINI.v1.md +135 -0
  12. package/docs/history/brainstorm_antigravity_unified_architecture.v1.md +105 -0
  13. package/docs/history/implementation_plan.v1.md +58 -0
  14. package/package.json +4 -1
  15. package/scripts/artifact-storage.js +130 -0
  16. package/scripts/automation-gate.js +35 -2
  17. package/scripts/claude-plan.js +76 -0
  18. package/scripts/dependency-manager.js +210 -0
  19. package/scripts/exec-rtk.js +11 -5
  20. package/scripts/i18n-helper.js +381 -0
  21. package/scripts/multi-model-pipeline.js +144 -0
  22. package/skill-packs/mobile-ios/pack.json +4 -2
  23. package/skill-packs/reverse-engineering/pack.json +1 -0
  24. package/skills/CATALOG.md +20 -0
  25. package/skills/GEMINI.md +9 -1
  26. package/skills/TRIGGER_INDEX.md +10 -0
  27. package/skills/ai-music/SKILL.md +275 -0
  28. package/skills/android-re-analyzer/SKILL.md +238 -0
  29. package/skills/android-re-analyzer/references/api-extraction-patterns.md +119 -0
  30. package/skills/android-re-analyzer/references/call-flow-analysis.md +176 -0
  31. package/skills/android-re-analyzer/references/fernflower-usage.md +115 -0
  32. package/skills/android-re-analyzer/references/jadx-usage.md +116 -0
  33. package/skills/android-re-analyzer/references/setup-guide.md +221 -0
  34. package/skills/android-re-analyzer/scripts/check-deps.sh +129 -0
  35. package/skills/android-re-analyzer/scripts/decompile.sh +375 -0
  36. package/skills/android-re-analyzer/scripts/find-api-calls.sh +118 -0
  37. package/skills/android-re-analyzer/scripts/install-dep.sh +448 -0
  38. package/skills/animal-island-ui-style/SKILL.md +1450 -0
  39. package/skills/app-store-review-agent/SKILL.md +164 -0
  40. package/skills/app-store-review-agent/references/guidelines/README.md +154 -0
  41. package/skills/app-store-review-agent/references/guidelines/by-app-type/ai_apps.md +37 -0
  42. package/skills/app-store-review-agent/references/guidelines/by-app-type/all_apps.md +50 -0
  43. package/skills/app-store-review-agent/references/guidelines/by-app-type/crypto_finance.md +31 -0
  44. package/skills/app-store-review-agent/references/guidelines/by-app-type/games.md +31 -0
  45. package/skills/app-store-review-agent/references/guidelines/by-app-type/health_fitness.md +31 -0
  46. package/skills/app-store-review-agent/references/guidelines/by-app-type/kids.md +27 -0
  47. package/skills/app-store-review-agent/references/guidelines/by-app-type/macos.md +38 -0
  48. package/skills/app-store-review-agent/references/guidelines/by-app-type/social_ugc.md +32 -0
  49. package/skills/app-store-review-agent/references/guidelines/by-app-type/subscription_iap.md +34 -0
  50. package/skills/app-store-review-agent/references/guidelines/by-app-type/vpn.md +18 -0
  51. package/skills/app-store-review-agent/references/rules/design/minimum_functionality.md +96 -0
  52. package/skills/app-store-review-agent/references/rules/design/sign_in_with_apple.md +54 -0
  53. package/skills/app-store-review-agent/references/rules/entitlements/unused_entitlements.md +83 -0
  54. package/skills/app-store-review-agent/references/rules/metadata/accurate_metadata.md +54 -0
  55. package/skills/app-store-review-agent/references/rules/metadata/apple_trademark.md +99 -0
  56. package/skills/app-store-review-agent/references/rules/metadata/china_storefront.md +72 -0
  57. package/skills/app-store-review-agent/references/rules/metadata/competitor_terms.md +56 -0
  58. package/skills/app-store-review-agent/references/rules/metadata/subscription_metadata.md +81 -0
  59. package/skills/app-store-review-agent/references/rules/privacy/privacy_manifest.md +84 -0
  60. package/skills/app-store-review-agent/references/rules/privacy/unnecessary_data.md +60 -0
  61. package/skills/app-store-review-agent/references/rules/subscription/misleading_pricing.md +63 -0
  62. package/skills/app-store-review-agent/references/rules/subscription/missing_tos_pp.md +54 -0
  63. package/skills/awf-ponytail/SKILL.md +91 -0
  64. package/skills/awf-ponytail-review/SKILL.md +67 -0
  65. package/skills/awf-session-restore/SKILL.md +3 -3
  66. package/skills/brainstorm-agent/SKILL.md +11 -2
  67. package/skills/brainstorm-agent/templates/brief-template.md +8 -0
  68. package/skills/claude-planner/SKILL.md +47 -0
  69. package/skills/code-review/SKILL.md +87 -0
  70. package/skills/expo-game-development/SKILL.md +163 -0
  71. package/skills/flutter/LICENSE.txt +202 -0
  72. package/skills/flutter/SKILL.md +127 -0
  73. package/skills/flutter-project-creater/LICENSE.txt +202 -0
  74. package/skills/flutter-project-creater/SKILL.md +106 -0
  75. package/skills/game-developer/SKILL.md +163 -0
  76. package/skills/game-developer/references/ecs-patterns.md +501 -0
  77. package/skills/game-developer/references/multiplayer-networking.md +475 -0
  78. package/skills/game-developer/references/performance-optimization.md +422 -0
  79. package/skills/game-developer/references/unity-patterns.md +271 -0
  80. package/skills/game-developer/references/unreal-cpp.md +352 -0
  81. package/skills/generate-gui-assets/SKILL.md +305 -0
  82. package/skills/generate-gui-assets/agents/openai.yaml +4 -0
  83. package/skills/generate-gui-assets/references/catalog-schema.md +58 -0
  84. package/skills/generate-gui-assets/references/extraction-techniques.md +21 -0
  85. package/skills/generate-gui-assets/references/prompt-patterns.md +58 -0
  86. package/skills/generate-gui-assets/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
  87. package/skills/generate-gui-assets/scripts/build_gui_contact_sheet.py +51 -0
  88. package/skills/generate-gui-assets/scripts/clean_chroma_edges.py +262 -0
  89. package/skills/generate-gui-assets/scripts/copy_approved_icons.py +64 -0
  90. package/skills/generate-gui-assets/scripts/prepare_gui_asset_run.py +91 -0
  91. package/skills/generate-gui-assets/scripts/suggest_grid_options.py +63 -0
  92. package/skills/generate-gui-assets/scripts/validate_gui_catalog.py +50 -0
  93. package/skills/godot-game-development/SKILL.md +142 -0
  94. package/skills/hatch-pet/LICENSE.txt +201 -0
  95. package/skills/hatch-pet/SKILL.md +420 -0
  96. package/skills/hatch-pet/agents/openai.yaml +4 -0
  97. package/skills/hatch-pet/references/animation-rows.md +29 -0
  98. package/skills/hatch-pet/references/codex-pet-contract.md +35 -0
  99. package/skills/hatch-pet/references/qa-rubric.md +60 -0
  100. package/skills/hatch-pet/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
  101. package/skills/hatch-pet/scripts/clean_chroma_edges.py +262 -0
  102. package/skills/hatch-pet/scripts/compose_atlas.py +150 -0
  103. package/skills/hatch-pet/scripts/derive_running_left_from_running_right.py +143 -0
  104. package/skills/hatch-pet/scripts/extract_strip_frames.py +323 -0
  105. package/skills/hatch-pet/scripts/finalize_pet_run.py +382 -0
  106. package/skills/hatch-pet/scripts/generate_pet_images.py +287 -0
  107. package/skills/hatch-pet/scripts/inspect_frames.py +246 -0
  108. package/skills/hatch-pet/scripts/make_contact_sheet.py +96 -0
  109. package/skills/hatch-pet/scripts/package_custom_pet.py +108 -0
  110. package/skills/hatch-pet/scripts/pet_job_status.py +117 -0
  111. package/skills/hatch-pet/scripts/prepare_pet_run.py +673 -0
  112. package/skills/hatch-pet/scripts/queue_pet_repairs.py +172 -0
  113. package/skills/hatch-pet/scripts/record_imagegen_result.py +250 -0
  114. package/skills/hatch-pet/scripts/render_animation_videos.py +134 -0
  115. package/skills/hatch-pet/scripts/render_animation_videos.sh +5 -0
  116. package/skills/hatch-pet/scripts/validate_atlas.py +139 -0
  117. package/skills/i18n-orchestrator/SKILL.md +37 -0
  118. package/skills/ios-simulator-skill/SKILL.md +390 -0
  119. package/skills/ios-simulator-skill/scripts/accessibility_audit.py +300 -0
  120. package/skills/ios-simulator-skill/scripts/app_launcher.py +326 -0
  121. package/skills/ios-simulator-skill/scripts/app_state_capture.py +400 -0
  122. package/skills/ios-simulator-skill/scripts/appearance.py +385 -0
  123. package/skills/ios-simulator-skill/scripts/build_and_test.py +348 -0
  124. package/skills/ios-simulator-skill/scripts/clipboard.py +103 -0
  125. package/skills/ios-simulator-skill/scripts/common/__init__.py +61 -0
  126. package/skills/ios-simulator-skill/scripts/common/cache_utils.py +289 -0
  127. package/skills/ios-simulator-skill/scripts/common/device_utils.py +462 -0
  128. package/skills/ios-simulator-skill/scripts/common/env_config.py +35 -0
  129. package/skills/ios-simulator-skill/scripts/common/hang_pipeline.py +862 -0
  130. package/skills/ios-simulator-skill/scripts/common/hang_sessions.py +490 -0
  131. package/skills/ios-simulator-skill/scripts/common/idb_utils.py +180 -0
  132. package/skills/ios-simulator-skill/scripts/common/screenshot_utils.py +338 -0
  133. package/skills/ios-simulator-skill/scripts/container.py +668 -0
  134. package/skills/ios-simulator-skill/scripts/gesture.py +394 -0
  135. package/skills/ios-simulator-skill/scripts/hang_watcher.py +1533 -0
  136. package/skills/ios-simulator-skill/scripts/keyboard.py +391 -0
  137. package/skills/ios-simulator-skill/scripts/localization_audit.py +483 -0
  138. package/skills/ios-simulator-skill/scripts/location.py +467 -0
  139. package/skills/ios-simulator-skill/scripts/log_monitor.py +493 -0
  140. package/skills/ios-simulator-skill/scripts/model_inspector.py +645 -0
  141. package/skills/ios-simulator-skill/scripts/navigator.py +461 -0
  142. package/skills/ios-simulator-skill/scripts/privacy_manager.py +310 -0
  143. package/skills/ios-simulator-skill/scripts/push_notification.py +240 -0
  144. package/skills/ios-simulator-skill/scripts/screen_mapper.py +296 -0
  145. package/skills/ios-simulator-skill/scripts/sim_health_check.sh +245 -0
  146. package/skills/ios-simulator-skill/scripts/sim_list.py +299 -0
  147. package/skills/ios-simulator-skill/scripts/simctl_boot.py +312 -0
  148. package/skills/ios-simulator-skill/scripts/simctl_create.py +316 -0
  149. package/skills/ios-simulator-skill/scripts/simctl_delete.py +357 -0
  150. package/skills/ios-simulator-skill/scripts/simctl_erase.py +351 -0
  151. package/skills/ios-simulator-skill/scripts/simctl_shutdown.py +290 -0
  152. package/skills/ios-simulator-skill/scripts/simulator_selector.py +375 -0
  153. package/skills/ios-simulator-skill/scripts/status_bar.py +250 -0
  154. package/skills/ios-simulator-skill/scripts/test_recorder.py +323 -0
  155. package/skills/ios-simulator-skill/scripts/visual_diff.py +235 -0
  156. package/skills/ios-simulator-skill/scripts/xcode/__init__.py +13 -0
  157. package/skills/ios-simulator-skill/scripts/xcode/builder.py +397 -0
  158. package/skills/ios-simulator-skill/scripts/xcode/cache.py +204 -0
  159. package/skills/ios-simulator-skill/scripts/xcode/config.py +178 -0
  160. package/skills/ios-simulator-skill/scripts/xcode/reporter.py +343 -0
  161. package/skills/ios-simulator-skill/scripts/xcode/xcresult.py +451 -0
  162. package/skills/ios-visual-qa-strategist/SKILL.md +111 -0
  163. package/skills/ios-visual-qa-strategist/agents/openai.yaml +4 -0
  164. package/skills/ios-visual-qa-strategist/references/ios-tool-selection.md +61 -0
  165. package/skills/ios-visual-qa-strategist/references/minimal-capture-policy.md +56 -0
  166. package/skills/ios-visual-qa-strategist/references/visual-reasoning-heuristics.md +53 -0
  167. package/skills/orchestrator/SKILL.md +0 -20
  168. package/skills/persistent-storage/SKILL.md +55 -0
  169. package/skills/short-maker/SKILL.md +23 -0
  170. package/skills/short-maker/scripts/effects.js +56 -0
  171. package/skills/short-maker/scripts/shortmaker-bridge.js +332 -0
  172. package/skills/short-maker/scripts/videomix.js +601 -0
  173. package/skills/short-maker/templates/hyperframes/cinematic-character.template.html +172 -0
  174. package/skills/short-maker/templates/hyperframes/index.template.html +194 -0
  175. package/skills/smali-to-kotlin/SKILL.md +128 -0
  176. package/skills/smali-to-kotlin/examples/getting-started/tech-stack.md +58 -0
  177. package/skills/smali-to-kotlin/examples/pipeline/data-ui-parity.md +118 -0
  178. package/skills/smali-to-kotlin/examples/pipeline/scanner-and-bootstrap.md +106 -0
  179. package/skills/smali-to-kotlin/library-patterns.md +189 -0
  180. package/skills/smali-to-kotlin/phase-0-discovery.md +128 -0
  181. package/skills/smali-to-kotlin/phase-1-architecture.md +166 -0
  182. package/skills/smali-to-kotlin/phase-2-blueprint-ui.md +347 -0
  183. package/skills/smali-to-kotlin/phase-2-blueprint.md +228 -0
  184. package/skills/smali-to-kotlin/phase-3-build.md +248 -0
  185. package/skills/smali-to-kotlin/phase-3-logic-build.md +268 -0
  186. package/skills/smali-to-kotlin/smali-reading-guide.md +310 -0
  187. package/skills/smali-to-kotlin/templates/app-map.md +101 -0
  188. package/skills/smali-to-kotlin/templates/architecture.md +142 -0
  189. package/skills/smali-to-kotlin/templates/blueprint.md +145 -0
  190. package/skills/spec-gate/SKILL.md +6 -2
  191. package/skills/symphony-enforcer/SKILL.md +8 -0
  192. package/skills/symphony-enforcer/examples/mindful-stop.md +2 -0
  193. package/skills/symphony-enforcer/examples/three-phase.md +16 -0
  194. package/skills/symphony-enforcer/examples/trigger-points.md +7 -1
  195. package/skills/unity-game-development/SKILL.md +231 -0
  196. package/skills/video-edit/SKILL.md +36 -0
  197. package/skills/video-edit/scripts/video_edit.py +324 -0
  198. package/templates/project-identity/android.json +2 -2
  199. package/templates/project-identity/backend-nestjs.json +2 -2
  200. package/templates/project-identity/expo.json +2 -2
  201. package/templates/project-identity/ios.json +2 -2
  202. package/templates/project-identity/web-nextjs.json +2 -2
  203. package/templates/setup-mapping.json +48 -0
  204. package/templates/specs/design-template.md +161 -71
  205. package/templates/specs/requirements-template.md +65 -133
  206. package/templates/specs/task-spec-template.xml +3 -0
  207. package/workflows/_uncategorized/critic.md +40 -0
  208. package/workflows/_uncategorized/git-rebase-flow.md +81 -0
  209. package/workflows/_uncategorized/image-gen.md +118 -0
  210. package/workflows/_uncategorized/multi-model-pipeline.md +60 -0
  211. package/workflows/_uncategorized/pixel-gen.md +86 -0
  212. package/workflows/_uncategorized/pixel-setup.md +90 -0
  213. package/workflows/_uncategorized/ponytail-review.md +59 -0
  214. package/workflows/_uncategorized/reverse-android-build.md +222 -0
  215. package/workflows/_uncategorized/reverse-android-design.md +139 -0
  216. package/workflows/_uncategorized/reverse-android-discover.md +150 -0
  217. package/workflows/_uncategorized/reverse-android-scan.md +158 -0
  218. package/workflows/_uncategorized/reverse-android.md +143 -0
  219. package/workflows/_uncategorized/reverse-ios-build.md +240 -0
  220. package/workflows/_uncategorized/reverse-ios-design.md +112 -0
  221. package/workflows/_uncategorized/reverse-ios-discover.md +120 -0
  222. package/workflows/_uncategorized/reverse-ios-scan.md +155 -0
  223. package/workflows/_uncategorized/reverse-ios.md +152 -0
  224. package/workflows/_uncategorized/safety-router.md +34 -0
  225. package/workflows/_uncategorized/teach.md +89 -0
  226. package/workflows/_uncategorized/verify-ui.md +53 -0
  227. package/workflows/_uncategorized/visualize-screenshots.md +34 -0
  228. package/workflows/ads/ads-analyst.md +201 -0
  229. package/workflows/ads/ads-audit.md +106 -0
  230. package/workflows/ads/ads-optimize.md +97 -0
  231. package/workflows/ads/ads-targeting.md +241 -0
  232. package/workflows/ads/adsExpert.md +160 -0
  233. package/workflows/ads/smali-ads-config.md +400 -0
  234. package/workflows/ads/smali-ads-flow.md +331 -0
  235. package/workflows/ads/smali-ads-interstitial.md +377 -0
  236. package/workflows/ads/smali-ads-native.md +382 -0
  237. package/workflows/context/teach.md +89 -0
  238. package/workflows/gitnexus.md +8 -8
  239. package/workflows/lifecycle/brainstorm.md +43 -0
  240. package/workflows/lifecycle/code.md +5 -0
  241. package/workflows/lifecycle/init.md +23 -5
  242. package/workflows/lifecycle/multi-model-pipeline.md +60 -0
  243. package/workflows/quality/ponytail-review.md +59 -0
  244. package/workflows/roles/critic.md +40 -0
  245. package/workflows/roles/safety-router.md +34 -0
@@ -0,0 +1,300 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ iOS Simulator Accessibility Audit
4
+
5
+ Scans the current simulator screen for accessibility compliance issues.
6
+ Optimized for minimal token output while maintaining functionality.
7
+
8
+ Usage: python scripts/accessibility_audit.py [options]
9
+ """
10
+
11
+ import argparse
12
+ import json
13
+ import subprocess
14
+ import sys
15
+ from dataclasses import asdict, dataclass
16
+ from typing import Any
17
+
18
+ from common import flatten_tree, get_accessibility_tree, resolve_udid
19
+ from common.env_config import env_int
20
+
21
+ A11Y_LABEL_MAX = env_int("IOS_SIM_A11Y_LABEL_MAX", 80)
22
+ A11Y_TOP_ISSUES = env_int("IOS_SIM_A11Y_TOP_ISSUES", 10)
23
+
24
+
25
+ @dataclass
26
+ class Issue:
27
+ """Represents an accessibility issue."""
28
+
29
+ severity: str # critical, warning, info
30
+ rule: str
31
+ element_type: str
32
+ issue: str
33
+ fix: str
34
+
35
+ def to_dict(self) -> dict:
36
+ """Convert to dictionary for JSON serialization."""
37
+ return asdict(self)
38
+
39
+
40
+ class AccessibilityAuditor:
41
+ """Performs accessibility audits on iOS simulator screens."""
42
+
43
+ # Critical rules that block users
44
+ CRITICAL_RULES = {
45
+ "missing_label": lambda e: e.get("type") in ["Button", "Link"] and not e.get("AXLabel"),
46
+ "empty_button": lambda e: e.get("type") == "Button"
47
+ and not (e.get("AXLabel") or e.get("AXValue")),
48
+ "image_no_alt": lambda e: e.get("type") == "Image" and not e.get("AXLabel"),
49
+ }
50
+
51
+ # Warnings that degrade UX
52
+ WARNING_RULES = {
53
+ "missing_hint": lambda e: e.get("type") in ["Slider", "TextField"] and not e.get("help"),
54
+ "missing_traits": lambda e: e.get("type") and not e.get("traits"),
55
+ }
56
+
57
+ # Info level suggestions
58
+ INFO_RULES = {
59
+ "no_identifier": lambda e: not e.get("AXUniqueId"),
60
+ "deep_nesting": lambda e: e.get("depth", 0) > 5,
61
+ }
62
+
63
+ def __init__(self, udid: str | None = None):
64
+ """Initialize auditor with optional device UDID."""
65
+ self.udid = udid
66
+
67
+ def get_accessibility_tree(self) -> dict:
68
+ """Fetch accessibility tree from simulator using shared utility."""
69
+ return get_accessibility_tree(self.udid, nested=True)
70
+
71
+ @staticmethod
72
+ def _is_small_target(element: dict) -> bool:
73
+ """Check if touch target is too small (< 44x44 points)."""
74
+ frame = element.get("frame", {})
75
+ width = frame.get("width", 0)
76
+ height = frame.get("height", 0)
77
+ return width < 44 or height < 44
78
+
79
+ def _flatten_tree(self, node: dict, depth: int = 0) -> list[dict]:
80
+ """Flatten nested accessibility tree for easier processing using shared utility."""
81
+ return flatten_tree(node, depth)
82
+
83
+ def audit_element(self, element: dict) -> list[Issue]:
84
+ """Audit a single element for accessibility issues."""
85
+ issues = []
86
+
87
+ # Check critical rules
88
+ for rule_name, rule_func in self.CRITICAL_RULES.items():
89
+ if rule_func(element):
90
+ issues.append(
91
+ Issue(
92
+ severity="critical",
93
+ rule=rule_name,
94
+ element_type=element.get("type", "Unknown"),
95
+ issue=self._get_issue_description(rule_name),
96
+ fix=self._get_fix_suggestion(rule_name),
97
+ )
98
+ )
99
+
100
+ # Check warnings (skip if critical issues found)
101
+ if not issues:
102
+ for rule_name, rule_func in self.WARNING_RULES.items():
103
+ if rule_func(element):
104
+ issues.append(
105
+ Issue(
106
+ severity="warning",
107
+ rule=rule_name,
108
+ element_type=element.get("type", "Unknown"),
109
+ issue=self._get_issue_description(rule_name),
110
+ fix=self._get_fix_suggestion(rule_name),
111
+ )
112
+ )
113
+
114
+ # Check info level (only if verbose or no other issues)
115
+ if not issues:
116
+ for rule_name, rule_func in self.INFO_RULES.items():
117
+ if rule_func(element):
118
+ issues.append(
119
+ Issue(
120
+ severity="info",
121
+ rule=rule_name,
122
+ element_type=element.get("type", "Unknown"),
123
+ issue=self._get_issue_description(rule_name),
124
+ fix=self._get_fix_suggestion(rule_name),
125
+ )
126
+ )
127
+
128
+ return issues
129
+
130
+ def _get_issue_description(self, rule: str) -> str:
131
+ """Get human-readable issue description."""
132
+ descriptions = {
133
+ "missing_label": "Interactive element missing accessibility label",
134
+ "empty_button": "Button has no text or label",
135
+ "image_no_alt": "Image missing alternative text",
136
+ "missing_hint": "Complex control missing hint",
137
+ "small_touch_target": "Touch target smaller than 44x44pt",
138
+ "missing_traits": "Element missing accessibility traits",
139
+ "no_identifier": "Missing accessibility identifier",
140
+ "deep_nesting": "Deeply nested (>5 levels)",
141
+ }
142
+ return descriptions.get(rule, "Accessibility issue")
143
+
144
+ def _get_fix_suggestion(self, rule: str) -> str:
145
+ """Get fix suggestion for issue."""
146
+ fixes = {
147
+ "missing_label": "Add accessibilityLabel",
148
+ "empty_button": "Set button title or accessibilityLabel",
149
+ "image_no_alt": "Add accessibilityLabel with description",
150
+ "missing_hint": "Add accessibilityHint",
151
+ "small_touch_target": "Increase to minimum 44x44pt",
152
+ "missing_traits": "Set appropriate accessibilityTraits",
153
+ "no_identifier": "Add accessibilityIdentifier for testing",
154
+ "deep_nesting": "Simplify view hierarchy",
155
+ }
156
+ return fixes.get(rule, "Review accessibility")
157
+
158
+ def audit(self, verbose: bool = False) -> dict[str, Any]:
159
+ """Perform full accessibility audit."""
160
+ # Get accessibility tree
161
+ tree = self.get_accessibility_tree()
162
+
163
+ # Flatten for processing
164
+ elements = self._flatten_tree(tree)
165
+
166
+ # Audit each element
167
+ all_issues = []
168
+ for element in elements:
169
+ issues = self.audit_element(element)
170
+ for issue in issues:
171
+ issue_dict = issue.to_dict()
172
+ # Add minimal element info for context
173
+ issue_dict["element"] = {
174
+ "type": element.get("type", "Unknown"),
175
+ "label": (
176
+ element.get("AXLabel", "")[:A11Y_LABEL_MAX]
177
+ if element.get("AXLabel")
178
+ else None
179
+ ),
180
+ }
181
+ all_issues.append(issue_dict)
182
+
183
+ # Count by severity
184
+ critical = len([i for i in all_issues if i["severity"] == "critical"])
185
+ warning = len([i for i in all_issues if i["severity"] == "warning"])
186
+ info = len([i for i in all_issues if i["severity"] == "info"])
187
+
188
+ # Build result (token-optimized)
189
+ result = {
190
+ "summary": {
191
+ "total": len(elements),
192
+ "issues": len(all_issues),
193
+ "critical": critical,
194
+ "warning": warning,
195
+ "info": info,
196
+ }
197
+ }
198
+
199
+ if verbose:
200
+ # Full details only if requested
201
+ result["issues"] = all_issues
202
+ else:
203
+ # Default: top issues only (token-efficient)
204
+ result["top_issues"] = self._get_top_issues(all_issues)
205
+
206
+ return result
207
+
208
+ def _get_top_issues(self, issues: list[dict]) -> list[dict]:
209
+ """Get top 3 issues grouped by type (token-efficient)."""
210
+ if not issues:
211
+ return []
212
+
213
+ # Group by rule
214
+ grouped = {}
215
+ for issue in issues:
216
+ rule = issue["rule"]
217
+ if rule not in grouped:
218
+ grouped[rule] = {
219
+ "severity": issue["severity"],
220
+ "rule": rule,
221
+ "count": 0,
222
+ "fix": issue["fix"],
223
+ }
224
+ grouped[rule]["count"] += 1
225
+
226
+ # Sort by severity and count
227
+ severity_order = {"critical": 0, "warning": 1, "info": 2}
228
+ sorted_issues = sorted(
229
+ grouped.values(), key=lambda x: (severity_order[x["severity"]], -x["count"])
230
+ )
231
+
232
+ return sorted_issues[:A11Y_TOP_ISSUES]
233
+
234
+
235
+ def main():
236
+ """Main entry point."""
237
+ parser = argparse.ArgumentParser(
238
+ description="Audit iOS simulator screen for accessibility issues"
239
+ )
240
+ parser.add_argument(
241
+ "--udid",
242
+ help="Device UDID (auto-detects booted simulator if not provided)",
243
+ )
244
+ parser.add_argument("--output", help="Save JSON report to file")
245
+ parser.add_argument(
246
+ "--verbose", action="store_true", help="Include all issue details (increases output)"
247
+ )
248
+
249
+ args = parser.parse_args()
250
+
251
+ # Resolve UDID with auto-detection
252
+ try:
253
+ udid = resolve_udid(args.udid)
254
+ except RuntimeError as e:
255
+ print(f"Error: {e}")
256
+ sys.exit(1)
257
+
258
+ # Perform audit
259
+ auditor = AccessibilityAuditor(udid=udid)
260
+
261
+ try:
262
+ result = auditor.audit(verbose=args.verbose)
263
+ except Exception as e:
264
+ print(f"Error: {e}")
265
+ sys.exit(1)
266
+
267
+ # Output results
268
+ if args.output:
269
+ # Save to file
270
+ with open(args.output, "w") as f:
271
+ json.dump(result, f, indent=2)
272
+ # Print minimal summary
273
+ summary = result["summary"]
274
+ print(f"Audit complete: {summary['issues']} issues ({summary['critical']} critical)")
275
+ print(f"Report saved to: {args.output}")
276
+ # Print to stdout (token-optimized by default)
277
+ elif args.verbose:
278
+ print(json.dumps(result, indent=2))
279
+ else:
280
+ # Ultra-compact output
281
+ summary = result["summary"]
282
+ print(f"Elements: {summary['total']}, Issues: {summary['issues']}")
283
+ print(
284
+ f"Critical: {summary['critical']}, Warning: {summary['warning']}, Info: {summary['info']}"
285
+ )
286
+
287
+ if result.get("top_issues"):
288
+ print("\nTop issues:")
289
+ for issue in result["top_issues"]:
290
+ print(
291
+ f" [{issue['severity']}] {issue['rule']} ({issue['count']}x) - {issue['fix']}"
292
+ )
293
+
294
+ # Exit with error if critical issues found
295
+ if result["summary"]["critical"] > 0:
296
+ sys.exit(1)
297
+
298
+
299
+ if __name__ == "__main__":
300
+ main()
@@ -0,0 +1,326 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ iOS App Launcher - App Lifecycle Control
4
+
5
+ Launches, terminates, and manages iOS apps in the simulator.
6
+ Handles deep links and app switching.
7
+
8
+ Usage: python scripts/app_launcher.py --launch com.example.app
9
+ """
10
+
11
+ import argparse
12
+ import contextlib
13
+ import subprocess
14
+ import sys
15
+ import time
16
+
17
+ from common import build_simctl_command, resolve_udid
18
+ from common.env_config import env_float, env_int
19
+
20
+ RELAUNCH_DELAY_SECONDS = env_float("IOS_SIM_RELAUNCH_DELAY_MS", 1000.0) / 1000.0
21
+ APPS_PREVIEW = env_int("IOS_SIM_APPS_PREVIEW", 30)
22
+
23
+
24
+ class AppLauncher:
25
+ """Controls app lifecycle on iOS simulator."""
26
+
27
+ def __init__(self, udid: str | None = None):
28
+ """Initialize app launcher."""
29
+ self.udid = udid
30
+
31
+ def launch(self, bundle_id: str, wait_for_debugger: bool = False) -> tuple[bool, int | None]:
32
+ """
33
+ Launch an app.
34
+
35
+ Args:
36
+ bundle_id: App bundle identifier
37
+ wait_for_debugger: Wait for debugger attachment
38
+
39
+ Returns:
40
+ (success, pid) tuple
41
+ """
42
+ cmd = build_simctl_command("launch", self.udid, bundle_id)
43
+
44
+ if wait_for_debugger:
45
+ cmd.insert(3, "--wait-for-debugger") # Insert after "launch" operation
46
+
47
+ try:
48
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
49
+ # Parse PID from output if available
50
+ pid = None
51
+ if result.stdout:
52
+ # Output format: "com.example.app: <PID>"
53
+ parts = result.stdout.strip().split(":")
54
+ if len(parts) > 1:
55
+ with contextlib.suppress(ValueError):
56
+ pid = int(parts[1].strip())
57
+ return (True, pid)
58
+ except subprocess.CalledProcessError:
59
+ return (False, None)
60
+
61
+ def terminate(self, bundle_id: str) -> bool:
62
+ """
63
+ Terminate an app.
64
+
65
+ Args:
66
+ bundle_id: App bundle identifier
67
+
68
+ Returns:
69
+ Success status
70
+ """
71
+ cmd = build_simctl_command("terminate", self.udid, bundle_id)
72
+
73
+ try:
74
+ subprocess.run(cmd, capture_output=True, check=True)
75
+ return True
76
+ except subprocess.CalledProcessError:
77
+ return False
78
+
79
+ def install(self, app_path: str) -> bool:
80
+ """
81
+ Install an app.
82
+
83
+ Args:
84
+ app_path: Path to .app bundle
85
+
86
+ Returns:
87
+ Success status
88
+ """
89
+ cmd = build_simctl_command("install", self.udid, app_path)
90
+
91
+ try:
92
+ subprocess.run(cmd, capture_output=True, check=True)
93
+ return True
94
+ except subprocess.CalledProcessError:
95
+ return False
96
+
97
+ def uninstall(self, bundle_id: str) -> bool:
98
+ """
99
+ Uninstall an app.
100
+
101
+ Args:
102
+ bundle_id: App bundle identifier
103
+
104
+ Returns:
105
+ Success status
106
+ """
107
+ cmd = build_simctl_command("uninstall", self.udid, bundle_id)
108
+
109
+ try:
110
+ subprocess.run(cmd, capture_output=True, check=True)
111
+ return True
112
+ except subprocess.CalledProcessError:
113
+ return False
114
+
115
+ def open_url(self, url: str) -> bool:
116
+ """
117
+ Open URL (for deep linking).
118
+
119
+ Args:
120
+ url: URL to open (http://, myapp://, etc.)
121
+
122
+ Returns:
123
+ Success status
124
+ """
125
+ cmd = build_simctl_command("openurl", self.udid, url)
126
+
127
+ try:
128
+ subprocess.run(cmd, capture_output=True, check=True)
129
+ return True
130
+ except subprocess.CalledProcessError:
131
+ return False
132
+
133
+ def list_apps(self) -> list[dict[str, str]]:
134
+ """
135
+ List installed apps.
136
+
137
+ Returns:
138
+ List of app info dictionaries
139
+ """
140
+ cmd = build_simctl_command("listapps", self.udid)
141
+
142
+ try:
143
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
144
+
145
+ # Parse plist output using plutil to convert to JSON
146
+ plist_data = result.stdout
147
+
148
+ # Use plutil to convert plist to JSON
149
+ convert_cmd = ["plutil", "-convert", "json", "-o", "-", "-"]
150
+ convert_result = subprocess.run(
151
+ convert_cmd, check=False, input=plist_data, capture_output=True, text=True
152
+ )
153
+
154
+ apps = []
155
+ if convert_result.returncode == 0:
156
+ import json
157
+
158
+ try:
159
+ data = json.loads(convert_result.stdout)
160
+ for bundle_id, app_info in data.items():
161
+ # Skip system internal apps that are hidden
162
+ if app_info.get("ApplicationType") == "Hidden":
163
+ continue
164
+
165
+ apps.append(
166
+ {
167
+ "bundle_id": bundle_id,
168
+ "name": app_info.get(
169
+ "CFBundleDisplayName", app_info.get("CFBundleName", bundle_id)
170
+ ),
171
+ "path": app_info.get("Path", ""),
172
+ "version": app_info.get("CFBundleVersion", "Unknown"),
173
+ "type": app_info.get("ApplicationType", "User"),
174
+ }
175
+ )
176
+ except json.JSONDecodeError:
177
+ pass
178
+
179
+ return apps
180
+ except subprocess.CalledProcessError:
181
+ return []
182
+
183
+ def get_app_state(self, bundle_id: str) -> str:
184
+ """
185
+ Get app state (running, suspended, etc.).
186
+
187
+ Args:
188
+ bundle_id: App bundle identifier
189
+
190
+ Returns:
191
+ State string or 'unknown'
192
+ """
193
+ # Check if app is running by trying to get its PID
194
+ cmd = build_simctl_command("spawn", self.udid, "launchctl", "list")
195
+
196
+ try:
197
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
198
+ if bundle_id in result.stdout:
199
+ return "running"
200
+ return "not running"
201
+ except subprocess.CalledProcessError:
202
+ return "unknown"
203
+
204
+ def restart_app(self, bundle_id: str, delay: float = RELAUNCH_DELAY_SECONDS) -> bool:
205
+ """
206
+ Restart an app (terminate then launch).
207
+
208
+ Args:
209
+ bundle_id: App bundle identifier
210
+ delay: Delay between terminate and launch
211
+
212
+ Returns:
213
+ Success status
214
+ """
215
+ # Terminate
216
+ self.terminate(bundle_id)
217
+ time.sleep(delay)
218
+
219
+ # Launch
220
+ success, _ = self.launch(bundle_id)
221
+ return success
222
+
223
+
224
+ def main():
225
+ """Main entry point."""
226
+ parser = argparse.ArgumentParser(description="Control iOS app lifecycle")
227
+
228
+ # Actions
229
+ parser.add_argument("--launch", help="Launch app by bundle ID")
230
+ parser.add_argument("--terminate", help="Terminate app by bundle ID")
231
+ parser.add_argument("--restart", help="Restart app by bundle ID")
232
+ parser.add_argument("--install", help="Install app from .app path")
233
+ parser.add_argument("--uninstall", help="Uninstall app by bundle ID")
234
+ parser.add_argument("--open-url", help="Open URL (deep link)")
235
+ parser.add_argument("--list", action="store_true", help="List installed apps")
236
+ parser.add_argument("--state", help="Get app state by bundle ID")
237
+
238
+ # Options
239
+ parser.add_argument(
240
+ "--wait-for-debugger", action="store_true", help="Wait for debugger when launching"
241
+ )
242
+ parser.add_argument(
243
+ "--udid",
244
+ help="Device UDID (auto-detects booted simulator if not provided)",
245
+ )
246
+
247
+ args = parser.parse_args()
248
+
249
+ # Resolve UDID with auto-detection
250
+ try:
251
+ udid = resolve_udid(args.udid)
252
+ except RuntimeError as e:
253
+ print(f"Error: {e}")
254
+ sys.exit(1)
255
+
256
+ launcher = AppLauncher(udid=udid)
257
+
258
+ # Execute requested action
259
+ if args.launch:
260
+ success, pid = launcher.launch(args.launch, args.wait_for_debugger)
261
+ if success:
262
+ if pid:
263
+ print(f"Launched {args.launch} (PID: {pid})")
264
+ else:
265
+ print(f"Launched {args.launch}")
266
+ else:
267
+ print(f"Failed to launch {args.launch}")
268
+ sys.exit(1)
269
+
270
+ elif args.terminate:
271
+ if launcher.terminate(args.terminate):
272
+ print(f"Terminated {args.terminate}")
273
+ else:
274
+ print(f"Failed to terminate {args.terminate}")
275
+ sys.exit(1)
276
+
277
+ elif args.restart:
278
+ if launcher.restart_app(args.restart):
279
+ print(f"Restarted {args.restart}")
280
+ else:
281
+ print(f"Failed to restart {args.restart}")
282
+ sys.exit(1)
283
+
284
+ elif args.install:
285
+ if launcher.install(args.install):
286
+ print(f"Installed {args.install}")
287
+ else:
288
+ print(f"Failed to install {args.install}")
289
+ sys.exit(1)
290
+
291
+ elif args.uninstall:
292
+ if launcher.uninstall(args.uninstall):
293
+ print(f"Uninstalled {args.uninstall}")
294
+ else:
295
+ print(f"Failed to uninstall {args.uninstall}")
296
+ sys.exit(1)
297
+
298
+ elif args.open_url:
299
+ if launcher.open_url(args.open_url):
300
+ print(f"Opened URL: {args.open_url}")
301
+ else:
302
+ print(f"Failed to open URL: {args.open_url}")
303
+ sys.exit(1)
304
+
305
+ elif args.list:
306
+ apps = launcher.list_apps()
307
+ if apps:
308
+ print(f"Installed apps ({len(apps)}):")
309
+ for app in apps[:APPS_PREVIEW]:
310
+ print(f" {app['bundle_id']}: {app['name']} (v{app['version']})")
311
+ if len(apps) > APPS_PREVIEW:
312
+ print(f" ... and {len(apps) - APPS_PREVIEW} more")
313
+ else:
314
+ print("No apps found or failed to list")
315
+
316
+ elif args.state:
317
+ state = launcher.get_app_state(args.state)
318
+ print(f"{args.state}: {state}")
319
+
320
+ else:
321
+ parser.print_help()
322
+ sys.exit(1)
323
+
324
+
325
+ if __name__ == "__main__":
326
+ main()