@leejungkiin/awkit 1.7.0 → 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 (241) 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 +40 -7
  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/verification-gate/SKILL.md +4 -2
  197. package/skills/video-edit/SKILL.md +36 -0
  198. package/skills/video-edit/scripts/video_edit.py +324 -0
  199. package/templates/setup-mapping.json +48 -0
  200. package/templates/specs/design-template.md +161 -71
  201. package/templates/specs/requirements-template.md +65 -133
  202. package/templates/specs/task-spec-template.xml +3 -0
  203. package/workflows/_uncategorized/critic.md +40 -0
  204. package/workflows/_uncategorized/git-rebase-flow.md +81 -0
  205. package/workflows/_uncategorized/image-gen.md +118 -0
  206. package/workflows/_uncategorized/multi-model-pipeline.md +60 -0
  207. package/workflows/_uncategorized/pixel-gen.md +86 -0
  208. package/workflows/_uncategorized/pixel-setup.md +90 -0
  209. package/workflows/_uncategorized/ponytail-review.md +59 -0
  210. package/workflows/_uncategorized/reverse-android-build.md +222 -0
  211. package/workflows/_uncategorized/reverse-android-design.md +139 -0
  212. package/workflows/_uncategorized/reverse-android-discover.md +150 -0
  213. package/workflows/_uncategorized/reverse-android-scan.md +158 -0
  214. package/workflows/_uncategorized/reverse-android.md +143 -0
  215. package/workflows/_uncategorized/reverse-ios-build.md +240 -0
  216. package/workflows/_uncategorized/reverse-ios-design.md +112 -0
  217. package/workflows/_uncategorized/reverse-ios-discover.md +120 -0
  218. package/workflows/_uncategorized/reverse-ios-scan.md +155 -0
  219. package/workflows/_uncategorized/reverse-ios.md +152 -0
  220. package/workflows/_uncategorized/safety-router.md +34 -0
  221. package/workflows/_uncategorized/teach.md +89 -0
  222. package/workflows/_uncategorized/verify-ui.md +53 -0
  223. package/workflows/_uncategorized/visualize-screenshots.md +34 -0
  224. package/workflows/ads/ads-analyst.md +201 -0
  225. package/workflows/ads/ads-audit.md +106 -0
  226. package/workflows/ads/ads-optimize.md +97 -0
  227. package/workflows/ads/ads-targeting.md +241 -0
  228. package/workflows/ads/adsExpert.md +160 -0
  229. package/workflows/ads/smali-ads-config.md +400 -0
  230. package/workflows/ads/smali-ads-flow.md +331 -0
  231. package/workflows/ads/smali-ads-interstitial.md +377 -0
  232. package/workflows/ads/smali-ads-native.md +382 -0
  233. package/workflows/context/teach.md +89 -0
  234. package/workflows/gitnexus.md +8 -8
  235. package/workflows/lifecycle/brainstorm.md +43 -0
  236. package/workflows/lifecycle/code.md +5 -0
  237. package/workflows/lifecycle/init.md +23 -5
  238. package/workflows/lifecycle/multi-model-pipeline.md +60 -0
  239. package/workflows/quality/ponytail-review.md +59 -0
  240. package/workflows/roles/critic.md +40 -0
  241. package/workflows/roles/safety-router.md +34 -0
