@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.
- package/bin/awk.js +576 -84
- package/core/CLAUDE.md +1 -1
- package/core/GEMINI.md +148 -167
- package/core/GEMINI.md.bak +149 -116
- package/core/skill-runtime-manifest.json +3 -0
- package/docs/Claude Fable 5.md +3826 -0
- package/docs/android_kotlin_system_instruction.md +210 -0
- package/docs/brainstorm_ponytail_integration.md +146 -0
- package/docs/brainstorm_smart_setup.md +113 -0
- package/docs/deep-research-report (1).md +293 -0
- package/docs/history/GEMINI.v1.md +135 -0
- package/docs/history/brainstorm_antigravity_unified_architecture.v1.md +105 -0
- package/docs/history/implementation_plan.v1.md +58 -0
- package/package.json +4 -1
- package/scripts/artifact-storage.js +130 -0
- package/scripts/automation-gate.js +35 -2
- package/scripts/claude-plan.js +76 -0
- package/scripts/dependency-manager.js +210 -0
- package/scripts/exec-rtk.js +11 -5
- package/scripts/i18n-helper.js +381 -0
- package/scripts/multi-model-pipeline.js +144 -0
- package/skill-packs/mobile-ios/pack.json +4 -2
- package/skill-packs/reverse-engineering/pack.json +1 -0
- package/skills/CATALOG.md +20 -0
- package/skills/GEMINI.md +9 -1
- package/skills/TRIGGER_INDEX.md +10 -0
- package/skills/ai-music/SKILL.md +275 -0
- package/skills/android-re-analyzer/SKILL.md +238 -0
- package/skills/android-re-analyzer/references/api-extraction-patterns.md +119 -0
- package/skills/android-re-analyzer/references/call-flow-analysis.md +176 -0
- package/skills/android-re-analyzer/references/fernflower-usage.md +115 -0
- package/skills/android-re-analyzer/references/jadx-usage.md +116 -0
- package/skills/android-re-analyzer/references/setup-guide.md +221 -0
- package/skills/android-re-analyzer/scripts/check-deps.sh +129 -0
- package/skills/android-re-analyzer/scripts/decompile.sh +375 -0
- package/skills/android-re-analyzer/scripts/find-api-calls.sh +118 -0
- package/skills/android-re-analyzer/scripts/install-dep.sh +448 -0
- package/skills/animal-island-ui-style/SKILL.md +1450 -0
- package/skills/app-store-review-agent/SKILL.md +164 -0
- package/skills/app-store-review-agent/references/guidelines/README.md +154 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/ai_apps.md +37 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/all_apps.md +50 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/crypto_finance.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/games.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/health_fitness.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/kids.md +27 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/macos.md +38 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/social_ugc.md +32 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/subscription_iap.md +34 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/vpn.md +18 -0
- package/skills/app-store-review-agent/references/rules/design/minimum_functionality.md +96 -0
- package/skills/app-store-review-agent/references/rules/design/sign_in_with_apple.md +54 -0
- package/skills/app-store-review-agent/references/rules/entitlements/unused_entitlements.md +83 -0
- package/skills/app-store-review-agent/references/rules/metadata/accurate_metadata.md +54 -0
- package/skills/app-store-review-agent/references/rules/metadata/apple_trademark.md +99 -0
- package/skills/app-store-review-agent/references/rules/metadata/china_storefront.md +72 -0
- package/skills/app-store-review-agent/references/rules/metadata/competitor_terms.md +56 -0
- package/skills/app-store-review-agent/references/rules/metadata/subscription_metadata.md +81 -0
- package/skills/app-store-review-agent/references/rules/privacy/privacy_manifest.md +84 -0
- package/skills/app-store-review-agent/references/rules/privacy/unnecessary_data.md +60 -0
- package/skills/app-store-review-agent/references/rules/subscription/misleading_pricing.md +63 -0
- package/skills/app-store-review-agent/references/rules/subscription/missing_tos_pp.md +54 -0
- package/skills/awf-ponytail/SKILL.md +91 -0
- package/skills/awf-ponytail-review/SKILL.md +67 -0
- package/skills/awf-session-restore/SKILL.md +3 -3
- package/skills/brainstorm-agent/SKILL.md +11 -2
- package/skills/brainstorm-agent/templates/brief-template.md +8 -0
- package/skills/claude-planner/SKILL.md +47 -0
- package/skills/code-review/SKILL.md +87 -0
- package/skills/expo-game-development/SKILL.md +163 -0
- package/skills/flutter/LICENSE.txt +202 -0
- package/skills/flutter/SKILL.md +127 -0
- package/skills/flutter-project-creater/LICENSE.txt +202 -0
- package/skills/flutter-project-creater/SKILL.md +106 -0
- package/skills/game-developer/SKILL.md +163 -0
- package/skills/game-developer/references/ecs-patterns.md +501 -0
- package/skills/game-developer/references/multiplayer-networking.md +475 -0
- package/skills/game-developer/references/performance-optimization.md +422 -0
- package/skills/game-developer/references/unity-patterns.md +271 -0
- package/skills/game-developer/references/unreal-cpp.md +352 -0
- package/skills/generate-gui-assets/SKILL.md +305 -0
- package/skills/generate-gui-assets/agents/openai.yaml +4 -0
- package/skills/generate-gui-assets/references/catalog-schema.md +58 -0
- package/skills/generate-gui-assets/references/extraction-techniques.md +21 -0
- package/skills/generate-gui-assets/references/prompt-patterns.md +58 -0
- package/skills/generate-gui-assets/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
- package/skills/generate-gui-assets/scripts/build_gui_contact_sheet.py +51 -0
- package/skills/generate-gui-assets/scripts/clean_chroma_edges.py +262 -0
- package/skills/generate-gui-assets/scripts/copy_approved_icons.py +64 -0
- package/skills/generate-gui-assets/scripts/prepare_gui_asset_run.py +91 -0
- package/skills/generate-gui-assets/scripts/suggest_grid_options.py +63 -0
- package/skills/generate-gui-assets/scripts/validate_gui_catalog.py +50 -0
- package/skills/godot-game-development/SKILL.md +142 -0
- package/skills/hatch-pet/LICENSE.txt +201 -0
- package/skills/hatch-pet/SKILL.md +420 -0
- package/skills/hatch-pet/agents/openai.yaml +4 -0
- package/skills/hatch-pet/references/animation-rows.md +29 -0
- package/skills/hatch-pet/references/codex-pet-contract.md +35 -0
- package/skills/hatch-pet/references/qa-rubric.md +60 -0
- package/skills/hatch-pet/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
- package/skills/hatch-pet/scripts/clean_chroma_edges.py +262 -0
- package/skills/hatch-pet/scripts/compose_atlas.py +150 -0
- package/skills/hatch-pet/scripts/derive_running_left_from_running_right.py +143 -0
- package/skills/hatch-pet/scripts/extract_strip_frames.py +323 -0
- package/skills/hatch-pet/scripts/finalize_pet_run.py +382 -0
- package/skills/hatch-pet/scripts/generate_pet_images.py +287 -0
- package/skills/hatch-pet/scripts/inspect_frames.py +246 -0
- package/skills/hatch-pet/scripts/make_contact_sheet.py +96 -0
- package/skills/hatch-pet/scripts/package_custom_pet.py +108 -0
- package/skills/hatch-pet/scripts/pet_job_status.py +117 -0
- package/skills/hatch-pet/scripts/prepare_pet_run.py +673 -0
- package/skills/hatch-pet/scripts/queue_pet_repairs.py +172 -0
- package/skills/hatch-pet/scripts/record_imagegen_result.py +250 -0
- package/skills/hatch-pet/scripts/render_animation_videos.py +134 -0
- package/skills/hatch-pet/scripts/render_animation_videos.sh +5 -0
- package/skills/hatch-pet/scripts/validate_atlas.py +139 -0
- package/skills/i18n-orchestrator/SKILL.md +37 -0
- package/skills/ios-simulator-skill/SKILL.md +390 -0
- package/skills/ios-simulator-skill/scripts/accessibility_audit.py +300 -0
- package/skills/ios-simulator-skill/scripts/app_launcher.py +326 -0
- package/skills/ios-simulator-skill/scripts/app_state_capture.py +400 -0
- package/skills/ios-simulator-skill/scripts/appearance.py +385 -0
- package/skills/ios-simulator-skill/scripts/build_and_test.py +348 -0
- package/skills/ios-simulator-skill/scripts/clipboard.py +103 -0
- package/skills/ios-simulator-skill/scripts/common/__init__.py +61 -0
- package/skills/ios-simulator-skill/scripts/common/cache_utils.py +289 -0
- package/skills/ios-simulator-skill/scripts/common/device_utils.py +462 -0
- package/skills/ios-simulator-skill/scripts/common/env_config.py +35 -0
- package/skills/ios-simulator-skill/scripts/common/hang_pipeline.py +862 -0
- package/skills/ios-simulator-skill/scripts/common/hang_sessions.py +490 -0
- package/skills/ios-simulator-skill/scripts/common/idb_utils.py +180 -0
- package/skills/ios-simulator-skill/scripts/common/screenshot_utils.py +338 -0
- package/skills/ios-simulator-skill/scripts/container.py +668 -0
- package/skills/ios-simulator-skill/scripts/gesture.py +394 -0
- package/skills/ios-simulator-skill/scripts/hang_watcher.py +1533 -0
- package/skills/ios-simulator-skill/scripts/keyboard.py +391 -0
- package/skills/ios-simulator-skill/scripts/localization_audit.py +483 -0
- package/skills/ios-simulator-skill/scripts/location.py +467 -0
- package/skills/ios-simulator-skill/scripts/log_monitor.py +493 -0
- package/skills/ios-simulator-skill/scripts/model_inspector.py +645 -0
- package/skills/ios-simulator-skill/scripts/navigator.py +461 -0
- package/skills/ios-simulator-skill/scripts/privacy_manager.py +310 -0
- package/skills/ios-simulator-skill/scripts/push_notification.py +240 -0
- package/skills/ios-simulator-skill/scripts/screen_mapper.py +296 -0
- package/skills/ios-simulator-skill/scripts/sim_health_check.sh +245 -0
- package/skills/ios-simulator-skill/scripts/sim_list.py +299 -0
- package/skills/ios-simulator-skill/scripts/simctl_boot.py +312 -0
- package/skills/ios-simulator-skill/scripts/simctl_create.py +316 -0
- package/skills/ios-simulator-skill/scripts/simctl_delete.py +357 -0
- package/skills/ios-simulator-skill/scripts/simctl_erase.py +351 -0
- package/skills/ios-simulator-skill/scripts/simctl_shutdown.py +290 -0
- package/skills/ios-simulator-skill/scripts/simulator_selector.py +375 -0
- package/skills/ios-simulator-skill/scripts/status_bar.py +250 -0
- package/skills/ios-simulator-skill/scripts/test_recorder.py +323 -0
- package/skills/ios-simulator-skill/scripts/visual_diff.py +235 -0
- package/skills/ios-simulator-skill/scripts/xcode/__init__.py +13 -0
- package/skills/ios-simulator-skill/scripts/xcode/builder.py +397 -0
- package/skills/ios-simulator-skill/scripts/xcode/cache.py +204 -0
- package/skills/ios-simulator-skill/scripts/xcode/config.py +178 -0
- package/skills/ios-simulator-skill/scripts/xcode/reporter.py +343 -0
- package/skills/ios-simulator-skill/scripts/xcode/xcresult.py +451 -0
- package/skills/ios-visual-qa-strategist/SKILL.md +111 -0
- package/skills/ios-visual-qa-strategist/agents/openai.yaml +4 -0
- package/skills/ios-visual-qa-strategist/references/ios-tool-selection.md +61 -0
- package/skills/ios-visual-qa-strategist/references/minimal-capture-policy.md +56 -0
- package/skills/ios-visual-qa-strategist/references/visual-reasoning-heuristics.md +53 -0
- package/skills/orchestrator/SKILL.md +0 -20
- package/skills/persistent-storage/SKILL.md +55 -0
- package/skills/short-maker/SKILL.md +23 -0
- package/skills/short-maker/scripts/effects.js +56 -0
- package/skills/short-maker/scripts/shortmaker-bridge.js +332 -0
- package/skills/short-maker/scripts/videomix.js +601 -0
- package/skills/short-maker/templates/hyperframes/cinematic-character.template.html +172 -0
- package/skills/short-maker/templates/hyperframes/index.template.html +194 -0
- package/skills/smali-to-kotlin/SKILL.md +128 -0
- package/skills/smali-to-kotlin/examples/getting-started/tech-stack.md +58 -0
- package/skills/smali-to-kotlin/examples/pipeline/data-ui-parity.md +118 -0
- package/skills/smali-to-kotlin/examples/pipeline/scanner-and-bootstrap.md +106 -0
- package/skills/smali-to-kotlin/library-patterns.md +189 -0
- package/skills/smali-to-kotlin/phase-0-discovery.md +128 -0
- package/skills/smali-to-kotlin/phase-1-architecture.md +166 -0
- package/skills/smali-to-kotlin/phase-2-blueprint-ui.md +347 -0
- package/skills/smali-to-kotlin/phase-2-blueprint.md +228 -0
- package/skills/smali-to-kotlin/phase-3-build.md +248 -0
- package/skills/smali-to-kotlin/phase-3-logic-build.md +268 -0
- package/skills/smali-to-kotlin/smali-reading-guide.md +310 -0
- package/skills/smali-to-kotlin/templates/app-map.md +101 -0
- package/skills/smali-to-kotlin/templates/architecture.md +142 -0
- package/skills/smali-to-kotlin/templates/blueprint.md +145 -0
- package/skills/spec-gate/SKILL.md +6 -2
- package/skills/symphony-enforcer/SKILL.md +8 -0
- package/skills/symphony-enforcer/examples/mindful-stop.md +2 -0
- package/skills/symphony-enforcer/examples/three-phase.md +16 -0
- package/skills/symphony-enforcer/examples/trigger-points.md +7 -1
- package/skills/unity-game-development/SKILL.md +231 -0
- package/skills/video-edit/SKILL.md +36 -0
- package/skills/video-edit/scripts/video_edit.py +324 -0
- package/templates/project-identity/android.json +2 -2
- package/templates/project-identity/backend-nestjs.json +2 -2
- package/templates/project-identity/expo.json +2 -2
- package/templates/project-identity/ios.json +2 -2
- package/templates/project-identity/web-nextjs.json +2 -2
- package/templates/setup-mapping.json +48 -0
- package/templates/specs/design-template.md +161 -71
- package/templates/specs/requirements-template.md +65 -133
- package/templates/specs/task-spec-template.xml +3 -0
- package/workflows/_uncategorized/critic.md +40 -0
- package/workflows/_uncategorized/git-rebase-flow.md +81 -0
- package/workflows/_uncategorized/image-gen.md +118 -0
- package/workflows/_uncategorized/multi-model-pipeline.md +60 -0
- package/workflows/_uncategorized/pixel-gen.md +86 -0
- package/workflows/_uncategorized/pixel-setup.md +90 -0
- package/workflows/_uncategorized/ponytail-review.md +59 -0
- package/workflows/_uncategorized/reverse-android-build.md +222 -0
- package/workflows/_uncategorized/reverse-android-design.md +139 -0
- package/workflows/_uncategorized/reverse-android-discover.md +150 -0
- package/workflows/_uncategorized/reverse-android-scan.md +158 -0
- package/workflows/_uncategorized/reverse-android.md +143 -0
- package/workflows/_uncategorized/reverse-ios-build.md +240 -0
- package/workflows/_uncategorized/reverse-ios-design.md +112 -0
- package/workflows/_uncategorized/reverse-ios-discover.md +120 -0
- package/workflows/_uncategorized/reverse-ios-scan.md +155 -0
- package/workflows/_uncategorized/reverse-ios.md +152 -0
- package/workflows/_uncategorized/safety-router.md +34 -0
- package/workflows/_uncategorized/teach.md +89 -0
- package/workflows/_uncategorized/verify-ui.md +53 -0
- package/workflows/_uncategorized/visualize-screenshots.md +34 -0
- package/workflows/ads/ads-analyst.md +201 -0
- package/workflows/ads/ads-audit.md +106 -0
- package/workflows/ads/ads-optimize.md +97 -0
- package/workflows/ads/ads-targeting.md +241 -0
- package/workflows/ads/adsExpert.md +160 -0
- package/workflows/ads/smali-ads-config.md +400 -0
- package/workflows/ads/smali-ads-flow.md +331 -0
- package/workflows/ads/smali-ads-interstitial.md +377 -0
- package/workflows/ads/smali-ads-native.md +382 -0
- package/workflows/context/teach.md +89 -0
- package/workflows/gitnexus.md +8 -8
- package/workflows/lifecycle/brainstorm.md +43 -0
- package/workflows/lifecycle/code.md +5 -0
- package/workflows/lifecycle/init.md +23 -5
- package/workflows/lifecycle/multi-model-pipeline.md +60 -0
- package/workflows/quality/ponytail-review.md +59 -0
- package/workflows/roles/critic.md +40 -0
- 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")
|