@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,58 @@
1
+ # GUI Asset Catalog Schema
2
+
3
+ ## catalog.json
4
+
5
+ Use one catalog per generation run.
6
+
7
+ ```json
8
+ {
9
+ "runId": "gui-v1",
10
+ "createdAt": "2026-05-23T00:00:00Z",
11
+ "styleNotes": "cozy mobile wellness garden icons",
12
+ "chromaKey": "#FF00FF",
13
+ "packs": [
14
+ {
15
+ "id": "ui_nav_core",
16
+ "grid": "4x3",
17
+ "icons": ["home", "garden"],
18
+ "promptFile": "prompts/ui_nav_core.txt",
19
+ "rawFile": "raw/ui_nav_core.png",
20
+ "source": {
21
+ "generator": "imagegen",
22
+ "path": "/absolute/path/to/generated/ig_*.png",
23
+ "recordedAt": "2026-05-23T00:00:00Z"
24
+ },
25
+ "qa": {
26
+ "state": "pending",
27
+ "notes": ""
28
+ }
29
+ }
30
+ ]
31
+ }
32
+ ```
33
+
34
+ ## icons_manifest.json
35
+
36
+ Use after extraction/copy approval.
37
+
38
+ ```json
39
+ {
40
+ "runId": "gui-v1",
41
+ "sourceDir": "assets/ui/generated/gui-v1/manual_crop/extracted_icons_mask",
42
+ "technique": "copy-approved-icons-as-is",
43
+ "icons": [
44
+ {
45
+ "name": "home",
46
+ "file": "icons/home.png",
47
+ "source": "source-file.png"
48
+ }
49
+ ]
50
+ }
51
+ ```
52
+
53
+ ## Rules
54
+
55
+ - Keep paths relative to the run directory when possible.
56
+ - Store absolute generator source paths only for provenance.
57
+ - Use `qa.state` values: `pending`, `approved`, `regenerate`, or `rejected`.
58
+ - Do not mark a pack approved until a contact sheet or direct visual inspection exists.
@@ -0,0 +1,21 @@
1
+ # Extraction Techniques
2
+
3
+ ## Approved Extraction Rule
4
+
5
+ When a source package says an extraction method is approved, do not recut, resegment, resize, or visually modify the source icons.
6
+
7
+ Allowed deterministic actions:
8
+
9
+ - Copy icons as-is into a flat `icons/` folder.
10
+ - Rename by semantic mapping.
11
+ - Add duplicate-safe suffixes such as `_2` and `_3`.
12
+ - Record full provenance in `icons_manifest.json`.
13
+
14
+ ## When To Stop
15
+
16
+ Stop and ask for direction if:
17
+
18
+ - The source icon count does not match the semantic mapping.
19
+ - The source package includes mixed extraction methods and no approved technique is identified.
20
+ - The user asks for visual cleanup that would alter approved pixels.
21
+ - File names cannot be mapped deterministically to semantic names.
@@ -0,0 +1,58 @@
1
+ # GUI Asset Prompt Patterns
2
+
3
+ ## Master Atlas
4
+
5
+ Use for early style discovery across many related packs.
6
+
7
+ ```text
8
+ Create one high-resolution GUI asset master atlas on a flat #FF00FF chroma-key background.
9
+ Style: <product/game style>, consistent palette, crisp clean edges, readable silhouettes.
10
+ Groups: <number> labeled only in this prompt, not in the image.
11
+ Each group uses its own grid: <pack name> <grid> with icons <names in row order>.
12
+ Layout: equal spacing, centered icons, 10% safe margin, no object touches cell borders.
13
+ Quality rules: no text, no labels, no guide marks, no shadows, no glow, no scenery, no duplicate generic placeholders, no clipping, no overlap.
14
+ ```
15
+
16
+ ## Bulk Canvas Atlas
17
+
18
+ Use after the user confirms preview style and chooses a uniform slicing grid. This is the default bulk strategy for reducing generation count.
19
+
20
+ ```text
21
+ Create one GUI icon atlas on a flat #FF00FF chroma-key background.
22
+ Style lock: match the approved preview exactly: <frozen style language>.
23
+ Canvas grid: <columns>x<rows> total icon cells, equal cell size, consistent spacing, centered icons, 10% safe margin.
24
+ Icons in row order, left to right then top to bottom: <names>.
25
+ Keep related icons near each other when possible, but preserve one icon per grid cell.
26
+ Quality rules: no text, no labels, no guide marks, no shadows, no glow, no scenery, no duplicate generic placeholders, no clipping, no overlap.
27
+ ```
28
+
29
+ ## Focused Pack
30
+
31
+ Use when one pack is weak but the global style is approved.
32
+
33
+ ```text
34
+ Create one GUI icon atlas on a flat #FF00FF chroma-key background.
35
+ Style lock: match the approved pack family: <frozen style language>.
36
+ Grid: <columns>x<rows>, equal cells, centered icons, generous padding.
37
+ Icons in row order: <names>.
38
+ Quality rules: no text, no labels, no shadows, no glow, no guides, no duplicate placeholders, no clipping, no overlap.
39
+ ```
40
+
41
+ ## Game HUD Pack
42
+
43
+ ```text
44
+ Create a game HUD asset atlas on a flat #FF00FF chroma-key background.
45
+ Style: <game art direction>, readable at small sizes, clean silhouettes.
46
+ Grid: <columns>x<rows>, equal cells.
47
+ Assets in row order: <health, mana, coin, timer, etc.>.
48
+ Do not include numbers, text, labels, panels, background scenery, cast shadows, glow, or particle effects.
49
+ ```
50
+
51
+ ## Repair Language
52
+
53
+ Add only the failing constraint:
54
+
55
+ - For clipping: `Every icon must stay 10-12% away from cell borders; no icon touches or crosses an edge.`
56
+ - For semantic drift: `Each icon must clearly represent its exact named concept; do not use generic circles or repeated placeholders.`
57
+ - For style drift: `Match the approved family exactly: same outline weight, palette, shape language, and shading depth.`
58
+ - For extraction issues: `Use a perfectly flat #FF00FF background with no antialiasing into the background color.`
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env python3
2
+ """Build a GUI asset contact sheet from catalog raw files."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import math
9
+ import shutil
10
+ import subprocess
11
+ from pathlib import Path
12
+
13
+
14
+ def main() -> None:
15
+ parser = argparse.ArgumentParser()
16
+ parser.add_argument("--run-dir", required=True)
17
+ parser.add_argument("--output", default="qa/contact_sheet.png")
18
+ parser.add_argument("--background", default="#FFF9F1")
19
+ args = parser.parse_args()
20
+
21
+ run_dir = Path(args.run_dir)
22
+ catalog = json.loads((run_dir / "catalog.json").read_text(encoding="utf-8"))
23
+ images = [run_dir / pack["rawFile"] for pack in catalog.get("packs", []) if (run_dir / pack["rawFile"]).exists()]
24
+ if not images:
25
+ raise SystemExit("no raw pack images found")
26
+
27
+ output = run_dir / args.output
28
+ output.parent.mkdir(parents=True, exist_ok=True)
29
+
30
+ magick = shutil.which("magick")
31
+ montage = shutil.which("montage")
32
+ if magick:
33
+ tile_cols = min(3, max(1, math.ceil(math.sqrt(len(images)))))
34
+ tile = f"{tile_cols}x"
35
+ subprocess.run(
36
+ [magick, "montage", *map(str, images), "-tile", tile, "-geometry", "+18+18", "-background", args.background, str(output)],
37
+ check=True,
38
+ )
39
+ elif montage:
40
+ subprocess.run(
41
+ [montage, *map(str, images), "-tile", "3x", "-geometry", "+18+18", "-background", args.background, str(output)],
42
+ check=True,
43
+ )
44
+ else:
45
+ raise SystemExit("ImageMagick not found: install magick or montage to build contact sheet")
46
+
47
+ print(output)
48
+
49
+
50
+ if __name__ == "__main__":
51
+ main()
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env python3
2
+ """Clean leftover chroma-key fringe from transparent PNG assets.
3
+
4
+ This is intentionally conservative: it only targets low-alpha pixels close to the
5
+ configured key color, then bleeds nearby foreground RGB into fully transparent
6
+ edge pixels so image scaling cannot sample hidden chroma color.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ from pathlib import Path
13
+
14
+ import numpy as np
15
+ from PIL import Image
16
+
17
+
18
+ DEFAULT_PATHS = (
19
+ "assets/ui/icons/v2/final",
20
+ "assets/mascot/v2/final",
21
+ "assets/mascot/v3/final",
22
+ "assets/illustrations/v1/final",
23
+ )
24
+
25
+
26
+ def smoothstep(edge0: float, edge1: float, x: np.ndarray) -> np.ndarray:
27
+ t = np.clip((x - edge0) / (edge1 - edge0), 0.0, 1.0)
28
+ return t * t * (3.0 - 2.0 * t)
29
+
30
+
31
+ def shifted(values: np.ndarray, dy: int, dx: int) -> np.ndarray:
32
+ out = np.zeros_like(values)
33
+ src_y0 = max(0, -dy)
34
+ src_y1 = values.shape[0] - max(0, dy)
35
+ src_x0 = max(0, -dx)
36
+ src_x1 = values.shape[1] - max(0, dx)
37
+ dst_y0 = max(0, dy)
38
+ dst_y1 = values.shape[0] - max(0, -dy)
39
+ dst_x0 = max(0, dx)
40
+ dst_x1 = values.shape[1] - max(0, -dx)
41
+ out[dst_y0:dst_y1, dst_x0:dst_x1] = values[src_y0:src_y1, src_x0:src_x1]
42
+ return out
43
+
44
+
45
+ def bleed_rgb(
46
+ rgb: np.ndarray,
47
+ alpha: np.ndarray,
48
+ chroma_mask: np.ndarray,
49
+ target_mask: np.ndarray,
50
+ iterations: int,
51
+ ) -> np.ndarray:
52
+ result = rgb.copy()
53
+ valid = (alpha > 8) & ~chroma_mask
54
+ seed = valid.copy()
55
+ remaining = target_mask.copy()
56
+
57
+ for _ in range(iterations):
58
+ accum = np.zeros_like(result, dtype=np.float32)
59
+ counts = np.zeros(alpha.shape, dtype=np.float32)
60
+
61
+ for dy in (-1, 0, 1):
62
+ for dx in (-1, 0, 1):
63
+ if dy == 0 and dx == 0:
64
+ continue
65
+ neighbor_valid = shifted(seed, dy, dx)
66
+ fill = remaining & neighbor_valid
67
+ if not np.any(fill):
68
+ continue
69
+ neighbor_rgb = shifted(result, dy, dx)
70
+ accum[fill] += neighbor_rgb[fill]
71
+ counts[fill] += 1.0
72
+
73
+ filled = counts > 0
74
+ if not np.any(filled):
75
+ break
76
+ result[filled] = np.clip(accum[filled] / counts[filled, None], 0, 255).astype(np.uint8)
77
+ seed[filled] = True
78
+ remaining[filled] = False
79
+
80
+ return result
81
+
82
+
83
+ def expand_mask(mask: np.ndarray, iterations: int) -> np.ndarray:
84
+ result = mask.copy()
85
+ for _ in range(iterations):
86
+ expanded = result.copy()
87
+ for dy in (-1, 0, 1):
88
+ for dx in (-1, 0, 1):
89
+ if dy == 0 and dx == 0:
90
+ continue
91
+ expanded |= shifted(result, dy, dx)
92
+ if np.array_equal(expanded, result):
93
+ break
94
+ result = expanded
95
+ return result
96
+
97
+
98
+ def clean_array(
99
+ rgba: np.ndarray,
100
+ key: tuple[int, int, int],
101
+ hard_dist: float,
102
+ soft_dist: float,
103
+ max_alpha: int,
104
+ bleed_iterations: int,
105
+ edge_despill: bool,
106
+ edge_radius: int,
107
+ edge_strength: float,
108
+ ) -> tuple[np.ndarray, dict[str, int]]:
109
+ arr = rgba.copy()
110
+ rgb = arr[..., :3].astype(np.float32)
111
+ alpha = arr[..., 3].astype(np.float32)
112
+ key_rgb = np.array(key, dtype=np.float32)
113
+ dist = np.sqrt(np.sum((rgb - key_rgb) ** 2, axis=2))
114
+
115
+ candidate = (alpha > 0) & (alpha <= max_alpha) & (dist < soft_dist)
116
+ hard = candidate & (dist <= hard_dist)
117
+ feather = candidate & (dist > hard_dist)
118
+
119
+ matte = smoothstep(hard_dist, soft_dist, dist)
120
+ new_alpha = alpha.copy()
121
+ new_alpha[hard] = 0.0
122
+ new_alpha[feather] = np.minimum(new_alpha[feather], alpha[feather] * matte[feather])
123
+
124
+ old_a = np.clip(alpha / 255.0, 1.0 / 255.0, 1.0)
125
+ unmixed = (rgb - key_rgb * (1.0 - old_a[..., None])) / old_a[..., None]
126
+ spill = candidate & ~hard
127
+ rgb[spill] = np.clip(unmixed[spill], 0, 255)
128
+
129
+ chroma_mask = dist < soft_dist
130
+ rgb_uint8 = np.clip(rgb, 0, 255).astype(np.uint8)
131
+ alpha_uint8 = np.clip(np.rint(new_alpha), 0, 255).astype(np.uint8)
132
+ near_foreground = expand_mask(alpha_uint8 > 8, min(3, bleed_iterations))
133
+ hidden_chroma = (alpha <= 8) & chroma_mask & near_foreground
134
+ rgb_uint8 = bleed_rgb(rgb_uint8, alpha_uint8, chroma_mask, hidden_chroma, bleed_iterations)
135
+
136
+ arr[..., :3] = rgb_uint8
137
+ arr[..., 3] = alpha_uint8
138
+
139
+ edge_pixels = 0
140
+ if edge_despill:
141
+ arr, edge_pixels = despill_opaque_edge(arr, edge_radius, edge_strength, bleed_iterations)
142
+
143
+ changed = np.any(arr != rgba, axis=2)
144
+ stats = {
145
+ "changed": int(np.count_nonzero(changed)),
146
+ "keyed": int(np.count_nonzero(hard)),
147
+ "feathered": int(np.count_nonzero(feather)),
148
+ "edge": edge_pixels,
149
+ }
150
+ return arr, stats
151
+
152
+
153
+ def despill_opaque_edge(
154
+ rgba: np.ndarray,
155
+ radius: int,
156
+ strength: float,
157
+ bleed_iterations: int,
158
+ ) -> tuple[np.ndarray, int]:
159
+ arr = rgba.copy()
160
+ rgb = arr[..., :3].astype(np.uint8)
161
+ alpha = arr[..., 3]
162
+ opaque = alpha > 8
163
+ edge = expand_mask(~opaque, radius) & opaque
164
+
165
+ r = rgb[..., 0].astype(np.int16)
166
+ g = rgb[..., 1].astype(np.int16)
167
+ b = rgb[..., 2].astype(np.int16)
168
+ magenta_score = (np.minimum(r, b) - g) + (b - g)
169
+ edge_spill = (
170
+ edge
171
+ & (r > 130)
172
+ & (b > 115)
173
+ & ((np.minimum(r, b) - g) > 22)
174
+ & ((b - g) > 18)
175
+ )
176
+ if not np.any(edge_spill):
177
+ return arr, 0
178
+
179
+ repaired_rgb = bleed_rgb(rgb, alpha, edge_spill, edge_spill, bleed_iterations)
180
+ matte = smoothstep(55.0, 275.0, magenta_score.astype(np.float32))
181
+ new_alpha = alpha.astype(np.float32)
182
+ new_alpha[edge_spill] *= 1.0 - np.clip(strength, 0.0, 1.0) * matte[edge_spill]
183
+
184
+ arr[..., :3] = repaired_rgb
185
+ arr[..., 3] = np.clip(np.rint(new_alpha), 0, 255).astype(np.uint8)
186
+ return arr, int(np.count_nonzero(edge_spill))
187
+
188
+
189
+ def png_paths(paths: list[str]) -> list[Path]:
190
+ files: list[Path] = []
191
+ for item in paths:
192
+ path = Path(item)
193
+ if path.is_file() and path.suffix.lower() == ".png":
194
+ files.append(path)
195
+ elif path.is_dir():
196
+ files.extend(sorted(path.rglob("*.png")))
197
+ return sorted(set(files))
198
+
199
+
200
+ def parse_key(value: str) -> tuple[int, int, int]:
201
+ raw = value.strip().lstrip("#")
202
+ if len(raw) != 6:
203
+ raise argparse.ArgumentTypeError("key must be a 6-digit hex color")
204
+ return tuple(int(raw[i : i + 2], 16) for i in (0, 2, 4))
205
+
206
+
207
+ def main() -> int:
208
+ parser = argparse.ArgumentParser()
209
+ parser.add_argument("paths", nargs="*", default=list(DEFAULT_PATHS))
210
+ parser.add_argument("--key", type=parse_key, default=parse_key("#ff00ff"))
211
+ parser.add_argument("--hard-dist", type=float, default=42.0)
212
+ parser.add_argument("--soft-dist", type=float, default=118.0)
213
+ parser.add_argument("--max-alpha", type=int, default=180)
214
+ parser.add_argument("--bleed-iterations", type=int, default=10)
215
+ parser.add_argument("--edge-despill", action="store_true")
216
+ parser.add_argument("--edge-radius", type=int, default=3)
217
+ parser.add_argument("--edge-strength", type=float, default=0.75)
218
+ parser.add_argument("--apply", action="store_true")
219
+ args = parser.parse_args()
220
+
221
+ files = png_paths(args.paths)
222
+ totals = {"files": 0, "changed": 0, "keyed": 0, "feathered": 0}
223
+
224
+ for path in files:
225
+ original = np.array(Image.open(path).convert("RGBA"))
226
+ cleaned, stats = clean_array(
227
+ original,
228
+ args.key,
229
+ args.hard_dist,
230
+ args.soft_dist,
231
+ args.max_alpha,
232
+ args.bleed_iterations,
233
+ args.edge_despill,
234
+ args.edge_radius,
235
+ args.edge_strength,
236
+ )
237
+ if stats["changed"] == 0:
238
+ continue
239
+
240
+ totals["files"] += 1
241
+ totals["changed"] += stats["changed"]
242
+ totals["keyed"] += stats["keyed"]
243
+ totals["feathered"] += stats["feathered"]
244
+ totals["edge"] = totals.get("edge", 0) + stats["edge"]
245
+ action = "cleaned" if args.apply else "would clean"
246
+ print(
247
+ f"{action} {path}: changed={stats['changed']} "
248
+ f"keyed={stats['keyed']} feathered={stats['feathered']} edge={stats['edge']}"
249
+ )
250
+ if args.apply:
251
+ Image.fromarray(cleaned, "RGBA").save(path)
252
+
253
+ mode = "APPLIED" if args.apply else "DRY_RUN"
254
+ print(
255
+ f"{mode} files={totals['files']} changed={totals['changed']} "
256
+ f"keyed={totals['keyed']} feathered={totals['feathered']} edge={totals.get('edge', 0)}"
257
+ )
258
+ return 0
259
+
260
+
261
+ if __name__ == "__main__":
262
+ raise SystemExit(main())
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env python3
2
+ """Copy approved extracted GUI icons as-is and write a manifest."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import shutil
9
+ from collections import defaultdict
10
+ from pathlib import Path
11
+
12
+
13
+ def unique_name(name: str, seen: defaultdict[str, int]) -> str:
14
+ seen[name] += 1
15
+ if seen[name] == 1:
16
+ return name
17
+ return f"{name}_{seen[name]}"
18
+
19
+
20
+ def main() -> None:
21
+ parser = argparse.ArgumentParser()
22
+ parser.add_argument("--source-dir", required=True)
23
+ parser.add_argument("--run-dir", required=True)
24
+ parser.add_argument("--mapping", required=True, help="JSON list of {source,name} entries")
25
+ parser.add_argument("--technique", default="copy-approved-icons-as-is")
26
+ args = parser.parse_args()
27
+
28
+ source_dir = Path(args.source_dir)
29
+ run_dir = Path(args.run_dir)
30
+ mapping = json.loads(Path(args.mapping).read_text(encoding="utf-8"))
31
+ icons_dir = run_dir / "final" / "icons"
32
+ icons_dir.mkdir(parents=True, exist_ok=True)
33
+
34
+ seen: defaultdict[str, int] = defaultdict(int)
35
+ records = []
36
+ for item in mapping:
37
+ source_name = item["source"]
38
+ semantic_name = unique_name(item["name"], seen)
39
+ source_path = source_dir / source_name
40
+ if not source_path.exists():
41
+ raise SystemExit(f"missing source icon: {source_path}")
42
+ destination = icons_dir / f"{semantic_name}{source_path.suffix.lower()}"
43
+ shutil.copy2(source_path, destination)
44
+ records.append(
45
+ {
46
+ "name": semantic_name,
47
+ "file": str(destination.relative_to(run_dir / "final")),
48
+ "source": str(source_path),
49
+ }
50
+ )
51
+
52
+ manifest = {
53
+ "runId": run_dir.name,
54
+ "sourceDir": str(source_dir),
55
+ "technique": args.technique,
56
+ "icons": records,
57
+ }
58
+ manifest_path = run_dir / "final" / "icons_manifest.json"
59
+ manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
60
+ print(manifest_path)
61
+
62
+
63
+ if __name__ == "__main__":
64
+ main()
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env python3
2
+ """Prepare a GUI asset generation run folder and catalog."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ from datetime import datetime, timezone
9
+ from pathlib import Path
10
+
11
+
12
+ def parse_pack(value: str) -> dict:
13
+ parts = value.split(":", 2)
14
+ if len(parts) != 3:
15
+ raise argparse.ArgumentTypeError("pack must be id:grid:icon1,icon2")
16
+ pack_id, grid, icons_csv = parts
17
+ icons = [item.strip() for item in icons_csv.split(",") if item.strip()]
18
+ if not pack_id or "x" not in grid or not icons:
19
+ raise argparse.ArgumentTypeError("pack must include id, grid like 4x3, and icons")
20
+ return {"id": pack_id.strip(), "grid": grid.strip(), "icons": icons}
21
+
22
+
23
+ def prompt_for_pack(pack: dict, style_notes: str, chroma_key: str, phase: str) -> str:
24
+ phase_line = (
25
+ "Mode: PREVIEW. Prioritize style and semantic fidelity for approval before bulk generation."
26
+ if phase == "preview"
27
+ else "Mode: BULK. Keep style locked to approved preview and maximize consistency across many packs."
28
+ )
29
+ return "\n".join(
30
+ [
31
+ f"Create one GUI icon atlas on a flat {chroma_key} chroma-key background.",
32
+ phase_line,
33
+ f"Style: {style_notes}.",
34
+ f"Grid: {pack['grid']}, equal cells, centered icons, 10% safe margin.",
35
+ "Icons in row order: " + ", ".join(pack["icons"]) + ".",
36
+ (
37
+ "Quality rules: no text, no labels, no shadows, no glow, no guides, "
38
+ "no duplicate generic placeholders, no clipping, no overlap, no scenery."
39
+ ),
40
+ "Output only the atlas image.",
41
+ ]
42
+ )
43
+
44
+
45
+ def main() -> None:
46
+ parser = argparse.ArgumentParser()
47
+ parser.add_argument("--run-id", required=True)
48
+ parser.add_argument("--output-root", default="assets/ui/generated")
49
+ parser.add_argument("--style-notes", required=True)
50
+ parser.add_argument("--chroma-key", default="#FF00FF")
51
+ parser.add_argument("--phase", choices=["preview", "bulk"], default="preview")
52
+ parser.add_argument("--pack", action="append", type=parse_pack, required=True)
53
+ args = parser.parse_args()
54
+
55
+ run_dir = Path(args.output_root) / args.run_id
56
+ raw_dir = run_dir / "raw"
57
+ prompts_dir = run_dir / "prompts"
58
+ qa_dir = run_dir / "qa"
59
+ for directory in (raw_dir, prompts_dir, qa_dir):
60
+ directory.mkdir(parents=True, exist_ok=True)
61
+
62
+ packs = []
63
+ for pack in args.pack:
64
+ prompt_path = prompts_dir / f"{pack['id']}.txt"
65
+ prompt_path.write_text(prompt_for_pack(pack, args.style_notes, args.chroma_key, args.phase), encoding="utf-8")
66
+ packs.append(
67
+ {
68
+ "id": pack["id"],
69
+ "grid": pack["grid"],
70
+ "icons": pack["icons"],
71
+ "promptFile": str(prompt_path.relative_to(run_dir)),
72
+ "rawFile": str((raw_dir / f"{pack['id']}.png").relative_to(run_dir)),
73
+ "source": None,
74
+ "qa": {"state": "pending", "notes": ""},
75
+ }
76
+ )
77
+
78
+ catalog = {
79
+ "runId": args.run_id,
80
+ "createdAt": datetime.now(timezone.utc).isoformat(),
81
+ "phase": args.phase,
82
+ "styleNotes": args.style_notes,
83
+ "chromaKey": args.chroma_key,
84
+ "packs": packs,
85
+ }
86
+ (run_dir / "catalog.json").write_text(json.dumps(catalog, indent=2) + "\n", encoding="utf-8")
87
+ print(run_dir)
88
+
89
+
90
+ if __name__ == "__main__":
91
+ main()
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env python3
2
+ """Suggest GUI atlas canvas options ordered from large to small."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+
8
+
9
+ CANVAS_ORDER = [
10
+ ("8x6", 48),
11
+ ("8x5", 40),
12
+ ("6x6", 36),
13
+ ("6x5", 30),
14
+ ("7x4", 28),
15
+ ("5x5", 25),
16
+ ("6x4", 24),
17
+ ("4x4", 16),
18
+ ("4x3", 12),
19
+ ("3x3", 9),
20
+ ]
21
+ QUALITY_ORDER = [("6x5", 30), ("5x5", 25), ("6x4", 24), ("4x4", 16), ("4x3", 12), ("3x3", 9), ("8x6", 48)]
22
+
23
+
24
+ def quality_note(grid: str) -> str:
25
+ if grid == "8x6":
26
+ return "maximum throughput, up to 48 icons in one image; use after preview approval"
27
+ if grid in {"8x5", "6x6"}:
28
+ return "high throughput with slightly more breathing room than 8x6"
29
+ if grid in {"6x5", "7x4", "5x5", "6x4"}:
30
+ return "balanced bulk mode for cleaner semantics"
31
+ if grid == "4x4":
32
+ return "quality-priority compact bulk mode"
33
+ return "precision fallback for difficult icon semantics"
34
+
35
+
36
+ def main() -> None:
37
+ parser = argparse.ArgumentParser()
38
+ parser.add_argument("--icon-count", type=int, required=True)
39
+ parser.add_argument(
40
+ "--strategy",
41
+ choices=["throughput", "quality"],
42
+ default="throughput",
43
+ help="throughput: recommend max icon count per image; quality: recommend safer dense canvas",
44
+ )
45
+ args = parser.parse_args()
46
+
47
+ icon_count = max(1, args.icon_count)
48
+ print(f"icon_count={icon_count}")
49
+ print("canvas_options_large_to_small:")
50
+ for grid, capacity in CANVAS_ORDER:
51
+ fits = "fits" if capacity >= icon_count else "does_not_fit"
52
+ print(f"- {grid} capacity={capacity} {fits} note=\"{quality_note(grid)}\"")
53
+
54
+ if args.strategy == "throughput":
55
+ recommended = "8x6"
56
+ else:
57
+ recommended = next((grid for grid, capacity in QUALITY_ORDER if capacity >= icon_count), "5x5")
58
+ print(f"strategy={args.strategy}")
59
+ print(f"recommended_grid={recommended}")
60
+
61
+
62
+ if __name__ == "__main__":
63
+ main()