@leejungkiin/awkit 1.7.0 → 1.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. package/bin/awk.js +576 -84
  2. package/core/CLAUDE.md +1 -1
  3. package/core/GEMINI.md +148 -167
  4. package/core/GEMINI.md.bak +149 -116
  5. package/core/skill-runtime-manifest.json +3 -0
  6. package/docs/Claude Fable 5.md +3826 -0
  7. package/docs/android_kotlin_system_instruction.md +210 -0
  8. package/docs/brainstorm_ponytail_integration.md +146 -0
  9. package/docs/brainstorm_smart_setup.md +113 -0
  10. package/docs/deep-research-report (1).md +293 -0
  11. package/docs/history/GEMINI.v1.md +135 -0
  12. package/docs/history/brainstorm_antigravity_unified_architecture.v1.md +105 -0
  13. package/docs/history/implementation_plan.v1.md +58 -0
  14. package/package.json +4 -1
  15. package/scripts/artifact-storage.js +130 -0
  16. package/scripts/automation-gate.js +40 -7
  17. package/scripts/claude-plan.js +76 -0
  18. package/scripts/dependency-manager.js +210 -0
  19. package/scripts/exec-rtk.js +11 -5
  20. package/scripts/i18n-helper.js +381 -0
  21. package/scripts/multi-model-pipeline.js +144 -0
  22. package/skill-packs/mobile-ios/pack.json +4 -2
  23. package/skill-packs/reverse-engineering/pack.json +1 -0
  24. package/skills/CATALOG.md +20 -0
  25. package/skills/GEMINI.md +9 -1
  26. package/skills/TRIGGER_INDEX.md +10 -0
  27. package/skills/ai-music/SKILL.md +275 -0
  28. package/skills/android-re-analyzer/SKILL.md +238 -0
  29. package/skills/android-re-analyzer/references/api-extraction-patterns.md +119 -0
  30. package/skills/android-re-analyzer/references/call-flow-analysis.md +176 -0
  31. package/skills/android-re-analyzer/references/fernflower-usage.md +115 -0
  32. package/skills/android-re-analyzer/references/jadx-usage.md +116 -0
  33. package/skills/android-re-analyzer/references/setup-guide.md +221 -0
  34. package/skills/android-re-analyzer/scripts/check-deps.sh +129 -0
  35. package/skills/android-re-analyzer/scripts/decompile.sh +375 -0
  36. package/skills/android-re-analyzer/scripts/find-api-calls.sh +118 -0
  37. package/skills/android-re-analyzer/scripts/install-dep.sh +448 -0
  38. package/skills/animal-island-ui-style/SKILL.md +1450 -0
  39. package/skills/app-store-review-agent/SKILL.md +164 -0
  40. package/skills/app-store-review-agent/references/guidelines/README.md +154 -0
  41. package/skills/app-store-review-agent/references/guidelines/by-app-type/ai_apps.md +37 -0
  42. package/skills/app-store-review-agent/references/guidelines/by-app-type/all_apps.md +50 -0
  43. package/skills/app-store-review-agent/references/guidelines/by-app-type/crypto_finance.md +31 -0
  44. package/skills/app-store-review-agent/references/guidelines/by-app-type/games.md +31 -0
  45. package/skills/app-store-review-agent/references/guidelines/by-app-type/health_fitness.md +31 -0
  46. package/skills/app-store-review-agent/references/guidelines/by-app-type/kids.md +27 -0
  47. package/skills/app-store-review-agent/references/guidelines/by-app-type/macos.md +38 -0
  48. package/skills/app-store-review-agent/references/guidelines/by-app-type/social_ugc.md +32 -0
  49. package/skills/app-store-review-agent/references/guidelines/by-app-type/subscription_iap.md +34 -0
  50. package/skills/app-store-review-agent/references/guidelines/by-app-type/vpn.md +18 -0
  51. package/skills/app-store-review-agent/references/rules/design/minimum_functionality.md +96 -0
  52. package/skills/app-store-review-agent/references/rules/design/sign_in_with_apple.md +54 -0
  53. package/skills/app-store-review-agent/references/rules/entitlements/unused_entitlements.md +83 -0
  54. package/skills/app-store-review-agent/references/rules/metadata/accurate_metadata.md +54 -0
  55. package/skills/app-store-review-agent/references/rules/metadata/apple_trademark.md +99 -0
  56. package/skills/app-store-review-agent/references/rules/metadata/china_storefront.md +72 -0
  57. package/skills/app-store-review-agent/references/rules/metadata/competitor_terms.md +56 -0
  58. package/skills/app-store-review-agent/references/rules/metadata/subscription_metadata.md +81 -0
  59. package/skills/app-store-review-agent/references/rules/privacy/privacy_manifest.md +84 -0
  60. package/skills/app-store-review-agent/references/rules/privacy/unnecessary_data.md +60 -0
  61. package/skills/app-store-review-agent/references/rules/subscription/misleading_pricing.md +63 -0
  62. package/skills/app-store-review-agent/references/rules/subscription/missing_tos_pp.md +54 -0
  63. package/skills/awf-ponytail/SKILL.md +91 -0
  64. package/skills/awf-ponytail-review/SKILL.md +67 -0
  65. package/skills/awf-session-restore/SKILL.md +3 -3
  66. package/skills/brainstorm-agent/SKILL.md +11 -2
  67. package/skills/brainstorm-agent/templates/brief-template.md +8 -0
  68. package/skills/claude-planner/SKILL.md +47 -0
  69. package/skills/code-review/SKILL.md +87 -0
  70. package/skills/expo-game-development/SKILL.md +163 -0
  71. package/skills/flutter/LICENSE.txt +202 -0
  72. package/skills/flutter/SKILL.md +127 -0
  73. package/skills/flutter-project-creater/LICENSE.txt +202 -0
  74. package/skills/flutter-project-creater/SKILL.md +106 -0
  75. package/skills/game-developer/SKILL.md +163 -0
  76. package/skills/game-developer/references/ecs-patterns.md +501 -0
  77. package/skills/game-developer/references/multiplayer-networking.md +475 -0
  78. package/skills/game-developer/references/performance-optimization.md +422 -0
  79. package/skills/game-developer/references/unity-patterns.md +271 -0
  80. package/skills/game-developer/references/unreal-cpp.md +352 -0
  81. package/skills/generate-gui-assets/SKILL.md +305 -0
  82. package/skills/generate-gui-assets/agents/openai.yaml +4 -0
  83. package/skills/generate-gui-assets/references/catalog-schema.md +58 -0
  84. package/skills/generate-gui-assets/references/extraction-techniques.md +21 -0
  85. package/skills/generate-gui-assets/references/prompt-patterns.md +58 -0
  86. package/skills/generate-gui-assets/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
  87. package/skills/generate-gui-assets/scripts/build_gui_contact_sheet.py +51 -0
  88. package/skills/generate-gui-assets/scripts/clean_chroma_edges.py +262 -0
  89. package/skills/generate-gui-assets/scripts/copy_approved_icons.py +64 -0
  90. package/skills/generate-gui-assets/scripts/prepare_gui_asset_run.py +91 -0
  91. package/skills/generate-gui-assets/scripts/suggest_grid_options.py +63 -0
  92. package/skills/generate-gui-assets/scripts/validate_gui_catalog.py +50 -0
  93. package/skills/godot-game-development/SKILL.md +142 -0
  94. package/skills/hatch-pet/LICENSE.txt +201 -0
  95. package/skills/hatch-pet/SKILL.md +420 -0
  96. package/skills/hatch-pet/agents/openai.yaml +4 -0
  97. package/skills/hatch-pet/references/animation-rows.md +29 -0
  98. package/skills/hatch-pet/references/codex-pet-contract.md +35 -0
  99. package/skills/hatch-pet/references/qa-rubric.md +60 -0
  100. package/skills/hatch-pet/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
  101. package/skills/hatch-pet/scripts/clean_chroma_edges.py +262 -0
  102. package/skills/hatch-pet/scripts/compose_atlas.py +150 -0
  103. package/skills/hatch-pet/scripts/derive_running_left_from_running_right.py +143 -0
  104. package/skills/hatch-pet/scripts/extract_strip_frames.py +323 -0
  105. package/skills/hatch-pet/scripts/finalize_pet_run.py +382 -0
  106. package/skills/hatch-pet/scripts/generate_pet_images.py +287 -0
  107. package/skills/hatch-pet/scripts/inspect_frames.py +246 -0
  108. package/skills/hatch-pet/scripts/make_contact_sheet.py +96 -0
  109. package/skills/hatch-pet/scripts/package_custom_pet.py +108 -0
  110. package/skills/hatch-pet/scripts/pet_job_status.py +117 -0
  111. package/skills/hatch-pet/scripts/prepare_pet_run.py +673 -0
  112. package/skills/hatch-pet/scripts/queue_pet_repairs.py +172 -0
  113. package/skills/hatch-pet/scripts/record_imagegen_result.py +250 -0
  114. package/skills/hatch-pet/scripts/render_animation_videos.py +134 -0
  115. package/skills/hatch-pet/scripts/render_animation_videos.sh +5 -0
  116. package/skills/hatch-pet/scripts/validate_atlas.py +139 -0
  117. package/skills/i18n-orchestrator/SKILL.md +37 -0
  118. package/skills/ios-simulator-skill/SKILL.md +390 -0
  119. package/skills/ios-simulator-skill/scripts/accessibility_audit.py +300 -0
  120. package/skills/ios-simulator-skill/scripts/app_launcher.py +326 -0
  121. package/skills/ios-simulator-skill/scripts/app_state_capture.py +400 -0
  122. package/skills/ios-simulator-skill/scripts/appearance.py +385 -0
  123. package/skills/ios-simulator-skill/scripts/build_and_test.py +348 -0
  124. package/skills/ios-simulator-skill/scripts/clipboard.py +103 -0
  125. package/skills/ios-simulator-skill/scripts/common/__init__.py +61 -0
  126. package/skills/ios-simulator-skill/scripts/common/cache_utils.py +289 -0
  127. package/skills/ios-simulator-skill/scripts/common/device_utils.py +462 -0
  128. package/skills/ios-simulator-skill/scripts/common/env_config.py +35 -0
  129. package/skills/ios-simulator-skill/scripts/common/hang_pipeline.py +862 -0
  130. package/skills/ios-simulator-skill/scripts/common/hang_sessions.py +490 -0
  131. package/skills/ios-simulator-skill/scripts/common/idb_utils.py +180 -0
  132. package/skills/ios-simulator-skill/scripts/common/screenshot_utils.py +338 -0
  133. package/skills/ios-simulator-skill/scripts/container.py +668 -0
  134. package/skills/ios-simulator-skill/scripts/gesture.py +394 -0
  135. package/skills/ios-simulator-skill/scripts/hang_watcher.py +1533 -0
  136. package/skills/ios-simulator-skill/scripts/keyboard.py +391 -0
  137. package/skills/ios-simulator-skill/scripts/localization_audit.py +483 -0
  138. package/skills/ios-simulator-skill/scripts/location.py +467 -0
  139. package/skills/ios-simulator-skill/scripts/log_monitor.py +493 -0
  140. package/skills/ios-simulator-skill/scripts/model_inspector.py +645 -0
  141. package/skills/ios-simulator-skill/scripts/navigator.py +461 -0
  142. package/skills/ios-simulator-skill/scripts/privacy_manager.py +310 -0
  143. package/skills/ios-simulator-skill/scripts/push_notification.py +240 -0
  144. package/skills/ios-simulator-skill/scripts/screen_mapper.py +296 -0
  145. package/skills/ios-simulator-skill/scripts/sim_health_check.sh +245 -0
  146. package/skills/ios-simulator-skill/scripts/sim_list.py +299 -0
  147. package/skills/ios-simulator-skill/scripts/simctl_boot.py +312 -0
  148. package/skills/ios-simulator-skill/scripts/simctl_create.py +316 -0
  149. package/skills/ios-simulator-skill/scripts/simctl_delete.py +357 -0
  150. package/skills/ios-simulator-skill/scripts/simctl_erase.py +351 -0
  151. package/skills/ios-simulator-skill/scripts/simctl_shutdown.py +290 -0
  152. package/skills/ios-simulator-skill/scripts/simulator_selector.py +375 -0
  153. package/skills/ios-simulator-skill/scripts/status_bar.py +250 -0
  154. package/skills/ios-simulator-skill/scripts/test_recorder.py +323 -0
  155. package/skills/ios-simulator-skill/scripts/visual_diff.py +235 -0
  156. package/skills/ios-simulator-skill/scripts/xcode/__init__.py +13 -0
  157. package/skills/ios-simulator-skill/scripts/xcode/builder.py +397 -0
  158. package/skills/ios-simulator-skill/scripts/xcode/cache.py +204 -0
  159. package/skills/ios-simulator-skill/scripts/xcode/config.py +178 -0
  160. package/skills/ios-simulator-skill/scripts/xcode/reporter.py +343 -0
  161. package/skills/ios-simulator-skill/scripts/xcode/xcresult.py +451 -0
  162. package/skills/ios-visual-qa-strategist/SKILL.md +111 -0
  163. package/skills/ios-visual-qa-strategist/agents/openai.yaml +4 -0
  164. package/skills/ios-visual-qa-strategist/references/ios-tool-selection.md +61 -0
  165. package/skills/ios-visual-qa-strategist/references/minimal-capture-policy.md +56 -0
  166. package/skills/ios-visual-qa-strategist/references/visual-reasoning-heuristics.md +53 -0
  167. package/skills/orchestrator/SKILL.md +0 -20
  168. package/skills/persistent-storage/SKILL.md +55 -0
  169. package/skills/short-maker/SKILL.md +23 -0
  170. package/skills/short-maker/scripts/effects.js +56 -0
  171. package/skills/short-maker/scripts/shortmaker-bridge.js +332 -0
  172. package/skills/short-maker/scripts/videomix.js +601 -0
  173. package/skills/short-maker/templates/hyperframes/cinematic-character.template.html +172 -0
  174. package/skills/short-maker/templates/hyperframes/index.template.html +194 -0
  175. package/skills/smali-to-kotlin/SKILL.md +128 -0
  176. package/skills/smali-to-kotlin/examples/getting-started/tech-stack.md +58 -0
  177. package/skills/smali-to-kotlin/examples/pipeline/data-ui-parity.md +118 -0
  178. package/skills/smali-to-kotlin/examples/pipeline/scanner-and-bootstrap.md +106 -0
  179. package/skills/smali-to-kotlin/library-patterns.md +189 -0
  180. package/skills/smali-to-kotlin/phase-0-discovery.md +128 -0
  181. package/skills/smali-to-kotlin/phase-1-architecture.md +166 -0
  182. package/skills/smali-to-kotlin/phase-2-blueprint-ui.md +347 -0
  183. package/skills/smali-to-kotlin/phase-2-blueprint.md +228 -0
  184. package/skills/smali-to-kotlin/phase-3-build.md +248 -0
  185. package/skills/smali-to-kotlin/phase-3-logic-build.md +268 -0
  186. package/skills/smali-to-kotlin/smali-reading-guide.md +310 -0
  187. package/skills/smali-to-kotlin/templates/app-map.md +101 -0
  188. package/skills/smali-to-kotlin/templates/architecture.md +142 -0
  189. package/skills/smali-to-kotlin/templates/blueprint.md +145 -0
  190. package/skills/spec-gate/SKILL.md +6 -2
  191. package/skills/symphony-enforcer/SKILL.md +8 -0
  192. package/skills/symphony-enforcer/examples/mindful-stop.md +2 -0
  193. package/skills/symphony-enforcer/examples/three-phase.md +16 -0
  194. package/skills/symphony-enforcer/examples/trigger-points.md +7 -1
  195. package/skills/unity-game-development/SKILL.md +231 -0
  196. package/skills/verification-gate/SKILL.md +4 -2
  197. package/skills/video-edit/SKILL.md +36 -0
  198. package/skills/video-edit/scripts/video_edit.py +324 -0
  199. package/templates/setup-mapping.json +48 -0
  200. package/templates/specs/design-template.md +161 -71
  201. package/templates/specs/requirements-template.md +65 -133
  202. package/templates/specs/task-spec-template.xml +3 -0
  203. package/workflows/_uncategorized/critic.md +40 -0
  204. package/workflows/_uncategorized/git-rebase-flow.md +81 -0
  205. package/workflows/_uncategorized/image-gen.md +118 -0
  206. package/workflows/_uncategorized/multi-model-pipeline.md +60 -0
  207. package/workflows/_uncategorized/pixel-gen.md +86 -0
  208. package/workflows/_uncategorized/pixel-setup.md +90 -0
  209. package/workflows/_uncategorized/ponytail-review.md +59 -0
  210. package/workflows/_uncategorized/reverse-android-build.md +222 -0
  211. package/workflows/_uncategorized/reverse-android-design.md +139 -0
  212. package/workflows/_uncategorized/reverse-android-discover.md +150 -0
  213. package/workflows/_uncategorized/reverse-android-scan.md +158 -0
  214. package/workflows/_uncategorized/reverse-android.md +143 -0
  215. package/workflows/_uncategorized/reverse-ios-build.md +240 -0
  216. package/workflows/_uncategorized/reverse-ios-design.md +112 -0
  217. package/workflows/_uncategorized/reverse-ios-discover.md +120 -0
  218. package/workflows/_uncategorized/reverse-ios-scan.md +155 -0
  219. package/workflows/_uncategorized/reverse-ios.md +152 -0
  220. package/workflows/_uncategorized/safety-router.md +34 -0
  221. package/workflows/_uncategorized/teach.md +89 -0
  222. package/workflows/_uncategorized/verify-ui.md +53 -0
  223. package/workflows/_uncategorized/visualize-screenshots.md +34 -0
  224. package/workflows/ads/ads-analyst.md +201 -0
  225. package/workflows/ads/ads-audit.md +106 -0
  226. package/workflows/ads/ads-optimize.md +97 -0
  227. package/workflows/ads/ads-targeting.md +241 -0
  228. package/workflows/ads/adsExpert.md +160 -0
  229. package/workflows/ads/smali-ads-config.md +400 -0
  230. package/workflows/ads/smali-ads-flow.md +331 -0
  231. package/workflows/ads/smali-ads-interstitial.md +377 -0
  232. package/workflows/ads/smali-ads-native.md +382 -0
  233. package/workflows/context/teach.md +89 -0
  234. package/workflows/gitnexus.md +8 -8
  235. package/workflows/lifecycle/brainstorm.md +43 -0
  236. package/workflows/lifecycle/code.md +5 -0
  237. package/workflows/lifecycle/init.md +23 -5
  238. package/workflows/lifecycle/multi-model-pipeline.md +60 -0
  239. package/workflows/quality/ponytail-review.md +59 -0
  240. package/workflows/roles/critic.md +40 -0
  241. package/workflows/roles/safety-router.md +34 -0