@@ -0,0 +1,178 @@
1
+ """
2
+ Configuration management for iOS Simulator Skill.
3
+
4
+ Handles loading, validation, and auto-updating of project-local config files.
5
+ """
6
+
7
+ import json
8
+ import sys
9
+ from datetime import UTC, datetime
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+
14
+ class Config:
15
+ """
16
+ Project-local configuration with auto-learning.
17
+
18
+ Config file location: .claude/skills/<skill-directory-name>/config.json
19
+
20
+ The skill directory name is auto-detected from the installation location,
21
+ so configs work regardless of what users name the skill directory.
22
+
23
+ Auto-updates last_used_simulator after successful builds.
24
+ """
25
+
26
+ DEFAULT_CONFIG = {
27
+ "device": {
28
+ "preferred_simulator": None,
29
+ "preferred_os_version": None,
30
+ "fallback_to_any_iphone": True,
31
+ "last_used_simulator": None,
32
+ "last_used_at": None,
33
+ }
34
+ }
35
+
36
+ def __init__(self, data: dict[str, Any], config_path: Path):
37
+ """
38
+ Initialize config.
39
+
40
+ Args:
41
+ data: Config data dict
42
+ config_path: Path to config file
43
+ """
44
+ self.data = data
45
+ self.config_path = config_path
46
+
47
+ @staticmethod
48
+ def load(project_dir: Path | None = None) -> "Config":
49
+ """
50
+ Load config from project directory.
51
+
52
+ Args:
53
+ project_dir: Project root (defaults to cwd)
54
+
55
+ Returns:
56
+ Config instance (creates default if not found)
57
+
58
+ Note:
59
+ The skill directory name is auto-detected from the installation location,
60
+ so configs work regardless of what users name the skill directory.
61
+ """
62
+ if project_dir is None:
63
+ project_dir = Path.cwd()
64
+
65
+ # Auto-detect skill directory name from actual installation location
66
+ # This file is at: skill/scripts/xcode/config.py
67
+ # Navigate up to skill/ directory and use its name
68
+ skill_root = Path(__file__).parent.parent.parent # xcode/ -> scripts/ -> skill/
69
+ skill_name = skill_root.name
70
+
71
+ config_path = project_dir / ".claude" / "skills" / skill_name / "config.json"
72
+
73
+ # Load existing config
74
+ if config_path.exists():
75
+ try:
76
+ with open(config_path) as f:
77
+ data = json.load(f)
78
+
79
+ # Merge with defaults (in case new fields added)
80
+ merged = Config._merge_with_defaults(data)
81
+ return Config(merged, config_path)
82
+
83
+ except json.JSONDecodeError as e:
84
+ print(f"Warning: Invalid JSON in {config_path}: {e}", file=sys.stderr)
85
+ print("Using default config", file=sys.stderr)
86
+ return Config(Config.DEFAULT_CONFIG.copy(), config_path)
87
+ except Exception as e:
88
+ print(f"Warning: Could not load config: {e}", file=sys.stderr)
89
+ return Config(Config.DEFAULT_CONFIG.copy(), config_path)
90
+
91
+ # Return default config (will be created on first save)
92
+ return Config(Config.DEFAULT_CONFIG.copy(), config_path)
93
+
94
+ @staticmethod
95
+ def _merge_with_defaults(data: dict[str, Any]) -> dict[str, Any]:
96
+ """
97
+ Merge user config with defaults.
98
+
99
+ Args:
100
+ data: User config data
101
+
102
+ Returns:
103
+ Merged config with all default fields
104
+ """
105
+ merged = Config.DEFAULT_CONFIG.copy()
106
+
107
+ # Deep merge device section
108
+ if "device" in data:
109
+ merged["device"].update(data["device"])
110
+
111
+ return merged
112
+
113
+ def save(self) -> None:
114
+ """
115
+ Save config to file atomically.
116
+
117
+ Uses temp file + rename for atomic writes.
118
+ Creates parent directories if needed.
119
+ """
120
+ try:
121
+ # Create parent directories
122
+ self.config_path.parent.mkdir(parents=True, exist_ok=True)
123
+
124
+ # Atomic write: temp file + rename
125
+ temp_path = self.config_path.with_suffix(".tmp")
126
+
127
+ with open(temp_path, "w") as f:
128
+ json.dump(self.data, f, indent=2)
129
+ f.write("\n") # Trailing newline
130
+
131
+ # Atomic rename
132
+ temp_path.replace(self.config_path)
133
+
134
+ except Exception as e:
135
+ print(f"Warning: Could not save config: {e}", file=sys.stderr)
136
+
137
+ def update_last_used_simulator(self, name: str) -> None:
138
+ """
139
+ Update last used simulator and timestamp.
140
+
141
+ Args:
142
+ name: Simulator name (e.g., "iPhone 16 Pro")
143
+ """
144
+ self.data["device"]["last_used_simulator"] = name
145
+ self.data["device"]["last_used_at"] = datetime.now(UTC).isoformat()
146
+
147
+ def get_preferred_simulator(self) -> str | None:
148
+ """
149
+ Get preferred simulator.
150
+
151
+ Returns:
152
+ Simulator name or None
153
+
154
+ Priority:
155
+ 1. preferred_simulator (manual preference)
156
+ 2. last_used_simulator (auto-learned)
157
+ 3. None (use auto-detection)
158
+ """
159
+ device = self.data.get("device", {})
160
+
161
+ # Manual preference takes priority
162
+ if device.get("preferred_simulator"):
163
+ return device["preferred_simulator"]
164
+
165
+ # Auto-learned preference
166
+ if device.get("last_used_simulator"):
167
+ return device["last_used_simulator"]
168
+
169
+ return None
170
+
171
+ def should_fallback_to_any_iphone(self) -> bool:
172
+ """
173
+ Check if fallback to any iPhone is enabled.
174
+
175
+ Returns:
176
+ True if should fallback, False otherwise
177
+ """
178
+ return self.data.get("device", {}).get("fallback_to_any_iphone", True)
@@ -0,0 +1,343 @@
1
+ """
2
+ Build/test output formatting.
3
+
4
+ Provides multiple output formats with progressive disclosure support.
5
+ """
6
+
7
+ import json
8
+
9
+ from common.env_config import env_int
10
+
11
+ BUILD_SUMMARY_CAP = env_int("IOS_SIM_BUILD_SUMMARY_CAP", 15)
12
+ BUILD_VERBOSE_CAP = env_int("IOS_SIM_BUILD_VERBOSE_CAP", 100)
13
+ BUILD_LOG_TAIL = env_int("IOS_SIM_LOG_TAIL", 200)
14
+
15
+
16
+ class OutputFormatter:
17
+ """
18
+ Format build/test results for display.
19
+
20
+ Supports ultra-minimal default output, verbose mode, and JSON output.
21
+ """
22
+
23
+ @staticmethod
24
+ def format_minimal(
25
+ status: str,
26
+ error_count: int,
27
+ warning_count: int,
28
+ xcresult_id: str,
29
+ test_info: dict | None = None,
30
+ hints: list[str] | None = None,
31
+ errors: list[dict] | None = None,
32
+ failed_tests: list[dict] | None = None,
33
+ ) -> str:
34
+ """
35
+ Format ultra-minimal output (5-10 tokens on success, more on failure).
36
+
37
+ On failure, automatically surfaces top errors/failed tests inline so agents
38
+ don't need a second round-trip with --get-errors.
39
+
40
+ Args:
41
+ status: Build status (SUCCESS/FAILED)
42
+ error_count: Number of errors
43
+ warning_count: Number of warnings
44
+ xcresult_id: XCResult bundle ID
45
+ test_info: Optional test results dict
46
+ hints: Optional list of actionable hints
47
+ errors: Optional error list to surface on failure
48
+ failed_tests: Optional failed test list to surface on failure
49
+
50
+ Returns:
51
+ Minimal formatted string
52
+ """
53
+ lines = []
54
+
55
+ if test_info:
56
+ # Test mode
57
+ total = test_info.get("total", 0)
58
+ passed = test_info.get("passed", 0)
59
+ failed = test_info.get("failed", 0)
60
+ duration = test_info.get("duration", 0.0)
61
+
62
+ test_status = "PASS" if failed == 0 else "FAIL"
63
+ lines.append(
64
+ f"Tests: {test_status} ({passed}/{total} passed, {duration:.1f}s) [{xcresult_id}]"
65
+ )
66
+ else:
67
+ # Build mode
68
+ lines.append(
69
+ f"Build: {status} ({error_count} errors, {warning_count} warnings) [{xcresult_id}]"
70
+ )
71
+
72
+ # Surface errors inline on failure
73
+ if status == "FAILED" and errors:
74
+ lines.append("")
75
+ lines.append(OutputFormatter.format_errors(errors, limit=BUILD_SUMMARY_CAP))
76
+
77
+ # Surface failed tests inline on failure
78
+ if failed_tests:
79
+ lines.append("")
80
+ lines.append(
81
+ OutputFormatter.format_test_failures(failed_tests, limit=BUILD_SUMMARY_CAP)
82
+ )
83
+
84
+ # Add hints if provided and build failed
85
+ if hints and status == "FAILED":
86
+ lines.append("")
87
+ lines.extend(hints)
88
+
89
+ return "\n".join(lines)
90
+
91
+ @staticmethod
92
+ def format_test_failures(failed_tests: list[dict], limit: int = BUILD_SUMMARY_CAP) -> str:
93
+ """
94
+ Format failed test details.
95
+
96
+ Args:
97
+ failed_tests: List of dicts with test_name and failure_message
98
+ limit: Maximum failures to show
99
+
100
+ Returns:
101
+ Formatted failure list
102
+ """
103
+ if not failed_tests:
104
+ return "No test failures found."
105
+
106
+ lines = [f"Failed tests ({len(failed_tests)}):"]
107
+ lines.append("")
108
+
109
+ for i, test in enumerate(failed_tests[:limit], 1):
110
+ name = test.get("test_name", "Unknown")
111
+ message = test.get("failure_message", "")
112
+ lines.append(f"{i}. {name}")
113
+ if message:
114
+ lines.append(f" {message}")
115
+ lines.append("")
116
+
117
+ if len(failed_tests) > limit:
118
+ lines.append(f"... and {len(failed_tests) - limit} more failures")
119
+
120
+ return "\n".join(lines)
121
+
122
+ @staticmethod
123
+ def format_errors(errors: list[dict], limit: int = BUILD_VERBOSE_CAP) -> str:
124
+ """
125
+ Format error details.
126
+
127
+ Args:
128
+ errors: List of error dicts
129
+ limit: Maximum errors to show
130
+
131
+ Returns:
132
+ Formatted error list
133
+ """
134
+ if not errors:
135
+ return "No errors found."
136
+
137
+ lines = [f"Errors ({len(errors)}):"]
138
+ lines.append("")
139
+
140
+ for i, error in enumerate(errors[:limit], 1):
141
+ message = error.get("message", "Unknown error")
142
+ location = error.get("location", {})
143
+
144
+ # Format location
145
+ loc_parts = []
146
+ if location.get("file"):
147
+ file_path = location["file"].replace("file://", "")
148
+ loc_parts.append(file_path)
149
+ if location.get("line"):
150
+ loc_parts.append(f"line {location['line']}")
151
+
152
+ location_str = ":".join(loc_parts) if loc_parts else "unknown location"
153
+
154
+ lines.append(f"{i}. {message}")
155
+ lines.append(f" Location: {location_str}")
156
+ lines.append("")
157
+
158
+ if len(errors) > limit:
159
+ lines.append(f"... and {len(errors) - limit} more errors")
160
+
161
+ return "\n".join(lines)
162
+
163
+ @staticmethod
164
+ def format_warnings(warnings: list[dict], limit: int = BUILD_VERBOSE_CAP) -> str:
165
+ """
166
+ Format warning details.
167
+
168
+ Args:
169
+ warnings: List of warning dicts
170
+ limit: Maximum warnings to show
171
+
172
+ Returns:
173
+ Formatted warning list
174
+ """
175
+ if not warnings:
176
+ return "No warnings found."
177
+
178
+ lines = [f"Warnings ({len(warnings)}):"]
179
+ lines.append("")
180
+
181
+ for i, warning in enumerate(warnings[:limit], 1):
182
+ message = warning.get("message", "Unknown warning")
183
+ location = warning.get("location", {})
184
+
185
+ # Format location
186
+ loc_parts = []
187
+ if location.get("file"):
188
+ file_path = location["file"].replace("file://", "")
189
+ loc_parts.append(file_path)
190
+ if location.get("line"):
191
+ loc_parts.append(f"line {location['line']}")
192
+
193
+ location_str = ":".join(loc_parts) if loc_parts else "unknown location"
194
+
195
+ lines.append(f"{i}. {message}")
196
+ lines.append(f" Location: {location_str}")
197
+ lines.append("")
198
+
199
+ if len(warnings) > limit:
200
+ lines.append(f"... and {len(warnings) - limit} more warnings")
201
+
202
+ return "\n".join(lines)
203
+
204
+ @staticmethod
205
+ def format_log(log: str, lines: int = BUILD_LOG_TAIL) -> str:
206
+ """
207
+ Format build log (show last N lines).
208
+
209
+ Args:
210
+ log: Full build log
211
+ lines: Number of lines to show
212
+
213
+ Returns:
214
+ Formatted log excerpt
215
+ """
216
+ if not log:
217
+ return "No build log available."
218
+
219
+ log_lines = log.strip().split("\n")
220
+
221
+ if len(log_lines) <= lines:
222
+ return log
223
+
224
+ # Show last N lines
225
+ excerpt = log_lines[-lines:]
226
+ return f"... (showing last {lines} lines of {len(log_lines)})\n\n" + "\n".join(excerpt)
227
+
228
+ @staticmethod
229
+ def format_json(data: dict) -> str:
230
+ """
231
+ Format data as JSON.
232
+
233
+ Args:
234
+ data: Data to format
235
+
236
+ Returns:
237
+ Pretty-printed JSON string
238
+ """
239
+ return json.dumps(data, indent=2)
240
+
241
+ @staticmethod
242
+ def generate_hints(errors: list[dict]) -> list[str]:
243
+ """
244
+ Generate actionable hints based on error types.
245
+
246
+ Args:
247
+ errors: List of error dicts
248
+
249
+ Returns:
250
+ List of hint strings
251
+ """
252
+ hints = []
253
+ error_types: set[str] = set()
254
+
255
+ # Collect error types
256
+ for error in errors:
257
+ error_type = error.get("type", "unknown")
258
+ error_types.add(error_type)
259
+
260
+ # Generate hints based on error types
261
+ if "provisioning" in error_types:
262
+ hints.append("Provisioning profile issue detected:")
263
+ hints.append(" • Ensure you have a valid provisioning profile for iOS Simulator")
264
+ hints.append(
265
+ ' • For simulator builds, use CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO'
266
+ )
267
+ hints.append(" • Or specify simulator explicitly: --simulator 'iPhone 16 Pro'")
268
+
269
+ if "signing" in error_types:
270
+ hints.append("Code signing issue detected:")
271
+ hints.append(" • For simulator builds, code signing is not required")
272
+ hints.append(" • Ensure build settings target iOS Simulator, not physical device")
273
+ hints.append(" • Check destination: platform=iOS Simulator,name=<device>")
274
+
275
+ if not error_types or "build" in error_types:
276
+ # Generic hints when error type is unknown
277
+ if any("destination" in error.get("message", "").lower() for error in errors):
278
+ hints.append("Device selection issue detected:")
279
+ hints.append(" • List available simulators: xcrun simctl list devices available")
280
+ hints.append(" • Specify simulator: --simulator 'iPhone 16 Pro'")
281
+
282
+ return hints
283
+
284
+ @staticmethod
285
+ def format_verbose(
286
+ status: str,
287
+ error_count: int,
288
+ warning_count: int,
289
+ xcresult_id: str,
290
+ errors: list[dict] | None = None,
291
+ warnings: list[dict] | None = None,
292
+ test_info: dict | None = None,
293
+ ) -> str:
294
+ """
295
+ Format verbose output with error/warning details.
296
+
297
+ Args:
298
+ status: Build status
299
+ error_count: Error count
300
+ warning_count: Warning count
301
+ xcresult_id: XCResult ID
302
+ errors: Optional error list
303
+ warnings: Optional warning list
304
+ test_info: Optional test results
305
+
306
+ Returns:
307
+ Verbose formatted output
308
+ """
309
+ lines = []
310
+
311
+ # Header
312
+ if test_info:
313
+ total = test_info.get("total", 0)
314
+ passed = test_info.get("passed", 0)
315
+ failed = test_info.get("failed", 0)
316
+ duration = test_info.get("duration", 0.0)
317
+
318
+ test_status = "PASS" if failed == 0 else "FAIL"
319
+ lines.append(f"Tests: {test_status}")
320
+ lines.append(f" Total: {total}")
321
+ lines.append(f" Passed: {passed}")
322
+ lines.append(f" Failed: {failed}")
323
+ lines.append(f" Duration: {duration:.1f}s")
324
+ else:
325
+ lines.append(f"Build: {status}")
326
+
327
+ lines.append(f"XCResult: {xcresult_id}")
328
+ lines.append("")
329
+
330
+ # Errors
331
+ if errors and len(errors) > 0:
332
+ lines.append(OutputFormatter.format_errors(errors, limit=BUILD_VERBOSE_CAP))
333
+ lines.append("")
334
+
335
+ # Warnings
336
+ if warnings and len(warnings) > 0:
337
+ lines.append(OutputFormatter.format_warnings(warnings, limit=BUILD_VERBOSE_CAP))
338
+ lines.append("")
339
+
340
+ # Summary
341
+ lines.append(f"Summary: {error_count} errors, {warning_count} warnings")
342
+
343
+ return "\n".join(lines)