@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.
- 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 +40 -7
- 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/verification-gate/SKILL.md +4 -2
- package/skills/video-edit/SKILL.md +36 -0
- package/skills/video-edit/scripts/video_edit.py +324 -0
- 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,300 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
iOS Simulator Accessibility Audit
|
|
4
|
+
|
|
5
|
+
Scans the current simulator screen for accessibility compliance issues.
|
|
6
|
+
Optimized for minimal token output while maintaining functionality.
|
|
7
|
+
|
|
8
|
+
Usage: python scripts/accessibility_audit.py [options]
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
from dataclasses import asdict, dataclass
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from common import flatten_tree, get_accessibility_tree, resolve_udid
|
|
19
|
+
from common.env_config import env_int
|
|
20
|
+
|
|
21
|
+
A11Y_LABEL_MAX = env_int("IOS_SIM_A11Y_LABEL_MAX", 80)
|
|
22
|
+
A11Y_TOP_ISSUES = env_int("IOS_SIM_A11Y_TOP_ISSUES", 10)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class Issue:
|
|
27
|
+
"""Represents an accessibility issue."""
|
|
28
|
+
|
|
29
|
+
severity: str # critical, warning, info
|
|
30
|
+
rule: str
|
|
31
|
+
element_type: str
|
|
32
|
+
issue: str
|
|
33
|
+
fix: str
|
|
34
|
+
|
|
35
|
+
def to_dict(self) -> dict:
|
|
36
|
+
"""Convert to dictionary for JSON serialization."""
|
|
37
|
+
return asdict(self)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AccessibilityAuditor:
|
|
41
|
+
"""Performs accessibility audits on iOS simulator screens."""
|
|
42
|
+
|
|
43
|
+
# Critical rules that block users
|
|
44
|
+
CRITICAL_RULES = {
|
|
45
|
+
"missing_label": lambda e: e.get("type") in ["Button", "Link"] and not e.get("AXLabel"),
|
|
46
|
+
"empty_button": lambda e: e.get("type") == "Button"
|
|
47
|
+
and not (e.get("AXLabel") or e.get("AXValue")),
|
|
48
|
+
"image_no_alt": lambda e: e.get("type") == "Image" and not e.get("AXLabel"),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Warnings that degrade UX
|
|
52
|
+
WARNING_RULES = {
|
|
53
|
+
"missing_hint": lambda e: e.get("type") in ["Slider", "TextField"] and not e.get("help"),
|
|
54
|
+
"missing_traits": lambda e: e.get("type") and not e.get("traits"),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Info level suggestions
|
|
58
|
+
INFO_RULES = {
|
|
59
|
+
"no_identifier": lambda e: not e.get("AXUniqueId"),
|
|
60
|
+
"deep_nesting": lambda e: e.get("depth", 0) > 5,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
def __init__(self, udid: str | None = None):
|
|
64
|
+
"""Initialize auditor with optional device UDID."""
|
|
65
|
+
self.udid = udid
|
|
66
|
+
|
|
67
|
+
def get_accessibility_tree(self) -> dict:
|
|
68
|
+
"""Fetch accessibility tree from simulator using shared utility."""
|
|
69
|
+
return get_accessibility_tree(self.udid, nested=True)
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def _is_small_target(element: dict) -> bool:
|
|
73
|
+
"""Check if touch target is too small (< 44x44 points)."""
|
|
74
|
+
frame = element.get("frame", {})
|
|
75
|
+
width = frame.get("width", 0)
|
|
76
|
+
height = frame.get("height", 0)
|
|
77
|
+
return width < 44 or height < 44
|
|
78
|
+
|
|
79
|
+
def _flatten_tree(self, node: dict, depth: int = 0) -> list[dict]:
|
|
80
|
+
"""Flatten nested accessibility tree for easier processing using shared utility."""
|
|
81
|
+
return flatten_tree(node, depth)
|
|
82
|
+
|
|
83
|
+
def audit_element(self, element: dict) -> list[Issue]:
|
|
84
|
+
"""Audit a single element for accessibility issues."""
|
|
85
|
+
issues = []
|
|
86
|
+
|
|
87
|
+
# Check critical rules
|
|
88
|
+
for rule_name, rule_func in self.CRITICAL_RULES.items():
|
|
89
|
+
if rule_func(element):
|
|
90
|
+
issues.append(
|
|
91
|
+
Issue(
|
|
92
|
+
severity="critical",
|
|
93
|
+
rule=rule_name,
|
|
94
|
+
element_type=element.get("type", "Unknown"),
|
|
95
|
+
issue=self._get_issue_description(rule_name),
|
|
96
|
+
fix=self._get_fix_suggestion(rule_name),
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Check warnings (skip if critical issues found)
|
|
101
|
+
if not issues:
|
|
102
|
+
for rule_name, rule_func in self.WARNING_RULES.items():
|
|
103
|
+
if rule_func(element):
|
|
104
|
+
issues.append(
|
|
105
|
+
Issue(
|
|
106
|
+
severity="warning",
|
|
107
|
+
rule=rule_name,
|
|
108
|
+
element_type=element.get("type", "Unknown"),
|
|
109
|
+
issue=self._get_issue_description(rule_name),
|
|
110
|
+
fix=self._get_fix_suggestion(rule_name),
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Check info level (only if verbose or no other issues)
|
|
115
|
+
if not issues:
|
|
116
|
+
for rule_name, rule_func in self.INFO_RULES.items():
|
|
117
|
+
if rule_func(element):
|
|
118
|
+
issues.append(
|
|
119
|
+
Issue(
|
|
120
|
+
severity="info",
|
|
121
|
+
rule=rule_name,
|
|
122
|
+
element_type=element.get("type", "Unknown"),
|
|
123
|
+
issue=self._get_issue_description(rule_name),
|
|
124
|
+
fix=self._get_fix_suggestion(rule_name),
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return issues
|
|
129
|
+
|
|
130
|
+
def _get_issue_description(self, rule: str) -> str:
|
|
131
|
+
"""Get human-readable issue description."""
|
|
132
|
+
descriptions = {
|
|
133
|
+
"missing_label": "Interactive element missing accessibility label",
|
|
134
|
+
"empty_button": "Button has no text or label",
|
|
135
|
+
"image_no_alt": "Image missing alternative text",
|
|
136
|
+
"missing_hint": "Complex control missing hint",
|
|
137
|
+
"small_touch_target": "Touch target smaller than 44x44pt",
|
|
138
|
+
"missing_traits": "Element missing accessibility traits",
|
|
139
|
+
"no_identifier": "Missing accessibility identifier",
|
|
140
|
+
"deep_nesting": "Deeply nested (>5 levels)",
|
|
141
|
+
}
|
|
142
|
+
return descriptions.get(rule, "Accessibility issue")
|
|
143
|
+
|
|
144
|
+
def _get_fix_suggestion(self, rule: str) -> str:
|
|
145
|
+
"""Get fix suggestion for issue."""
|
|
146
|
+
fixes = {
|
|
147
|
+
"missing_label": "Add accessibilityLabel",
|
|
148
|
+
"empty_button": "Set button title or accessibilityLabel",
|
|
149
|
+
"image_no_alt": "Add accessibilityLabel with description",
|
|
150
|
+
"missing_hint": "Add accessibilityHint",
|
|
151
|
+
"small_touch_target": "Increase to minimum 44x44pt",
|
|
152
|
+
"missing_traits": "Set appropriate accessibilityTraits",
|
|
153
|
+
"no_identifier": "Add accessibilityIdentifier for testing",
|
|
154
|
+
"deep_nesting": "Simplify view hierarchy",
|
|
155
|
+
}
|
|
156
|
+
return fixes.get(rule, "Review accessibility")
|
|
157
|
+
|
|
158
|
+
def audit(self, verbose: bool = False) -> dict[str, Any]:
|
|
159
|
+
"""Perform full accessibility audit."""
|
|
160
|
+
# Get accessibility tree
|
|
161
|
+
tree = self.get_accessibility_tree()
|
|
162
|
+
|
|
163
|
+
# Flatten for processing
|
|
164
|
+
elements = self._flatten_tree(tree)
|
|
165
|
+
|
|
166
|
+
# Audit each element
|
|
167
|
+
all_issues = []
|
|
168
|
+
for element in elements:
|
|
169
|
+
issues = self.audit_element(element)
|
|
170
|
+
for issue in issues:
|
|
171
|
+
issue_dict = issue.to_dict()
|
|
172
|
+
# Add minimal element info for context
|
|
173
|
+
issue_dict["element"] = {
|
|
174
|
+
"type": element.get("type", "Unknown"),
|
|
175
|
+
"label": (
|
|
176
|
+
element.get("AXLabel", "")[:A11Y_LABEL_MAX]
|
|
177
|
+
if element.get("AXLabel")
|
|
178
|
+
else None
|
|
179
|
+
),
|
|
180
|
+
}
|
|
181
|
+
all_issues.append(issue_dict)
|
|
182
|
+
|
|
183
|
+
# Count by severity
|
|
184
|
+
critical = len([i for i in all_issues if i["severity"] == "critical"])
|
|
185
|
+
warning = len([i for i in all_issues if i["severity"] == "warning"])
|
|
186
|
+
info = len([i for i in all_issues if i["severity"] == "info"])
|
|
187
|
+
|
|
188
|
+
# Build result (token-optimized)
|
|
189
|
+
result = {
|
|
190
|
+
"summary": {
|
|
191
|
+
"total": len(elements),
|
|
192
|
+
"issues": len(all_issues),
|
|
193
|
+
"critical": critical,
|
|
194
|
+
"warning": warning,
|
|
195
|
+
"info": info,
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if verbose:
|
|
200
|
+
# Full details only if requested
|
|
201
|
+
result["issues"] = all_issues
|
|
202
|
+
else:
|
|
203
|
+
# Default: top issues only (token-efficient)
|
|
204
|
+
result["top_issues"] = self._get_top_issues(all_issues)
|
|
205
|
+
|
|
206
|
+
return result
|
|
207
|
+
|
|
208
|
+
def _get_top_issues(self, issues: list[dict]) -> list[dict]:
|
|
209
|
+
"""Get top 3 issues grouped by type (token-efficient)."""
|
|
210
|
+
if not issues:
|
|
211
|
+
return []
|
|
212
|
+
|
|
213
|
+
# Group by rule
|
|
214
|
+
grouped = {}
|
|
215
|
+
for issue in issues:
|
|
216
|
+
rule = issue["rule"]
|
|
217
|
+
if rule not in grouped:
|
|
218
|
+
grouped[rule] = {
|
|
219
|
+
"severity": issue["severity"],
|
|
220
|
+
"rule": rule,
|
|
221
|
+
"count": 0,
|
|
222
|
+
"fix": issue["fix"],
|
|
223
|
+
}
|
|
224
|
+
grouped[rule]["count"] += 1
|
|
225
|
+
|
|
226
|
+
# Sort by severity and count
|
|
227
|
+
severity_order = {"critical": 0, "warning": 1, "info": 2}
|
|
228
|
+
sorted_issues = sorted(
|
|
229
|
+
grouped.values(), key=lambda x: (severity_order[x["severity"]], -x["count"])
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return sorted_issues[:A11Y_TOP_ISSUES]
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def main():
|
|
236
|
+
"""Main entry point."""
|
|
237
|
+
parser = argparse.ArgumentParser(
|
|
238
|
+
description="Audit iOS simulator screen for accessibility issues"
|
|
239
|
+
)
|
|
240
|
+
parser.add_argument(
|
|
241
|
+
"--udid",
|
|
242
|
+
help="Device UDID (auto-detects booted simulator if not provided)",
|
|
243
|
+
)
|
|
244
|
+
parser.add_argument("--output", help="Save JSON report to file")
|
|
245
|
+
parser.add_argument(
|
|
246
|
+
"--verbose", action="store_true", help="Include all issue details (increases output)"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
args = parser.parse_args()
|
|
250
|
+
|
|
251
|
+
# Resolve UDID with auto-detection
|
|
252
|
+
try:
|
|
253
|
+
udid = resolve_udid(args.udid)
|
|
254
|
+
except RuntimeError as e:
|
|
255
|
+
print(f"Error: {e}")
|
|
256
|
+
sys.exit(1)
|
|
257
|
+
|
|
258
|
+
# Perform audit
|
|
259
|
+
auditor = AccessibilityAuditor(udid=udid)
|
|
260
|
+
|
|
261
|
+
try:
|
|
262
|
+
result = auditor.audit(verbose=args.verbose)
|
|
263
|
+
except Exception as e:
|
|
264
|
+
print(f"Error: {e}")
|
|
265
|
+
sys.exit(1)
|
|
266
|
+
|
|
267
|
+
# Output results
|
|
268
|
+
if args.output:
|
|
269
|
+
# Save to file
|
|
270
|
+
with open(args.output, "w") as f:
|
|
271
|
+
json.dump(result, f, indent=2)
|
|
272
|
+
# Print minimal summary
|
|
273
|
+
summary = result["summary"]
|
|
274
|
+
print(f"Audit complete: {summary['issues']} issues ({summary['critical']} critical)")
|
|
275
|
+
print(f"Report saved to: {args.output}")
|
|
276
|
+
# Print to stdout (token-optimized by default)
|
|
277
|
+
elif args.verbose:
|
|
278
|
+
print(json.dumps(result, indent=2))
|
|
279
|
+
else:
|
|
280
|
+
# Ultra-compact output
|
|
281
|
+
summary = result["summary"]
|
|
282
|
+
print(f"Elements: {summary['total']}, Issues: {summary['issues']}")
|
|
283
|
+
print(
|
|
284
|
+
f"Critical: {summary['critical']}, Warning: {summary['warning']}, Info: {summary['info']}"
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
if result.get("top_issues"):
|
|
288
|
+
print("\nTop issues:")
|
|
289
|
+
for issue in result["top_issues"]:
|
|
290
|
+
print(
|
|
291
|
+
f" [{issue['severity']}] {issue['rule']} ({issue['count']}x) - {issue['fix']}"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Exit with error if critical issues found
|
|
295
|
+
if result["summary"]["critical"] > 0:
|
|
296
|
+
sys.exit(1)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
if __name__ == "__main__":
|
|
300
|
+
main()
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
iOS App Launcher - App Lifecycle Control
|
|
4
|
+
|
|
5
|
+
Launches, terminates, and manages iOS apps in the simulator.
|
|
6
|
+
Handles deep links and app switching.
|
|
7
|
+
|
|
8
|
+
Usage: python scripts/app_launcher.py --launch com.example.app
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import contextlib
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
import time
|
|
16
|
+
|
|
17
|
+
from common import build_simctl_command, resolve_udid
|
|
18
|
+
from common.env_config import env_float, env_int
|
|
19
|
+
|
|
20
|
+
RELAUNCH_DELAY_SECONDS = env_float("IOS_SIM_RELAUNCH_DELAY_MS", 1000.0) / 1000.0
|
|
21
|
+
APPS_PREVIEW = env_int("IOS_SIM_APPS_PREVIEW", 30)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AppLauncher:
|
|
25
|
+
"""Controls app lifecycle on iOS simulator."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, udid: str | None = None):
|
|
28
|
+
"""Initialize app launcher."""
|
|
29
|
+
self.udid = udid
|
|
30
|
+
|
|
31
|
+
def launch(self, bundle_id: str, wait_for_debugger: bool = False) -> tuple[bool, int | None]:
|
|
32
|
+
"""
|
|
33
|
+
Launch an app.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
bundle_id: App bundle identifier
|
|
37
|
+
wait_for_debugger: Wait for debugger attachment
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
(success, pid) tuple
|
|
41
|
+
"""
|
|
42
|
+
cmd = build_simctl_command("launch", self.udid, bundle_id)
|
|
43
|
+
|
|
44
|
+
if wait_for_debugger:
|
|
45
|
+
cmd.insert(3, "--wait-for-debugger") # Insert after "launch" operation
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
49
|
+
# Parse PID from output if available
|
|
50
|
+
pid = None
|
|
51
|
+
if result.stdout:
|
|
52
|
+
# Output format: "com.example.app: <PID>"
|
|
53
|
+
parts = result.stdout.strip().split(":")
|
|
54
|
+
if len(parts) > 1:
|
|
55
|
+
with contextlib.suppress(ValueError):
|
|
56
|
+
pid = int(parts[1].strip())
|
|
57
|
+
return (True, pid)
|
|
58
|
+
except subprocess.CalledProcessError:
|
|
59
|
+
return (False, None)
|
|
60
|
+
|
|
61
|
+
def terminate(self, bundle_id: str) -> bool:
|
|
62
|
+
"""
|
|
63
|
+
Terminate an app.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
bundle_id: App bundle identifier
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Success status
|
|
70
|
+
"""
|
|
71
|
+
cmd = build_simctl_command("terminate", self.udid, bundle_id)
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
subprocess.run(cmd, capture_output=True, check=True)
|
|
75
|
+
return True
|
|
76
|
+
except subprocess.CalledProcessError:
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
def install(self, app_path: str) -> bool:
|
|
80
|
+
"""
|
|
81
|
+
Install an app.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
app_path: Path to .app bundle
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Success status
|
|
88
|
+
"""
|
|
89
|
+
cmd = build_simctl_command("install", self.udid, app_path)
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
subprocess.run(cmd, capture_output=True, check=True)
|
|
93
|
+
return True
|
|
94
|
+
except subprocess.CalledProcessError:
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
def uninstall(self, bundle_id: str) -> bool:
|
|
98
|
+
"""
|
|
99
|
+
Uninstall an app.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
bundle_id: App bundle identifier
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Success status
|
|
106
|
+
"""
|
|
107
|
+
cmd = build_simctl_command("uninstall", self.udid, bundle_id)
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
subprocess.run(cmd, capture_output=True, check=True)
|
|
111
|
+
return True
|
|
112
|
+
except subprocess.CalledProcessError:
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
def open_url(self, url: str) -> bool:
|
|
116
|
+
"""
|
|
117
|
+
Open URL (for deep linking).
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
url: URL to open (http://, myapp://, etc.)
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Success status
|
|
124
|
+
"""
|
|
125
|
+
cmd = build_simctl_command("openurl", self.udid, url)
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
subprocess.run(cmd, capture_output=True, check=True)
|
|
129
|
+
return True
|
|
130
|
+
except subprocess.CalledProcessError:
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
def list_apps(self) -> list[dict[str, str]]:
|
|
134
|
+
"""
|
|
135
|
+
List installed apps.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
List of app info dictionaries
|
|
139
|
+
"""
|
|
140
|
+
cmd = build_simctl_command("listapps", self.udid)
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
144
|
+
|
|
145
|
+
# Parse plist output using plutil to convert to JSON
|
|
146
|
+
plist_data = result.stdout
|
|
147
|
+
|
|
148
|
+
# Use plutil to convert plist to JSON
|
|
149
|
+
convert_cmd = ["plutil", "-convert", "json", "-o", "-", "-"]
|
|
150
|
+
convert_result = subprocess.run(
|
|
151
|
+
convert_cmd, check=False, input=plist_data, capture_output=True, text=True
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
apps = []
|
|
155
|
+
if convert_result.returncode == 0:
|
|
156
|
+
import json
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
data = json.loads(convert_result.stdout)
|
|
160
|
+
for bundle_id, app_info in data.items():
|
|
161
|
+
# Skip system internal apps that are hidden
|
|
162
|
+
if app_info.get("ApplicationType") == "Hidden":
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
apps.append(
|
|
166
|
+
{
|
|
167
|
+
"bundle_id": bundle_id,
|
|
168
|
+
"name": app_info.get(
|
|
169
|
+
"CFBundleDisplayName", app_info.get("CFBundleName", bundle_id)
|
|
170
|
+
),
|
|
171
|
+
"path": app_info.get("Path", ""),
|
|
172
|
+
"version": app_info.get("CFBundleVersion", "Unknown"),
|
|
173
|
+
"type": app_info.get("ApplicationType", "User"),
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
except json.JSONDecodeError:
|
|
177
|
+
pass
|
|
178
|
+
|
|
179
|
+
return apps
|
|
180
|
+
except subprocess.CalledProcessError:
|
|
181
|
+
return []
|
|
182
|
+
|
|
183
|
+
def get_app_state(self, bundle_id: str) -> str:
|
|
184
|
+
"""
|
|
185
|
+
Get app state (running, suspended, etc.).
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
bundle_id: App bundle identifier
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
State string or 'unknown'
|
|
192
|
+
"""
|
|
193
|
+
# Check if app is running by trying to get its PID
|
|
194
|
+
cmd = build_simctl_command("spawn", self.udid, "launchctl", "list")
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
198
|
+
if bundle_id in result.stdout:
|
|
199
|
+
return "running"
|
|
200
|
+
return "not running"
|
|
201
|
+
except subprocess.CalledProcessError:
|
|
202
|
+
return "unknown"
|
|
203
|
+
|
|
204
|
+
def restart_app(self, bundle_id: str, delay: float = RELAUNCH_DELAY_SECONDS) -> bool:
|
|
205
|
+
"""
|
|
206
|
+
Restart an app (terminate then launch).
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
bundle_id: App bundle identifier
|
|
210
|
+
delay: Delay between terminate and launch
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Success status
|
|
214
|
+
"""
|
|
215
|
+
# Terminate
|
|
216
|
+
self.terminate(bundle_id)
|
|
217
|
+
time.sleep(delay)
|
|
218
|
+
|
|
219
|
+
# Launch
|
|
220
|
+
success, _ = self.launch(bundle_id)
|
|
221
|
+
return success
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def main():
|
|
225
|
+
"""Main entry point."""
|
|
226
|
+
parser = argparse.ArgumentParser(description="Control iOS app lifecycle")
|
|
227
|
+
|
|
228
|
+
# Actions
|
|
229
|
+
parser.add_argument("--launch", help="Launch app by bundle ID")
|
|
230
|
+
parser.add_argument("--terminate", help="Terminate app by bundle ID")
|
|
231
|
+
parser.add_argument("--restart", help="Restart app by bundle ID")
|
|
232
|
+
parser.add_argument("--install", help="Install app from .app path")
|
|
233
|
+
parser.add_argument("--uninstall", help="Uninstall app by bundle ID")
|
|
234
|
+
parser.add_argument("--open-url", help="Open URL (deep link)")
|
|
235
|
+
parser.add_argument("--list", action="store_true", help="List installed apps")
|
|
236
|
+
parser.add_argument("--state", help="Get app state by bundle ID")
|
|
237
|
+
|
|
238
|
+
# Options
|
|
239
|
+
parser.add_argument(
|
|
240
|
+
"--wait-for-debugger", action="store_true", help="Wait for debugger when launching"
|
|
241
|
+
)
|
|
242
|
+
parser.add_argument(
|
|
243
|
+
"--udid",
|
|
244
|
+
help="Device UDID (auto-detects booted simulator if not provided)",
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
args = parser.parse_args()
|
|
248
|
+
|
|
249
|
+
# Resolve UDID with auto-detection
|
|
250
|
+
try:
|
|
251
|
+
udid = resolve_udid(args.udid)
|
|
252
|
+
except RuntimeError as e:
|
|
253
|
+
print(f"Error: {e}")
|
|
254
|
+
sys.exit(1)
|
|
255
|
+
|
|
256
|
+
launcher = AppLauncher(udid=udid)
|
|
257
|
+
|
|
258
|
+
# Execute requested action
|
|
259
|
+
if args.launch:
|
|
260
|
+
success, pid = launcher.launch(args.launch, args.wait_for_debugger)
|
|
261
|
+
if success:
|
|
262
|
+
if pid:
|
|
263
|
+
print(f"Launched {args.launch} (PID: {pid})")
|
|
264
|
+
else:
|
|
265
|
+
print(f"Launched {args.launch}")
|
|
266
|
+
else:
|
|
267
|
+
print(f"Failed to launch {args.launch}")
|
|
268
|
+
sys.exit(1)
|
|
269
|
+
|
|
270
|
+
elif args.terminate:
|
|
271
|
+
if launcher.terminate(args.terminate):
|
|
272
|
+
print(f"Terminated {args.terminate}")
|
|
273
|
+
else:
|
|
274
|
+
print(f"Failed to terminate {args.terminate}")
|
|
275
|
+
sys.exit(1)
|
|
276
|
+
|
|
277
|
+
elif args.restart:
|
|
278
|
+
if launcher.restart_app(args.restart):
|
|
279
|
+
print(f"Restarted {args.restart}")
|
|
280
|
+
else:
|
|
281
|
+
print(f"Failed to restart {args.restart}")
|
|
282
|
+
sys.exit(1)
|
|
283
|
+
|
|
284
|
+
elif args.install:
|
|
285
|
+
if launcher.install(args.install):
|
|
286
|
+
print(f"Installed {args.install}")
|
|
287
|
+
else:
|
|
288
|
+
print(f"Failed to install {args.install}")
|
|
289
|
+
sys.exit(1)
|
|
290
|
+
|
|
291
|
+
elif args.uninstall:
|
|
292
|
+
if launcher.uninstall(args.uninstall):
|
|
293
|
+
print(f"Uninstalled {args.uninstall}")
|
|
294
|
+
else:
|
|
295
|
+
print(f"Failed to uninstall {args.uninstall}")
|
|
296
|
+
sys.exit(1)
|
|
297
|
+
|
|
298
|
+
elif args.open_url:
|
|
299
|
+
if launcher.open_url(args.open_url):
|
|
300
|
+
print(f"Opened URL: {args.open_url}")
|
|
301
|
+
else:
|
|
302
|
+
print(f"Failed to open URL: {args.open_url}")
|
|
303
|
+
sys.exit(1)
|
|
304
|
+
|
|
305
|
+
elif args.list:
|
|
306
|
+
apps = launcher.list_apps()
|
|
307
|
+
if apps:
|
|
308
|
+
print(f"Installed apps ({len(apps)}):")
|
|
309
|
+
for app in apps[:APPS_PREVIEW]:
|
|
310
|
+
print(f" {app['bundle_id']}: {app['name']} (v{app['version']})")
|
|
311
|
+
if len(apps) > APPS_PREVIEW:
|
|
312
|
+
print(f" ... and {len(apps) - APPS_PREVIEW} more")
|
|
313
|
+
else:
|
|
314
|
+
print("No apps found or failed to list")
|
|
315
|
+
|
|
316
|
+
elif args.state:
|
|
317
|
+
state = launcher.get_app_state(args.state)
|
|
318
|
+
print(f"{args.state}: {state}")
|
|
319
|
+
|
|
320
|
+
else:
|
|
321
|
+
parser.print_help()
|
|
322
|
+
sys.exit(1)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
if __name__ == "__main__":
|
|
326
|
+
main()
|