@@ -0,0 +1,645 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Core Data / SwiftData Model Inspector
4
+
5
+ Parse .xcdatamodeld packages and @Model declarations from project source files.
6
+ Zero external dependencies — uses stdlib xml and regex.
7
+
8
+ Usage:
9
+ python scripts/model_inspector.py --project-path /path/to/project
10
+ python scripts/model_inspector.py --project-path . --verbose
11
+ python scripts/model_inspector.py --project-path . --json
12
+ python scripts/model_inspector.py --project-path . --show-versions
13
+ """
14
+
15
+ import argparse
16
+ import json
17
+ import plistlib
18
+ import re
19
+ import sys
20
+ from pathlib import Path
21
+ from xml.etree import ElementTree
22
+
23
+
24
+ class ModelInspector:
25
+ """Inspects Core Data and SwiftData models from project source files."""
26
+
27
+ def __init__(self, project_path: str):
28
+ """Initialize inspector.
29
+
30
+ Args:
31
+ project_path: Path to Xcode project root directory
32
+ """
33
+ self.project_path = Path(project_path).resolve()
34
+
35
+ def execute(
36
+ self,
37
+ *,
38
+ core_data_only: bool = False,
39
+ swiftdata_only: bool = False,
40
+ show_versions: bool = False,
41
+ ) -> tuple[bool, dict]:
42
+ """Inspect models and return structured results.
43
+
44
+ Args:
45
+ core_data_only: Skip SwiftData scanning
46
+ swiftdata_only: Skip Core Data scanning
47
+ show_versions: Include version history details
48
+ Returns:
49
+ (success, results_dict) tuple
50
+ """
51
+ if not self.project_path.is_dir():
52
+ return False, {"error": f"Directory not found: {self.project_path}"}
53
+
54
+ results: dict = {"core_data": [], "swiftdata": []}
55
+
56
+ if not swiftdata_only:
57
+ packages = self._find_xcdatamodeld()
58
+ for package in packages:
59
+ parsed = self._parse_xcdatamodeld(package, show_versions=show_versions)
60
+ if parsed:
61
+ results["core_data"].append(parsed)
62
+
63
+ if not core_data_only:
64
+ models = self._find_swiftdata_models()
65
+ results["swiftdata"] = models
66
+
67
+ has_results = bool(results["core_data"]) or bool(results["swiftdata"])
68
+ return has_results, results
69
+
70
+ # === RAW SOURCE EXTRACTION ===
71
+
72
+ def get_raw_source(self, model_name: str) -> tuple[bool, str]:
73
+ """Get raw source for a named model.
74
+
75
+ Searches SwiftData @Model classes first, then Core Data XML entities.
76
+
77
+ Args:
78
+ model_name: Class or entity name to look up
79
+
80
+ Returns:
81
+ (success, raw_source) tuple
82
+ """
83
+ # Search SwiftData files
84
+ swift_files = sorted(self.project_path.rglob("*.swift"))
85
+ skip_dirs = {"DerivedData", "Pods", "Carthage"}
86
+
87
+ model_pattern = re.compile(
88
+ r"@Model\s*\n\s*(?:final\s+)?class\s+(\w+)",
89
+ re.MULTILINE,
90
+ )
91
+
92
+ for swift_file in swift_files:
93
+ if any(
94
+ part in skip_dirs or part.startswith(".")
95
+ for part in swift_file.relative_to(self.project_path).parts
96
+ ):
97
+ continue
98
+
99
+ try:
100
+ content = swift_file.read_text(encoding="utf-8")
101
+ except (OSError, UnicodeDecodeError):
102
+ continue
103
+
104
+ for match in model_pattern.finditer(content):
105
+ if match.group(1) == model_name:
106
+ class_start = match.start()
107
+ body = self._extract_class_body(content, match.end())
108
+ if body is None:
109
+ continue
110
+ # Find closing brace position
111
+ brace_start = content.find("{", match.end())
112
+ depth = 0
113
+ end = brace_start
114
+ for i in range(brace_start, len(content)):
115
+ if content[i] == "{":
116
+ depth += 1
117
+ elif content[i] == "}":
118
+ depth -= 1
119
+ if depth == 0:
120
+ end = i + 1
121
+ break
122
+ rel_path = swift_file.relative_to(self.project_path)
123
+ raw = content[class_start:end]
124
+ return True, f"// {rel_path}\n{raw}"
125
+
126
+ # Search Core Data XML
127
+ for package in self._find_xcdatamodeld():
128
+ current_version = self._detect_current_version(package)
129
+ versions = [d for d in package.iterdir() if d.suffix == ".xcdatamodel"]
130
+ target = current_version or (versions[0].name if versions else None)
131
+ if not target:
132
+ continue
133
+ contents_path = package / target / "contents"
134
+ if not contents_path.exists():
135
+ continue
136
+ try:
137
+ tree = ElementTree.parse(contents_path)
138
+ except ElementTree.ParseError:
139
+ continue
140
+ for entity_elem in tree.getroot().findall("entity"):
141
+ if entity_elem.get("name") == model_name:
142
+ raw_xml = ElementTree.tostring(entity_elem, encoding="unicode")
143
+ return True, f"<!-- {package.name}/{target}/contents -->\n{raw_xml}"
144
+
145
+ return False, f"Model '{model_name}' not found"
146
+
147
+ # === CORE DATA PARSING ===
148
+
149
+ def _find_xcdatamodeld(self) -> list[Path]:
150
+ """Find all .xcdatamodeld packages in the project."""
151
+ return sorted(self.project_path.rglob("*.xcdatamodeld"))
152
+
153
+ def _parse_xcdatamodeld(
154
+ self, package_path: Path, *, show_versions: bool = False
155
+ ) -> dict | None:
156
+ """Parse a .xcdatamodeld package into structured data.
157
+
158
+ Args:
159
+ package_path: Path to the .xcdatamodeld directory
160
+ show_versions: Include all version details
161
+
162
+ Returns:
163
+ Parsed model data or None on error
164
+ """
165
+ current_version = self._detect_current_version(package_path)
166
+ versions = sorted(
167
+ [d.name for d in package_path.iterdir() if d.suffix == ".xcdatamodel"],
168
+ )
169
+
170
+ result: dict = {
171
+ "package": package_path.name,
172
+ "current_version": current_version,
173
+ "version_count": len(versions),
174
+ "entities": [],
175
+ }
176
+
177
+ if show_versions:
178
+ result["versions"] = [{"name": v, "is_current": v == current_version} for v in versions]
179
+
180
+ # Parse the current version (or first available)
181
+ target_version = current_version or (versions[0] if versions else None)
182
+ if not target_version:
183
+ return result
184
+
185
+ contents_path = package_path / target_version / "contents"
186
+ if not contents_path.exists():
187
+ return result
188
+
189
+ entities = self._parse_contents_xml(contents_path)
190
+ result["entities"] = entities
191
+
192
+ return result
193
+
194
+ def _detect_current_version(self, package_path: Path) -> str | None:
195
+ """Read .xccurrentversion plist to find active model version.
196
+
197
+ Args:
198
+ package_path: Path to the .xcdatamodeld directory
199
+
200
+ Returns:
201
+ Current version name or None
202
+ """
203
+ plist_path = package_path / ".xccurrentversion"
204
+ if not plist_path.exists():
205
+ return None
206
+
207
+ try:
208
+ with open(plist_path, "rb") as f:
209
+ plist = plistlib.load(f)
210
+ return plist.get("_XCCurrentVersionName")
211
+ except Exception:
212
+ return None
213
+
214
+ def _parse_contents_xml(self, xml_path: Path) -> list[dict]:
215
+ """Parse a Core Data contents XML file.
216
+
217
+ Args:
218
+ xml_path: Path to the contents XML file
219
+
220
+ Returns:
221
+ List of entity dicts with attributes, relationships, and fetch requests
222
+ """
223
+ try:
224
+ tree = ElementTree.parse(xml_path)
225
+ except ElementTree.ParseError:
226
+ return []
227
+
228
+ root = tree.getroot()
229
+ entities = []
230
+
231
+ for entity_elem in root.findall("entity"):
232
+ entity: dict = {
233
+ "name": entity_elem.get("name", ""),
234
+ "is_abstract": entity_elem.get("isAbstract") == "YES",
235
+ "parent_entity": entity_elem.get("parentEntity"),
236
+ "represented_class": entity_elem.get("representedClassName"),
237
+ "attributes": [],
238
+ "relationships": [],
239
+ "fetch_requests": [],
240
+ }
241
+
242
+ for attr in entity_elem.findall("attribute"):
243
+ entity["attributes"].append(
244
+ {
245
+ "name": attr.get("name", ""),
246
+ "type": attr.get("attributeType", ""),
247
+ "optional": attr.get("optional") == "YES",
248
+ "default_value": attr.get("defaultValueString"),
249
+ }
250
+ )
251
+
252
+ for rel in entity_elem.findall("relationship"):
253
+ entity["relationships"].append(
254
+ {
255
+ "name": rel.get("name", ""),
256
+ "destination": rel.get("destinationEntity", ""),
257
+ "to_many": rel.get("toMany") == "YES",
258
+ "inverse": rel.get("inverseName"),
259
+ "optional": rel.get("optional") == "YES",
260
+ }
261
+ )
262
+
263
+ for req in entity_elem.findall("fetchRequest"):
264
+ entity["fetch_requests"].append(
265
+ {
266
+ "name": req.get("name", ""),
267
+ "predicate": req.get("predicateString", ""),
268
+ }
269
+ )
270
+
271
+ # Remove None parent_entity for cleaner output
272
+ if entity["parent_entity"] is None:
273
+ del entity["parent_entity"]
274
+
275
+ entities.append(entity)
276
+
277
+ return entities
278
+
279
+ # === SWIFTDATA EXTRACTION ===
280
+
281
+ def _find_swiftdata_models(self) -> list[dict]:
282
+ """Best-effort regex extraction of @Model classes from .swift files.
283
+
284
+ Returns:
285
+ List of detected model dicts
286
+ """
287
+ models = []
288
+ swift_files = sorted(self.project_path.rglob("*.swift"))
289
+
290
+ # Skip common non-source directories and any dotdirs (e.g. .archived, .build, .git)
291
+ skip_dirs = {"DerivedData", "Pods", "Carthage"}
292
+
293
+ for swift_file in swift_files:
294
+ if any(
295
+ part in skip_dirs or part.startswith(".")
296
+ for part in swift_file.relative_to(self.project_path).parts
297
+ ):
298
+ continue
299
+
300
+ try:
301
+ content = swift_file.read_text(encoding="utf-8")
302
+ except (OSError, UnicodeDecodeError):
303
+ continue
304
+
305
+ file_models = self._extract_models_from_swift(content, swift_file)
306
+ models.extend(file_models)
307
+
308
+ return models
309
+
310
+ def _extract_models_from_swift(self, content: str, file_path: Path) -> list[dict]:
311
+ """Extract @Model class declarations from Swift source.
312
+
313
+ Args:
314
+ content: Swift source file content
315
+ file_path: Path to the source file (for reporting)
316
+
317
+ Returns:
318
+ List of model dicts found in this file
319
+ """
320
+ models = []
321
+
322
+ # Match @Model decorator followed by class declaration
323
+ model_pattern = re.compile(
324
+ r"@Model\s*\n\s*(?:final\s+)?class\s+(\w+)",
325
+ re.MULTILINE,
326
+ )
327
+
328
+ for match in model_pattern.finditer(content):
329
+ class_name = match.group(1)
330
+ class_start = match.end()
331
+
332
+ # Find the class body by counting braces
333
+ body = self._extract_class_body(content, class_start)
334
+ if body is None:
335
+ continue
336
+
337
+ properties = self._extract_swift_properties(body)
338
+ relationships = self._extract_swift_relationships(body)
339
+
340
+ models.append(
341
+ {
342
+ "class_name": class_name,
343
+ "file": str(file_path.relative_to(self.project_path)),
344
+ "properties": properties,
345
+ "relationships": relationships,
346
+ }
347
+ )
348
+
349
+ return models
350
+
351
+ def _extract_class_body(self, content: str, start: int) -> str | None:
352
+ """Extract class body by matching braces.
353
+
354
+ Args:
355
+ content: Full file content
356
+ start: Position after class name
357
+
358
+ Returns:
359
+ Class body string or None if braces don't balance
360
+ """
361
+ brace_start = content.find("{", start)
362
+ if brace_start == -1:
363
+ return None
364
+
365
+ depth = 0
366
+ for i in range(brace_start, len(content)):
367
+ if content[i] == "{":
368
+ depth += 1
369
+ elif content[i] == "}":
370
+ depth -= 1
371
+ if depth == 0:
372
+ return content[brace_start + 1 : i]
373
+
374
+ return None
375
+
376
+ def _extract_swift_properties(self, body: str) -> list[dict]:
377
+ """Extract stored properties from a Swift class body.
378
+
379
+ Args:
380
+ body: Class body content
381
+
382
+ Returns:
383
+ List of property dicts
384
+ """
385
+ properties = []
386
+ # Match var/let declarations (skip computed properties with { get })
387
+ prop_pattern = re.compile(
388
+ r"^\s*(?:var|let)\s+(\w+)\s*:\s*([^\n{=]+?)(?:\s*=\s*[^\n]+)?$",
389
+ re.MULTILINE,
390
+ )
391
+
392
+ for match in prop_pattern.finditer(body):
393
+ name = match.group(1)
394
+ # Strip trailing inline comments (// ...)
395
+ prop_type = re.sub(r"\s*//.*$", "", match.group(2)).strip()
396
+
397
+ # Skip if preceded by @Relationship on current or previous line
398
+ line_start = body.rfind("\n", 0, match.start()) + 1
399
+ prev_line_start = body.rfind("\n", 0, max(0, line_start - 1)) + 1
400
+ preceding = body[prev_line_start : match.start()]
401
+ if "@Relationship" in preceding:
402
+ continue
403
+
404
+ properties.append({"name": name, "type": prop_type})
405
+
406
+ return properties
407
+
408
+ def _extract_swift_relationships(self, body: str) -> list[dict]:
409
+ """Extract @Relationship properties from a Swift class body.
410
+
411
+ Args:
412
+ body: Class body content
413
+
414
+ Returns:
415
+ List of relationship dicts
416
+ """
417
+ relationships = []
418
+ rel_pattern = re.compile(
419
+ r"@Relationship[^\n]*\n\s*(?:var|let)\s+(\w+)\s*:\s*([^\n{=]+)",
420
+ re.MULTILINE,
421
+ )
422
+
423
+ for match in rel_pattern.finditer(body):
424
+ name = match.group(1)
425
+ rel_type = re.sub(r"\s*//.*$", "", match.group(2)).strip()
426
+ to_many = rel_type.startswith("[") or "Array<" in rel_type
427
+
428
+ relationships.append(
429
+ {
430
+ "name": name,
431
+ "type": rel_type,
432
+ "to_many": to_many,
433
+ }
434
+ )
435
+
436
+ return relationships
437
+
438
+
439
+ # === OUTPUT FORMATTING ===
440
+
441
+
442
+ def format_default(results: dict) -> str:
443
+ """Format results as token-efficient summary.
444
+
445
+ Args:
446
+ results: Parsed model data
447
+
448
+ Returns:
449
+ Formatted string (3-5 lines)
450
+ """
451
+ lines = []
452
+
453
+ for model in results["core_data"]:
454
+ entities = model["entities"]
455
+ attr_count = sum(len(e["attributes"]) for e in entities)
456
+ rel_count = sum(len(e["relationships"]) for e in entities)
457
+
458
+ version_info = f"{model['version_count']} version"
459
+ if model["version_count"] != 1:
460
+ version_info += "s"
461
+ if model["current_version"]:
462
+ current = model["current_version"].replace(".xcdatamodel", "")
463
+ version_info += f", current: {current}"
464
+
465
+ lines.append(f"Core Data: {model['package']} ({version_info})")
466
+ lines.append(
467
+ f" {len(entities)} entities, {attr_count} attributes, {rel_count} relationships"
468
+ )
469
+
470
+ if results["swiftdata"]:
471
+ count = len(results["swiftdata"])
472
+ names = ", ".join(m["class_name"] for m in results["swiftdata"])
473
+ lines.append(f"SwiftData: {count} @Model class{'es' if count != 1 else ''} ({names})")
474
+
475
+ return "\n".join(lines)
476
+
477
+
478
+ def format_verbose(results: dict) -> str:
479
+ """Format results with full details.
480
+
481
+ Args:
482
+ results: Parsed model data
483
+
484
+ Returns:
485
+ Formatted string with entity/attribute/relationship details
486
+ """
487
+ lines = []
488
+
489
+ for model in results["core_data"]:
490
+ lines.append(f"Core Data: {model['package']}")
491
+
492
+ if model.get("versions"):
493
+ version_list = []
494
+ for v in model["versions"]:
495
+ name = v["name"].replace(".xcdatamodel", "")
496
+ suffix = " (current)" if v["is_current"] else ""
497
+ version_list.append(f"{name}{suffix}")
498
+ lines.append(f" Versions: {', '.join(version_list)}")
499
+
500
+ if model.get("current_version"):
501
+ current = model["current_version"].replace(".xcdatamodel", "")
502
+ lines.append(f" Current version: {current}")
503
+
504
+ lines.append("")
505
+
506
+ for entity in model["entities"]:
507
+ attr_count = len(entity["attributes"])
508
+ rel_count = len(entity["relationships"])
509
+ abstract = " (abstract)" if entity.get("is_abstract") else ""
510
+ parent = f" extends {entity['parent_entity']}" if entity.get("parent_entity") else ""
511
+ lines.append(
512
+ f" Entity: {entity['name']}{abstract}{parent}"
513
+ f" ({attr_count} attributes, {rel_count} relationships)"
514
+ )
515
+
516
+ if entity["attributes"]:
517
+ lines.append(" Attributes:")
518
+ for attr in entity["attributes"]:
519
+ optional = "optional" if attr["optional"] else "required"
520
+ default = f", default: {attr['default_value']}" if attr["default_value"] else ""
521
+ lines.append(f" - {attr['name']}: {attr['type']} ({optional}{default})")
522
+
523
+ if entity["relationships"]:
524
+ lines.append(" Relationships:")
525
+ for rel in entity["relationships"]:
526
+ cardinality = "toMany" if rel["to_many"] else "toOne"
527
+ inverse = f", inverse: {rel['inverse']}" if rel["inverse"] else ""
528
+ lines.append(
529
+ f" - {rel['name']}: {rel['destination']} ({cardinality}{inverse})"
530
+ )
531
+
532
+ if entity["fetch_requests"]:
533
+ lines.append(" Fetch Requests:")
534
+ for req in entity["fetch_requests"]:
535
+ lines.append(f" - {req['name']}: {req['predicate']}")
536
+
537
+ lines.append("")
538
+
539
+ if results["swiftdata"]:
540
+ lines.append("SwiftData:")
541
+ for model in results["swiftdata"]:
542
+ prop_count = len(model["properties"])
543
+ rel_count = len(model["relationships"])
544
+ lines.append(
545
+ f" @Model {model['class_name']} ({prop_count} properties, {rel_count} relationships)"
546
+ )
547
+ lines.append(f" File: {model['file']}")
548
+
549
+ if model["properties"]:
550
+ for prop in model["properties"]:
551
+ lines.append(f" - {prop['name']}: {prop['type']}")
552
+
553
+ if model["relationships"]:
554
+ for rel in model["relationships"]:
555
+ cardinality = "toMany" if rel["to_many"] else "toOne"
556
+ lines.append(f" @Relationship {rel['name']}: {rel['type']} ({cardinality})")
557
+
558
+ lines.append("")
559
+
560
+ return "\n".join(lines).rstrip()
561
+
562
+
563
+ def main():
564
+ """Main entry point."""
565
+ parser = argparse.ArgumentParser(
566
+ description="Inspect Core Data and SwiftData models from project files",
567
+ formatter_class=argparse.RawDescriptionHelpFormatter,
568
+ epilog="""
569
+ Examples:
570
+ python scripts/model_inspector.py --project-path /path/to/project
571
+ python scripts/model_inspector.py --project-path . --verbose
572
+ python scripts/model_inspector.py --project-path . --json
573
+ python scripts/model_inspector.py --project-path . --show-versions
574
+ python scripts/model_inspector.py --project-path . --core-data-only
575
+ python scripts/model_inspector.py --project-path . --raw TrainingSession
576
+ """,
577
+ )
578
+
579
+ inspection_group = parser.add_argument_group("Inspection Options")
580
+ inspection_group.add_argument(
581
+ "--project-path",
582
+ default=".",
583
+ help="Path to Xcode project root (default: current directory)",
584
+ )
585
+ inspection_group.add_argument(
586
+ "--core-data-only",
587
+ action="store_true",
588
+ help="Only inspect Core Data .xcdatamodeld packages",
589
+ )
590
+ inspection_group.add_argument(
591
+ "--swiftdata-only",
592
+ action="store_true",
593
+ help="Only inspect SwiftData @Model classes",
594
+ )
595
+ inspection_group.add_argument(
596
+ "--show-versions",
597
+ action="store_true",
598
+ help="List all model versions with current version highlighted",
599
+ )
600
+ inspection_group.add_argument(
601
+ "--raw",
602
+ metavar="MODEL_NAME",
603
+ help="Dump raw source for a specific model (Swift class or Core Data entity)",
604
+ )
605
+
606
+ output_group = parser.add_argument_group("Output Options")
607
+ output_group.add_argument("--json", action="store_true", help="Output as JSON")
608
+ output_group.add_argument("--verbose", action="store_true", help="Show full details")
609
+
610
+ args = parser.parse_args()
611
+
612
+ inspector = ModelInspector(project_path=args.project_path)
613
+
614
+ if args.raw:
615
+ success, raw = inspector.get_raw_source(args.raw)
616
+ if success:
617
+ print(raw)
618
+ else:
619
+ print(f"Error: {raw}", file=sys.stderr)
620
+ sys.exit(1)
621
+ return
622
+
623
+ success, results = inspector.execute(
624
+ core_data_only=args.core_data_only,
625
+ swiftdata_only=args.swiftdata_only,
626
+ show_versions=args.show_versions,
627
+ )
628
+
629
+ if not success:
630
+ if "error" in results:
631
+ print(f"Error: {results['error']}", file=sys.stderr)
632
+ else:
633
+ print("No Core Data or SwiftData models found", file=sys.stderr)
634
+ sys.exit(1)
635
+
636
+ if args.json:
637
+ print(json.dumps(results, indent=2, default=str))
638
+ elif args.verbose or args.show_versions:
639
+ print(format_verbose(results))
640
+ else:
641
+ print(format_default(results))
642
+
643
+
644
+ if __name__ == "__main__":
645
+ main()