@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,397 @@
1
+ """
2
+ Xcode build execution.
3
+
4
+ Handles xcodebuild command construction and execution with xcresult generation.
5
+ """
6
+
7
+ import re
8
+ import subprocess
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ from common.env_config import env_int
13
+
14
+ from .cache import XCResultCache
15
+ from .config import Config
16
+
17
+ BUILD_TIMEOUT = env_int("IOS_SIM_BUILD_TIMEOUT", 1800)
18
+ TEST_TIMEOUT = env_int("IOS_SIM_TEST_TIMEOUT", 2700)
19
+ INTROSPECT_TIMEOUT = env_int("IOS_SIM_INTROSPECT_TIMEOUT", 60)
20
+
21
+
22
+ class BuildRunner:
23
+ """
24
+ Execute xcodebuild commands with xcresult bundle generation.
25
+
26
+ Handles scheme auto-detection, command construction, and build/test execution.
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ project_path: str | None = None,
32
+ workspace_path: str | None = None,
33
+ scheme: str | None = None,
34
+ configuration: str = "Debug",
35
+ simulator: str | None = None,
36
+ cache: XCResultCache | None = None,
37
+ ):
38
+ """
39
+ Initialize build runner.
40
+
41
+ Args:
42
+ project_path: Path to .xcodeproj
43
+ workspace_path: Path to .xcworkspace
44
+ scheme: Build scheme (auto-detected if not provided)
45
+ configuration: Build configuration (Debug/Release)
46
+ simulator: Simulator name
47
+ cache: XCResult cache (creates default if not provided)
48
+ """
49
+ self.project_path = project_path
50
+ self.workspace_path = workspace_path
51
+ self.scheme = scheme
52
+ self.configuration = configuration
53
+ self.simulator = simulator
54
+ self.cache = cache or XCResultCache()
55
+
56
+ def auto_detect_scheme(self) -> str | None:
57
+ """
58
+ Auto-detect build scheme from project/workspace.
59
+
60
+ Returns:
61
+ Detected scheme name or None
62
+ """
63
+ cmd = ["xcodebuild", "-list"]
64
+
65
+ if self.workspace_path:
66
+ cmd.extend(["-workspace", self.workspace_path])
67
+ elif self.project_path:
68
+ cmd.extend(["-project", self.project_path])
69
+ else:
70
+ return None
71
+
72
+ try:
73
+ result = subprocess.run(
74
+ cmd, capture_output=True, text=True, check=True, timeout=INTROSPECT_TIMEOUT
75
+ )
76
+
77
+ # Parse schemes from output
78
+ in_schemes_section = False
79
+ for line in result.stdout.split("\n"):
80
+ line = line.strip()
81
+
82
+ if "Schemes:" in line:
83
+ in_schemes_section = True
84
+ continue
85
+
86
+ if in_schemes_section and line and not line.startswith("Build"):
87
+ # First scheme in list
88
+ return line
89
+
90
+ except subprocess.CalledProcessError as e:
91
+ print(f"Error auto-detecting scheme: {e}", file=sys.stderr)
92
+
93
+ return None
94
+
95
+ def get_simulator_destination(self) -> str:
96
+ """
97
+ Get xcodebuild destination string.
98
+
99
+ Uses config preferences with fallback to auto-detection.
100
+
101
+ Priority:
102
+ 1. --simulator CLI flag (self.simulator)
103
+ 2. Config preferred_simulator
104
+ 3. Config last_used_simulator
105
+ 4. Auto-detect first iPhone
106
+ 5. Generic iOS Simulator
107
+
108
+ Returns:
109
+ Destination string for -destination flag
110
+ """
111
+ # Priority 1: CLI flag
112
+ if self.simulator:
113
+ return f"platform=iOS Simulator,name={self.simulator}"
114
+
115
+ # Priority 2-3: Config preferences
116
+ try:
117
+ # Determine project directory from project/workspace path
118
+ project_dir = None
119
+ if self.project_path:
120
+ project_dir = Path(self.project_path).parent
121
+ elif self.workspace_path:
122
+ project_dir = Path(self.workspace_path).parent
123
+
124
+ config = Config.load(project_dir=project_dir)
125
+ preferred = config.get_preferred_simulator()
126
+
127
+ if preferred:
128
+ # Check if preferred simulator exists
129
+ if self._simulator_exists(preferred):
130
+ return f"platform=iOS Simulator,name={preferred}"
131
+ print(f"Warning: Preferred simulator '{preferred}' not available", file=sys.stderr)
132
+ if config.should_fallback_to_any_iphone():
133
+ print("Falling back to auto-detection...", file=sys.stderr)
134
+ else:
135
+ # Strict mode: don't fallback
136
+ return f"platform=iOS Simulator,name={preferred}"
137
+
138
+ except Exception as e:
139
+ print(f"Warning: Could not load config: {e}", file=sys.stderr)
140
+
141
+ # Priority 4-5: Auto-detect
142
+ return self._auto_detect_simulator()
143
+
144
+ def _simulator_exists(self, name: str) -> bool:
145
+ """
146
+ Check if simulator with given name exists and is available.
147
+
148
+ Args:
149
+ name: Simulator name (e.g., "iPhone 16 Pro")
150
+
151
+ Returns:
152
+ True if simulator exists and is available
153
+ """
154
+ try:
155
+ result = subprocess.run(
156
+ ["xcrun", "simctl", "list", "devices", "available", "iOS"],
157
+ capture_output=True,
158
+ text=True,
159
+ check=True,
160
+ timeout=INTROSPECT_TIMEOUT,
161
+ )
162
+
163
+ # Check if simulator name appears in available devices
164
+ return any(name in line and "(" in line for line in result.stdout.split("\n"))
165
+
166
+ except subprocess.CalledProcessError:
167
+ return False
168
+
169
+ def _extract_simulator_name_from_destination(self, destination: str) -> str | None:
170
+ """
171
+ Extract simulator name from destination string.
172
+
173
+ Args:
174
+ destination: Destination string (e.g., "platform=iOS Simulator,name=iPhone 16 Pro")
175
+
176
+ Returns:
177
+ Simulator name or None
178
+ """
179
+ # Pattern: name=<simulator name>
180
+ match = re.search(r"name=([^,]+)", destination)
181
+ if match:
182
+ return match.group(1).strip()
183
+ return None
184
+
185
+ def _auto_detect_simulator(self) -> str:
186
+ """
187
+ Auto-detect best available iOS simulator.
188
+
189
+ Returns:
190
+ Destination string for -destination flag
191
+ """
192
+ try:
193
+ result = subprocess.run(
194
+ ["xcrun", "simctl", "list", "devices", "available", "iOS"],
195
+ capture_output=True,
196
+ text=True,
197
+ check=True,
198
+ timeout=INTROSPECT_TIMEOUT,
199
+ )
200
+
201
+ # Parse available simulators, prefer latest iPhone
202
+ # Looking for lines like: "iPhone 16 Pro (12345678-1234-1234-1234-123456789012) (Shutdown)"
203
+ for line in result.stdout.split("\n"):
204
+ if "iPhone" in line and "(" in line:
205
+ # Extract device name
206
+ name = line.split("(")[0].strip()
207
+ if name:
208
+ return f"platform=iOS Simulator,name={name}"
209
+
210
+ # Fallback to generic iOS Simulator if no iPhone found
211
+ return "generic/platform=iOS Simulator"
212
+
213
+ except subprocess.CalledProcessError as e:
214
+ print(f"Warning: Could not auto-detect simulator: {e}", file=sys.stderr)
215
+ return "generic/platform=iOS Simulator"
216
+
217
+ def build(self, clean: bool = False) -> tuple[bool, str, str]:
218
+ """
219
+ Build the project.
220
+
221
+ Args:
222
+ clean: Perform clean build
223
+
224
+ Returns:
225
+ Tuple of (success: bool, xcresult_id: str, stderr: str)
226
+ """
227
+ # Auto-detect scheme if needed
228
+ if not self.scheme:
229
+ self.scheme = self.auto_detect_scheme()
230
+ if not self.scheme:
231
+ print("Error: Could not auto-detect scheme. Use --scheme", file=sys.stderr)
232
+ return (False, "", "")
233
+
234
+ # Generate xcresult ID and path
235
+ xcresult_id = self.cache.generate_id()
236
+ xcresult_path = self.cache.get_path(xcresult_id)
237
+
238
+ # Build command
239
+ cmd = ["xcodebuild", "-quiet"] # Suppress verbose output
240
+
241
+ if clean:
242
+ cmd.append("clean")
243
+
244
+ cmd.append("build")
245
+
246
+ if self.workspace_path:
247
+ cmd.extend(["-workspace", self.workspace_path])
248
+ elif self.project_path:
249
+ cmd.extend(["-project", self.project_path])
250
+ else:
251
+ print("Error: No project or workspace specified", file=sys.stderr)
252
+ return (False, "", "")
253
+
254
+ cmd.extend(
255
+ [
256
+ "-scheme",
257
+ self.scheme,
258
+ "-configuration",
259
+ self.configuration,
260
+ "-destination",
261
+ self.get_simulator_destination(),
262
+ "-resultBundlePath",
263
+ str(xcresult_path),
264
+ ]
265
+ )
266
+
267
+ # Execute build
268
+ try:
269
+ result = subprocess.run(
270
+ cmd,
271
+ capture_output=True,
272
+ text=True,
273
+ check=False, # Don't raise on non-zero exit
274
+ timeout=BUILD_TIMEOUT,
275
+ )
276
+
277
+ success = result.returncode == 0
278
+
279
+ # xcresult bundle should be created even on failure
280
+ if not xcresult_path.exists():
281
+ print("Warning: xcresult bundle was not created", file=sys.stderr)
282
+ return (success, "", result.stderr)
283
+
284
+ # Auto-update config with last used simulator (on success only)
285
+ if success:
286
+ try:
287
+ # Determine project directory from project/workspace path
288
+ project_dir = None
289
+ if self.project_path:
290
+ project_dir = Path(self.project_path).parent
291
+ elif self.workspace_path:
292
+ project_dir = Path(self.workspace_path).parent
293
+
294
+ config = Config.load(project_dir=project_dir)
295
+ destination = self.get_simulator_destination()
296
+ simulator_name = self._extract_simulator_name_from_destination(destination)
297
+
298
+ if simulator_name:
299
+ config.update_last_used_simulator(simulator_name)
300
+ config.save()
301
+
302
+ except Exception as e:
303
+ # Don't fail build if config update fails
304
+ print(f"Warning: Could not update config: {e}", file=sys.stderr)
305
+
306
+ return (success, xcresult_id, result.stderr)
307
+
308
+ except Exception as e:
309
+ print(f"Error executing build: {e}", file=sys.stderr)
310
+ return (False, "", str(e))
311
+
312
+ def test(self, test_suite: str | None = None) -> tuple[bool, str, str]:
313
+ """
314
+ Run tests.
315
+
316
+ Args:
317
+ test_suite: Specific test suite to run
318
+
319
+ Returns:
320
+ Tuple of (success: bool, xcresult_id: str, stderr: str)
321
+ """
322
+ # Auto-detect scheme if needed
323
+ if not self.scheme:
324
+ self.scheme = self.auto_detect_scheme()
325
+ if not self.scheme:
326
+ print("Error: Could not auto-detect scheme. Use --scheme", file=sys.stderr)
327
+ return (False, "", "")
328
+
329
+ # Generate xcresult ID and path
330
+ xcresult_id = self.cache.generate_id()
331
+ xcresult_path = self.cache.get_path(xcresult_id)
332
+
333
+ # Build command
334
+ cmd = ["xcodebuild", "-quiet", "test"]
335
+
336
+ if self.workspace_path:
337
+ cmd.extend(["-workspace", self.workspace_path])
338
+ elif self.project_path:
339
+ cmd.extend(["-project", self.project_path])
340
+ else:
341
+ print("Error: No project or workspace specified", file=sys.stderr)
342
+ return (False, "", "")
343
+
344
+ cmd.extend(
345
+ [
346
+ "-scheme",
347
+ self.scheme,
348
+ "-destination",
349
+ self.get_simulator_destination(),
350
+ "-resultBundlePath",
351
+ str(xcresult_path),
352
+ ]
353
+ )
354
+
355
+ if test_suite:
356
+ cmd.extend(["-only-testing", test_suite])
357
+
358
+ # Execute tests
359
+ try:
360
+ result = subprocess.run(
361
+ cmd, capture_output=True, text=True, check=False, timeout=TEST_TIMEOUT
362
+ )
363
+
364
+ success = result.returncode == 0
365
+
366
+ # xcresult bundle should be created even on failure
367
+ if not xcresult_path.exists():
368
+ print("Warning: xcresult bundle was not created", file=sys.stderr)
369
+ return (success, "", result.stderr)
370
+
371
+ # Auto-update config with last used simulator (on success only)
372
+ if success:
373
+ try:
374
+ # Determine project directory from project/workspace path
375
+ project_dir = None
376
+ if self.project_path:
377
+ project_dir = Path(self.project_path).parent
378
+ elif self.workspace_path:
379
+ project_dir = Path(self.workspace_path).parent
380
+
381
+ config = Config.load(project_dir=project_dir)
382
+ destination = self.get_simulator_destination()
383
+ simulator_name = self._extract_simulator_name_from_destination(destination)
384
+
385
+ if simulator_name:
386
+ config.update_last_used_simulator(simulator_name)
387
+ config.save()
388
+
389
+ except Exception as e:
390
+ # Don't fail test if config update fails
391
+ print(f"Warning: Could not update config: {e}", file=sys.stderr)
392
+
393
+ return (success, xcresult_id, result.stderr)
394
+
395
+ except Exception as e:
396
+ print(f"Error executing tests: {e}", file=sys.stderr)
397
+ return (False, "", str(e))
@@ -0,0 +1,204 @@
1
+ """
2
+ XCResult cache management.
3
+
4
+ Handles storage, retrieval, and lifecycle of xcresult bundles for progressive disclosure.
5
+ """
6
+
7
+ import shutil
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+
11
+
12
+ class XCResultCache:
13
+ """
14
+ Manage xcresult bundle cache for progressive disclosure.
15
+
16
+ Stores xcresult bundles with timestamp-based IDs and provides
17
+ retrieval and cleanup operations.
18
+ """
19
+
20
+ # Default cache directory
21
+ DEFAULT_CACHE_DIR = Path.home() / ".ios-simulator-skill" / "xcresults"
22
+
23
+ def __init__(self, cache_dir: Path | None = None):
24
+ """
25
+ Initialize cache manager.
26
+
27
+ Args:
28
+ cache_dir: Custom cache directory (uses default if not specified)
29
+ """
30
+ self.cache_dir = cache_dir or self.DEFAULT_CACHE_DIR
31
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
32
+
33
+ def generate_id(self, prefix: str = "xcresult") -> str:
34
+ """
35
+ Generate timestamped xcresult ID.
36
+
37
+ Args:
38
+ prefix: ID prefix (default: "xcresult")
39
+
40
+ Returns:
41
+ ID string like "xcresult-20251018-143052"
42
+ """
43
+ timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
44
+ return f"{prefix}-{timestamp}"
45
+
46
+ def get_path(self, xcresult_id: str) -> Path:
47
+ """
48
+ Get full path for xcresult ID.
49
+
50
+ Args:
51
+ xcresult_id: XCResult ID
52
+
53
+ Returns:
54
+ Path to xcresult bundle
55
+ """
56
+ # Handle both with and without .xcresult extension
57
+ if xcresult_id.endswith(".xcresult"):
58
+ return self.cache_dir / xcresult_id
59
+ return self.cache_dir / f"{xcresult_id}.xcresult"
60
+
61
+ def exists(self, xcresult_id: str) -> bool:
62
+ """
63
+ Check if xcresult bundle exists.
64
+
65
+ Args:
66
+ xcresult_id: XCResult ID
67
+
68
+ Returns:
69
+ True if bundle exists
70
+ """
71
+ return self.get_path(xcresult_id).exists()
72
+
73
+ def save(self, source_path: Path, xcresult_id: str | None = None) -> str:
74
+ """
75
+ Save xcresult bundle to cache.
76
+
77
+ Args:
78
+ source_path: Source xcresult bundle path
79
+ xcresult_id: Optional custom ID (generates if not provided)
80
+
81
+ Returns:
82
+ xcresult ID
83
+ """
84
+ if not source_path.exists():
85
+ raise FileNotFoundError(f"Source xcresult not found: {source_path}")
86
+
87
+ # Generate ID if not provided
88
+ if not xcresult_id:
89
+ xcresult_id = self.generate_id()
90
+
91
+ # Get destination path
92
+ dest_path = self.get_path(xcresult_id)
93
+
94
+ # Copy xcresult bundle (it's a directory)
95
+ if dest_path.exists():
96
+ shutil.rmtree(dest_path)
97
+
98
+ shutil.copytree(source_path, dest_path)
99
+
100
+ return xcresult_id
101
+
102
+ def list(self, limit: int = 10) -> list[dict]:
103
+ """
104
+ List recent xcresult bundles.
105
+
106
+ Args:
107
+ limit: Maximum number to return
108
+
109
+ Returns:
110
+ List of xcresult metadata dicts
111
+ """
112
+ if not self.cache_dir.exists():
113
+ return []
114
+
115
+ results = []
116
+ for path in sorted(
117
+ self.cache_dir.glob("*.xcresult"), key=lambda p: p.stat().st_mtime, reverse=True
118
+ )[:limit]:
119
+ # Calculate bundle size
120
+ size_bytes = sum(f.stat().st_size for f in path.rglob("*") if f.is_file())
121
+
122
+ results.append(
123
+ {
124
+ "id": path.stem,
125
+ "path": str(path),
126
+ "created": datetime.fromtimestamp(path.stat().st_mtime).isoformat(),
127
+ "size_mb": round(size_bytes / (1024 * 1024), 2),
128
+ }
129
+ )
130
+
131
+ return results
132
+
133
+ def cleanup(self, keep_recent: int = 20) -> int:
134
+ """
135
+ Clean up old xcresult bundles.
136
+
137
+ Args:
138
+ keep_recent: Number of recent bundles to keep
139
+
140
+ Returns:
141
+ Number of bundles removed
142
+ """
143
+ if not self.cache_dir.exists():
144
+ return 0
145
+
146
+ # Get all bundles sorted by modification time
147
+ all_bundles = sorted(
148
+ self.cache_dir.glob("*.xcresult"), key=lambda p: p.stat().st_mtime, reverse=True
149
+ )
150
+
151
+ # Remove old bundles
152
+ removed = 0
153
+ for bundle_path in all_bundles[keep_recent:]:
154
+ shutil.rmtree(bundle_path)
155
+ removed += 1
156
+
157
+ return removed
158
+
159
+ def get_size_mb(self, xcresult_id: str) -> float:
160
+ """
161
+ Get size of xcresult bundle in MB.
162
+
163
+ Args:
164
+ xcresult_id: XCResult ID
165
+
166
+ Returns:
167
+ Size in MB
168
+ """
169
+ path = self.get_path(xcresult_id)
170
+ if not path.exists():
171
+ return 0.0
172
+
173
+ size_bytes = sum(f.stat().st_size for f in path.rglob("*") if f.is_file())
174
+ return round(size_bytes / (1024 * 1024), 2)
175
+
176
+ def save_stderr(self, xcresult_id: str, stderr: str) -> None:
177
+ """
178
+ Save stderr output alongside xcresult bundle.
179
+
180
+ Args:
181
+ xcresult_id: XCResult ID
182
+ stderr: stderr output from xcodebuild
183
+ """
184
+ if not stderr:
185
+ return
186
+
187
+ stderr_path = self.cache_dir / f"{xcresult_id}.stderr"
188
+ stderr_path.write_text(stderr, encoding="utf-8")
189
+
190
+ def get_stderr(self, xcresult_id: str) -> str:
191
+ """
192
+ Retrieve cached stderr output.
193
+
194
+ Args:
195
+ xcresult_id: XCResult ID
196
+
197
+ Returns:
198
+ stderr content or empty string if not found
199
+ """
200
+ stderr_path = self.cache_dir / f"{xcresult_id}.stderr"
201
+ if not stderr_path.exists():
202
+ return ""
203
+
204
+ return stderr_path.read_text(encoding="utf-8")