@playcraft/cli 0.0.43 → 0.0.46

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 (996) hide show
  1. package/dist/atom-tree/build-skeleton.js +180 -0
  2. package/dist/atom-tree/fusion-hints.js +86 -0
  3. package/dist/atom-tree/selection.js +402 -0
  4. package/dist/commands/build-all.js +33 -5
  5. package/dist/commands/build.js +13 -7
  6. package/dist/commands/create.js +774 -50
  7. package/dist/commands/image.js +11 -0
  8. package/dist/commands/prad.js +3 -3
  9. package/dist/commands/remix.js +20 -0
  10. package/dist/commands/skills.js +961 -185
  11. package/dist/commands/tools-detect.js +56 -0
  12. package/dist/commands/tools-generation.js +21 -0
  13. package/dist/commands/tools-video-review.js +100 -0
  14. package/dist/commands/tools.js +4 -0
  15. package/dist/index.js +2 -0
  16. package/dist/prad/check.test.js +23 -1
  17. package/dist/prad/skills-index.js +1 -1
  18. package/dist/remix/init-template.js +80 -0
  19. package/dist/scaffold/compose.js +211 -0
  20. package/engine-packs/phaser/game/adapters/phaser.ts +52 -0
  21. package/engine-packs/phaser/game/config/game-config.ts +46 -0
  22. package/engine-packs/phaser/game/contract/publish.ts +141 -0
  23. package/engine-packs/phaser/game/contract/types.ts +34 -0
  24. package/engine-packs/phaser/game/index.ts +81 -0
  25. package/engine-packs/phaser/game/runtime-contract.ts +28 -0
  26. package/engine-packs/phaser/manifest.json +1 -0
  27. package/engine-packs/phaser/package.deps.json +1 -0
  28. package/engine-packs/threejs/game/config/game-config.ts +46 -0
  29. package/engine-packs/threejs/game/contract/publish.ts +141 -0
  30. package/engine-packs/threejs/game/contract/types.ts +34 -0
  31. package/engine-packs/threejs/game/index.ts +76 -0
  32. package/engine-packs/threejs/game/runtime-contract.ts +15 -0
  33. package/engine-packs/threejs/manifest.json +1 -0
  34. package/engine-packs/threejs/package.deps.json +8 -0
  35. package/package.json +18 -4
  36. package/project-template/.claude/agents/artist.md +101 -0
  37. package/project-template/.claude/agents/developer.md +90 -93
  38. package/project-template/.claude/agents/game-designer.md +192 -0
  39. package/project-template/.claude/agents/refs/README.md +57 -51
  40. package/project-template/.claude/agents/refs/{designer-color-audio-recipes.md → artist-color-audio-recipes.md} +2 -2
  41. package/project-template/.claude/agents/refs/{designer-deliverable-spec.md → artist-deliverable-spec.md} +57 -57
  42. package/project-template/.claude/agents/refs/{designer-dimension-axis.md → artist-dimension-axis.md} +5 -5
  43. package/project-template/.claude/agents/refs/{designer-handoff-v2-checklist.md → artist-handoff-v2-checklist.md} +15 -15
  44. package/project-template/.claude/agents/refs/artist-legacy-fallback.md +14 -0
  45. package/project-template/.claude/agents/refs/{designer-master-composite-recipes.md → artist-master-composite-recipes.md} +3 -3
  46. package/project-template/.claude/agents/refs/artist-style-exploration-flow.md +55 -0
  47. package/project-template/.claude/agents/refs/developer-dev-handoff.md +83 -40
  48. package/project-template/.claude/agents/refs/developer-impl-cookbook.md +365 -67
  49. package/project-template/.claude/agents/refs/developer-legacy-fallback.md +15 -0
  50. package/project-template/.claude/agents/refs/developer-phase1-flow.md +131 -85
  51. package/project-template/.claude/agents/refs/framework-5-component-filter.md +252 -0
  52. package/project-template/.claude/agents/refs/framework-game-feel-juice.md +266 -0
  53. package/project-template/.claude/agents/refs/framework-mda.md +147 -0
  54. package/project-template/.claude/agents/refs/game-designer-gameplay-sufficiency.md +123 -0
  55. package/project-template/.claude/agents/refs/game-designer-legacy-fallback.md +15 -0
  56. package/project-template/.claude/agents/refs/game-designer-workflow-detail.md +69 -0
  57. package/project-template/.claude/agents/refs/implementation-plan-guide.md +316 -0
  58. package/project-template/.claude/agents/refs/reviewer-convergence-eval.md +247 -33
  59. package/project-template/.claude/agents/refs/reviewer-legacy-fallback.md +14 -0
  60. package/project-template/.claude/agents/refs/skill-selector-guide.md +465 -0
  61. package/project-template/.claude/agents/refs/skill-value-layers.md +145 -0
  62. package/project-template/.claude/agents/refs/storyboard-generation-guide.md +110 -0
  63. package/project-template/.claude/agents/refs/ta-atlas-deliverable-standard.md +4 -4
  64. package/project-template/.claude/agents/refs/ta-pipeline-cookbook.md +101 -104
  65. package/project-template/.claude/agents/refs/technical-artist-legacy-fallback.md +14 -0
  66. package/project-template/.claude/agents/reviewer.md +81 -95
  67. package/project-template/.claude/agents/technical-artist.md +71 -95
  68. package/project-template/.claude/hooks/README.md +83 -35
  69. package/project-template/.claude/hooks/asr-coverage-validate.mjs +19 -19
  70. package/project-template/.claude/hooks/block-developer-preflight.mjs +185 -0
  71. package/project-template/.claude/hooks/block-ring1-tasklist.mjs +95 -0
  72. package/project-template/.claude/hooks/gate-dispatcher.mjs +78 -0
  73. package/project-template/.claude/hooks/gate-runner.mjs +557 -0
  74. package/project-template/.claude/hooks/gate.mjs +170 -0
  75. package/project-template/.claude/hooks/snapshot-milestone.mjs +246 -0
  76. package/project-template/.claude/hooks/user-prompt.mjs +193 -0
  77. package/project-template/.claude/hooks/validate-atom-tree-selection.mjs +281 -0
  78. package/project-template/.claude/hooks/validate-workflow-stop.mjs +1535 -95
  79. package/project-template/.claude/settings.json +54 -9
  80. package/project-template/.claude/skills/brainstorming/SKILL.md +161 -0
  81. package/project-template/.claude/skills/brainstorming/scripts/frame-template.html +270 -0
  82. package/project-template/.claude/skills/brainstorming/scripts/helper.js +177 -0
  83. package/project-template/.claude/skills/brainstorming/scripts/server.cjs +354 -0
  84. package/project-template/.claude/skills/brainstorming/scripts/start-server.sh +148 -0
  85. package/project-template/.claude/skills/brainstorming/scripts/stop-server.sh +56 -0
  86. package/project-template/.claude/skills/brainstorming/scripts/wait-for-selection.sh +62 -0
  87. package/project-template/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  88. package/project-template/.claude/skills/brainstorming/visual-companion.md +309 -0
  89. package/project-template/.claude/skills/playcraft-ad-psychology/SKILL.md +3 -3
  90. package/project-template/.claude/skills/playcraft-art-style-guide/SKILL.md +3 -3
  91. package/project-template/.claude/skills/playcraft-asset-state-sheet/SKILL.md +35 -25
  92. package/project-template/.claude/skills/playcraft-audio-generation/SKILL.md +15 -15
  93. package/project-template/.claude/skills/playcraft-build-optimizer/SKILL.md +5 -5
  94. package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +5 -5
  95. package/project-template/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +5 -5
  96. package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +116 -27
  97. package/project-template/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +1 -1
  98. package/project-template/.claude/skills/playcraft-image-processing/SKILL.md +219 -0
  99. package/project-template/.claude/skills/playcraft-playable-optimization/SKILL.md +161 -0
  100. package/project-template/.claude/skills/playcraft-research/SKILL.md +24 -21
  101. package/project-template/.claude/skills/playcraft-skill-recommender/SKILL.md +331 -0
  102. package/project-template/.claude/skills/playcraft-sprite-generation/SKILL.md +48 -48
  103. package/project-template/.claude/skills/playcraft-sprite-remix/SKILL.md +158 -0
  104. package/project-template/.claude/skills/playcraft-sprite-sheet/SKILL.md +100 -0
  105. package/project-template/.claude/skills/playcraft-storyboard/SKILL.md +4 -4
  106. package/project-template/.claude/skills/playcraft-style-qa/SKILL.md +45 -45
  107. package/project-template/.claude/skills/playcraft-text-rendering/SKILL.md +27 -27
  108. package/project-template/.claude/skills/session-analyzer/SKILL.md +386 -0
  109. package/project-template/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +182 -0
  110. package/project-template/.claude/skills/session-analyzer/scripts/find-turns.mjs +72 -0
  111. package/project-template/.claude/skills/session-analyzer/scripts/heavy-output.mjs +121 -0
  112. package/project-template/.claude/skills/session-analyzer/scripts/resolve-session.mjs +103 -0
  113. package/project-template/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +127 -0
  114. package/project-template/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +106 -0
  115. package/project-template/.claude/skills/session-analyzer/scripts/time-gaps.mjs +128 -0
  116. package/project-template/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +67 -0
  117. package/project-template/.claude/snapshot.mjs +350 -0
  118. package/project-template/.codebuddy/settings.json +53 -0
  119. package/project-template/.cursor/hooks.json +1 -1
  120. package/project-template/.cursor/rules/playcraft-orchestrator.mdc +91 -86
  121. package/project-template/.cursor/rules/playcraft-subagent-boundary.mdc +16 -5
  122. package/project-template/CHANGELOG.md +105 -0
  123. package/project-template/CLAUDE.md +58 -254
  124. package/project-template/docs/DESIGN.md +65 -0
  125. package/project-template/docs/intent-clarifications.md +58 -0
  126. package/project-template/docs/pipeline-overview.md +69 -0
  127. package/project-template/docs/project-state.md +211 -0
  128. package/project-template/docs/team/agent-conduct.md +51 -105
  129. package/project-template/docs/team/agent-runtime-matrix.md +60 -74
  130. package/project-template/docs/team/atom-tree-format.md +169 -0
  131. package/project-template/docs/team/collaboration.md +319 -225
  132. package/project-template/docs/team/core-model.md +80 -46
  133. package/project-template/docs/team/game-designer-migration-todo.md +8 -0
  134. package/project-template/docs/team/harness-legacy-deprecation.md +23 -0
  135. package/project-template/docs/team/platform-capabilities.md +16 -13
  136. package/project-template/docs/team/workflow-changelog.md +95 -23
  137. package/project-template/docs/team/workflow-consistency-checklist.md +93 -45
  138. package/project-template/focus-lab/.gitkeep +0 -0
  139. package/project-template/game/.playcraft-scaffold-pending +0 -0
  140. package/project-template/game/index.ts +18 -0
  141. package/project-template/globals.d.ts +51 -0
  142. package/project-template/index.css +34 -0
  143. package/project-template/index.html +18 -0
  144. package/project-template/main.ts +9 -0
  145. package/project-template/package.json +79 -0
  146. package/project-template/skills/README.md +52 -0
  147. package/project-template/skills/_shared/agent-prep.md +136 -0
  148. package/project-template/skills/_shared/requirements.txt +1 -0
  149. package/project-template/skills/_shared/scripts/build-evidence-gate.mjs +87 -0
  150. package/project-template/skills/_shared/scripts/context-budget.mjs +215 -0
  151. package/project-template/skills/_shared/scripts/create-playable.mjs +140 -0
  152. package/project-template/skills/_shared/scripts/dev-start.mjs +151 -0
  153. package/project-template/skills/_shared/scripts/disclosure-config.mjs +480 -0
  154. package/project-template/skills/_shared/scripts/ensure-project.mjs +532 -0
  155. package/project-template/skills/_shared/scripts/gen-placeholder-audio.py +345 -0
  156. package/project-template/skills/_shared/scripts/gen-placeholder.py +244 -0
  157. package/project-template/skills/_shared/scripts/harness-smoke.mjs +257 -0
  158. package/project-template/skills/_shared/scripts/lib/validator-artifacts.mjs +129 -0
  159. package/project-template/skills/_shared/scripts/placeholder-pack-sheet.mjs +67 -0
  160. package/project-template/skills/_shared/scripts/placeholder_audio_lib.py +142 -0
  161. package/project-template/skills/_shared/scripts/placeholder_lib.py +993 -0
  162. package/project-template/skills/_shared/scripts/render-project-state.mjs +278 -0
  163. package/project-template/skills/_shared/scripts/ring-config.mjs +252 -0
  164. package/project-template/skills/_shared/scripts/skills-check.mjs +176 -0
  165. package/project-template/skills/_shared/scripts/slice-set.mjs +77 -0
  166. package/project-template/skills/_shared/scripts/state-advance-ring.mjs +63 -0
  167. package/project-template/skills/_shared/scripts/state-get.mjs +215 -0
  168. package/project-template/skills/_shared/scripts/state-handoff.mjs +120 -0
  169. package/project-template/skills/_shared/scripts/state-set.mjs +113 -0
  170. package/project-template/skills/_shared/scripts/state-store.mjs +893 -0
  171. package/project-template/skills/_shared/scripts/sync-template-skills.mjs +112 -0
  172. package/project-template/skills/_shared/scripts/verify-asset-code-sync.mjs +265 -0
  173. package/project-template/skills/_shared/scripts/verify-atom-conformance.mjs +181 -0
  174. package/project-template/skills/_shared/scripts/verify-env.mjs +285 -0
  175. package/project-template/skills/_shared/scripts/verify-placeholders.mjs +161 -0
  176. package/project-template/skills/_shared/scripts/workflow-doctor.mjs +348 -0
  177. package/project-template/skills/playable-animate/SKILL.md +52 -0
  178. package/project-template/skills/playable-animate/agents/openai.yaml +4 -0
  179. package/project-template/skills/playable-animate/references/board-refresh.md +30 -0
  180. package/project-template/skills/playable-animate/references/diagnosis-checklist.md +25 -0
  181. package/project-template/skills/playable-animate/references/entrance-sequencing.md +33 -0
  182. package/project-template/skills/playable-animate/references/interaction-timing.md +23 -0
  183. package/project-template/skills/playable-art/SKILL.md +203 -0
  184. package/project-template/skills/playable-art/agents/openai.yaml +4 -0
  185. package/project-template/skills/playable-art/references/alpha-postprocess.md +46 -0
  186. package/project-template/skills/playable-art/references/cta-art-spec.md +59 -0
  187. package/project-template/skills/playable-art/references/layout-proportions.md +30 -0
  188. package/project-template/skills/playable-art/references/prompt-patterns.md +83 -0
  189. package/project-template/skills/playable-art/references/reference-comparison.md +30 -0
  190. package/project-template/skills/playable-art/references/sheet-import-v2.md +91 -0
  191. package/project-template/skills/playable-art/scripts/build-atlas.py +332 -0
  192. package/project-template/skills/playable-art/scripts/chroma-key.py +50 -0
  193. package/project-template/skills/playable-art/scripts/extract-alpha-split.py +65 -0
  194. package/project-template/skills/playable-art/scripts/extract-dual-bg-alpha.py +118 -0
  195. package/project-template/skills/playable-art/scripts/import-gpt-art-sheets-v2.py +403 -0
  196. package/project-template/skills/playable-art/scripts/import-gpt-art-sheets.py +280 -0
  197. package/project-template/skills/playable-art/scripts/import-gpt-tile-sheet.py +114 -0
  198. package/project-template/skills/playable-art/scripts/import_gpt_art_pipeline.py +321 -0
  199. package/project-template/skills/playable-art/scripts/import_gpt_art_sheets_v2_test.py +126 -0
  200. package/project-template/skills/playable-art/scripts/placeholder_common.py +98 -0
  201. package/project-template/skills/playable-art/scripts/remove-bg.py +91 -0
  202. package/project-template/skills/playable-art/scripts/review-processed-assets.py +468 -0
  203. package/project-template/skills/playable-art/scripts/review_processed_assets_test.py +116 -0
  204. package/project-template/skills/playable-art/scripts/verify-alpha-clean.py +105 -0
  205. package/project-template/skills/playable-debug/SKILL.md +116 -0
  206. package/project-template/skills/playable-debug/agents/openai.yaml +4 -0
  207. package/project-template/skills/playable-debug/references/debug-config.md +40 -0
  208. package/project-template/skills/playable-focus/SKILL.md +148 -0
  209. package/project-template/skills/playable-focus/scripts/bootstrap-playground.mjs +84 -0
  210. package/project-template/skills/playable-focus/scripts/extract-focus-frames.mjs +87 -0
  211. package/project-template/skills/playable-focus/scripts/generate-focus-reports.mjs +121 -0
  212. package/project-template/skills/playable-focus/scripts/playground.mjs +390 -0
  213. package/project-template/skills/playable-focus/templates/focus-lab/frame.html +39 -0
  214. package/project-template/skills/playable-focus/templates/focus-lab/harness/clean-stage.js +114 -0
  215. package/project-template/skills/playable-focus/templates/focus-lab/harness/frame-lab-scene.js +94 -0
  216. package/project-template/skills/playable-focus/templates/focus-lab/harness/particle-lab-scene.js +224 -0
  217. package/project-template/skills/playable-focus/templates/focus-lab/model.html +16 -0
  218. package/project-template/skills/playable-focus/templates/focus-lab/particle.html +38 -0
  219. package/project-template/skills/playable-focus/templates/focus-lab/shatter.html +47 -0
  220. package/project-template/skills/playable-focus/templates/playground/registry.template.json +55 -0
  221. package/project-template/skills/playable-focus/templates/playground/vfx-catalog.template.json +280 -0
  222. package/project-template/skills/playable-fx-decompose/SKILL.md +212 -0
  223. package/project-template/skills/playable-fx-decompose/examples.md +89 -0
  224. package/project-template/skills/playable-fx-decompose/reference.md +201 -0
  225. package/project-template/skills/playable-fx-decompose/requirements.txt +2 -0
  226. package/project-template/skills/playable-fx-decompose/scripts/build-blank-tile-crack.py +192 -0
  227. package/project-template/skills/playable-fx-decompose/scripts/fix-shard-debris-frames.py +60 -0
  228. package/project-template/skills/playable-fx-decompose/scripts/generate-shard-debris.py +276 -0
  229. package/project-template/skills/playable-fx-peak/SKILL.md +237 -0
  230. package/project-template/skills/playable-fx-peak/requirements.txt +2 -0
  231. package/project-template/skills/playable-fx-peak/scripts/fix-frame-edges.py +70 -0
  232. package/project-template/skills/playable-fx-peak/scripts/repack-frame-sequence.py +526 -0
  233. package/project-template/skills/playable-fx-peak/scripts/validate-frame-anchors.py +130 -0
  234. package/project-template/skills/playable-placeholder/SKILL.md +239 -0
  235. package/project-template/skills/playable-record/SKILL.md +166 -0
  236. package/project-template/skills/playable-record/scripts/lib/build-artifact.mjs +92 -0
  237. package/project-template/skills/playable-record/scripts/lib/dev-server.mjs +104 -0
  238. package/project-template/skills/playable-record/scripts/lib/record-audio-bridge.js +141 -0
  239. package/project-template/skills/playable-record/scripts/lib/slice-structure-verify.mjs +199 -0
  240. package/project-template/skills/playable-record/scripts/record-playable.mjs +583 -0
  241. package/project-template/skills/playable-record/scripts/verify-build.mjs +185 -0
  242. package/project-template/skills/playable-record/scripts/verify-contract.mjs +261 -0
  243. package/project-template/skills/playable-record/scripts/verify-firstwin.mjs +398 -0
  244. package/project-template/skills/playable-record/scripts/verify-fusion.mjs +320 -0
  245. package/project-template/skills/playable-record/scripts/verify-lifecycle.mjs +78 -0
  246. package/project-template/skills/playable-record/scripts/verify-slice-artist.mjs +53 -0
  247. package/project-template/skills/playable-record/scripts/verify-slice-ta.mjs +53 -0
  248. package/project-template/skills/playable-record/scripts/verify-slice.mjs +142 -0
  249. package/project-template/skills/playable-record/scripts/verify-visual-ring.mjs +250 -0
  250. package/project-template/skills/playable-record/scripts/verify-visual-ring1.mjs +190 -0
  251. package/project-template/skills/playable-record/scripts/verify-vlm-video.mjs +227 -0
  252. package/project-template/skills/playable-validate/SKILL.md +260 -0
  253. package/project-template/skills/playable-validate/references/checklist-detail.md +36 -0
  254. package/project-template/skills/playable-validate/references/single-html-bundle.md +76 -0
  255. package/project-template/skills/playable-validate/validation-rules.md +200 -0
  256. package/project-template/skills/playable-verify-ui/SKILL.md +295 -0
  257. package/project-template/skills/playable-verify-ui/examples/asset-manifest.example.json +36 -0
  258. package/project-template/skills/playable-verify-ui/examples/mahjong-match.profile.yaml +334 -0
  259. package/project-template/skills/playable-verify-ui/profiles/template.profile.yaml +84 -0
  260. package/project-template/skills/playable-verify-ui/reference.md +165 -0
  261. package/project-template/skills/playable-verify-ui/references/phaser-patterns.md +67 -0
  262. package/project-template/skills/playable-verify-ui/references/scaling-decision-tree.md +51 -0
  263. package/project-template/skills/playable-verify-ui/requirements.txt +7 -0
  264. package/project-template/skills/playable-verify-ui/schema/asset-manifest.schema.json +68 -0
  265. package/project-template/skills/playable-verify-ui/schema/playable-ui-profile.schema.json +171 -0
  266. package/project-template/skills/playable-verify-ui/scripts/asset_manifest.py +231 -0
  267. package/project-template/skills/playable-verify-ui/scripts/lib/__init__.py +1 -0
  268. package/project-template/skills/playable-verify-ui/scripts/lib/config_expr.js +218 -0
  269. package/project-template/skills/playable-verify-ui/scripts/lib/node_utils.js +51 -0
  270. package/project-template/skills/playable-verify-ui/scripts/lib/profile_loader.py +59 -0
  271. package/project-template/skills/playable-verify-ui/scripts/lib/report.py +106 -0
  272. package/project-template/skills/playable-verify-ui/scripts/lib/runtime_contract.js +116 -0
  273. package/project-template/skills/playable-verify-ui/scripts/measure_frame_content.py +77 -0
  274. package/project-template/skills/playable-verify-ui/scripts/run_all.py +265 -0
  275. package/project-template/skills/playable-verify-ui/scripts/verify_asset_sizes.py +77 -0
  276. package/project-template/skills/playable-verify-ui/scripts/verify_assets_present.py +62 -0
  277. package/project-template/skills/playable-verify-ui/scripts/verify_atlas_budget.py +84 -0
  278. package/project-template/skills/playable-verify-ui/scripts/verify_atlas_frames.py +79 -0
  279. package/project-template/skills/playable-verify-ui/scripts/verify_bg_clean.py +365 -0
  280. package/project-template/skills/playable-verify-ui/scripts/verify_doc_asset_sync.py +90 -0
  281. package/project-template/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +59 -0
  282. package/project-template/skills/playable-verify-ui/scripts/verify_game_config_load.js +54 -0
  283. package/project-template/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +64 -0
  284. package/project-template/skills/playable-verify-ui/scripts/verify_layout_regions.js +110 -0
  285. package/project-template/skills/playable-verify-ui/scripts/verify_profile_schema.py +37 -0
  286. package/project-template/skills/playable-verify-ui/scripts/verify_ring1_visual.py +211 -0
  287. package/project-template/skills/playable-verify-ui/scripts/verify_runtime_contract.js +58 -0
  288. package/project-template/skills/playable-verify-ui/scripts/verify_script_load_order.js +73 -0
  289. package/project-template/skills/playable-verify-ui/scripts/verify_slice_plan.py +238 -0
  290. package/project-template/skills/playable-verify-ui/scripts/verify_slice_plan_test.py +125 -0
  291. package/project-template/skills/playable-verify-ui/scripts/verify_text_overlap.py +333 -0
  292. package/project-template/skills/playable-verify-ui/scripts/verify_tile_consistency.py +323 -0
  293. package/project-template/skills/playable-verify-ui/scripts/verify_vision_evidence.py +894 -0
  294. package/project-template/skills/playable-verify-ui/scripts/verify_vision_evidence_test.py +122 -0
  295. package/project-template/skills/playable-verify-ui/scripts/verify_visual.py +422 -0
  296. package/project-template/templates/{designer-log.template.md → artist-log.template.md} +26 -31
  297. package/project-template/templates/atom-tree.template.json +133 -0
  298. package/project-template/templates/design-brief.template.md +59 -162
  299. package/project-template/templates/design-lens-checklist.reference.md +14 -14
  300. package/project-template/templates/design-methodology.md +25 -25
  301. package/project-template/templates/design.template.md +50 -0
  302. package/project-template/templates/developer-log.template.md +187 -63
  303. package/project-template/templates/five-axis-framework.md +89 -44
  304. package/project-template/templates/gen-placeholder-audio.template.mjs +63 -0
  305. package/project-template/templates/gen-placeholders.template.mjs +81 -0
  306. package/project-template/templates/intent-clarifications.template.md +7 -7
  307. package/project-template/templates/layout-spec.template.md +32 -20
  308. package/project-template/templates/plan.template.md +80 -0
  309. package/project-template/templates/project-state.template.md +90 -191
  310. package/project-template/templates/review-report.template.md +208 -24
  311. package/project-template/templates/style-exploration.template.md +8 -8
  312. package/project-template/templates/ta-log.template.md +58 -35
  313. package/project-template/tsconfig.json +20 -0
  314. package/project-template/vite.config.ts +27 -0
  315. package/project-templates/phaser/.claude/agents/artist.md +101 -0
  316. package/project-templates/phaser/.claude/agents/developer.md +123 -0
  317. package/project-templates/phaser/.claude/agents/game-designer.md +192 -0
  318. package/project-templates/phaser/.claude/agents/refs/README.md +77 -0
  319. package/project-templates/phaser/.claude/agents/refs/artist-art-style-catalog.md +533 -0
  320. package/project-templates/phaser/.claude/agents/refs/artist-color-audio-recipes.md +153 -0
  321. package/project-templates/phaser/.claude/agents/refs/artist-deliverable-spec.md +230 -0
  322. package/project-templates/phaser/.claude/agents/refs/artist-dimension-axis.md +27 -0
  323. package/project-templates/phaser/.claude/agents/refs/artist-handoff-v2-checklist.md +76 -0
  324. package/project-templates/phaser/.claude/agents/refs/artist-legacy-fallback.md +14 -0
  325. package/project-templates/phaser/.claude/agents/refs/artist-master-composite-recipes.md +208 -0
  326. package/project-templates/phaser/.claude/agents/refs/artist-style-exploration-flow.md +55 -0
  327. package/project-templates/phaser/.claude/agents/refs/developer-dev-handoff.md +152 -0
  328. package/project-templates/phaser/.claude/agents/refs/developer-impl-cookbook.md +432 -0
  329. package/project-templates/phaser/.claude/agents/refs/developer-legacy-fallback.md +15 -0
  330. package/project-templates/phaser/.claude/agents/refs/developer-phase1-flow.md +404 -0
  331. package/project-templates/phaser/.claude/agents/refs/framework-5-component-filter.md +252 -0
  332. package/project-templates/phaser/.claude/agents/refs/framework-game-feel-juice.md +266 -0
  333. package/project-templates/phaser/.claude/agents/refs/framework-mda.md +147 -0
  334. package/project-templates/phaser/.claude/agents/refs/game-designer-gameplay-sufficiency.md +123 -0
  335. package/project-templates/phaser/.claude/agents/refs/game-designer-legacy-fallback.md +15 -0
  336. package/project-templates/phaser/.claude/agents/refs/game-designer-workflow-detail.md +69 -0
  337. package/project-templates/phaser/.claude/agents/refs/implementation-plan-guide.md +309 -0
  338. package/project-templates/phaser/.claude/agents/refs/reviewer-convergence-eval.md +350 -0
  339. package/project-templates/phaser/.claude/agents/refs/reviewer-legacy-fallback.md +14 -0
  340. package/project-templates/phaser/.claude/agents/refs/reviewer-six-dimension-eval.md +6 -0
  341. package/project-templates/phaser/.claude/agents/refs/skill-selector-guide.md +465 -0
  342. package/project-templates/phaser/.claude/agents/refs/skill-value-layers.md +145 -0
  343. package/project-templates/phaser/.claude/agents/refs/storyboard-generation-guide.md +110 -0
  344. package/project-templates/phaser/.claude/agents/refs/ta-3d-flip-recipe.md +88 -0
  345. package/project-templates/phaser/.claude/agents/refs/ta-atlas-deliverable-standard.md +67 -0
  346. package/project-templates/phaser/.claude/agents/refs/ta-batch-pipeline-recipes.md +120 -0
  347. package/project-templates/phaser/.claude/agents/refs/ta-image-generation-detail.md +300 -0
  348. package/project-templates/phaser/.claude/agents/refs/ta-image-ops-reference.md +495 -0
  349. package/project-templates/phaser/.claude/agents/refs/ta-pipeline-cookbook.md +1141 -0
  350. package/project-templates/phaser/.claude/agents/refs/ta-tools-reference.md +111 -0
  351. package/project-templates/phaser/.claude/agents/refs/ta-vfx-preset-catalog.md +365 -0
  352. package/project-templates/phaser/.claude/agents/refs/technical-artist-legacy-fallback.md +14 -0
  353. package/project-templates/phaser/.claude/agents/reviewer.md +116 -0
  354. package/project-templates/phaser/.claude/agents/technical-artist.md +100 -0
  355. package/project-templates/phaser/.claude/hooks/README.md +120 -0
  356. package/project-templates/phaser/.claude/hooks/asr-coverage-validate.mjs +381 -0
  357. package/project-templates/phaser/.claude/hooks/block-developer-preflight.mjs +185 -0
  358. package/project-templates/phaser/.claude/hooks/block-ring1-tasklist.mjs +95 -0
  359. package/project-templates/phaser/.claude/hooks/gate-dispatcher.mjs +78 -0
  360. package/project-templates/phaser/.claude/hooks/gate-runner.mjs +557 -0
  361. package/project-templates/phaser/.claude/hooks/gate.mjs +171 -0
  362. package/project-templates/phaser/.claude/hooks/snapshot-milestone.mjs +247 -0
  363. package/project-templates/phaser/.claude/hooks/user-prompt.mjs +194 -0
  364. package/project-templates/phaser/.claude/hooks/validate-atom-tree-selection.mjs +281 -0
  365. package/project-templates/phaser/.claude/hooks/validate-workflow-stop.mjs +1889 -0
  366. package/project-templates/phaser/.claude/settings.json +81 -0
  367. package/project-templates/phaser/.claude/skills/brainstorming/SKILL.md +161 -0
  368. package/project-templates/phaser/.claude/skills/brainstorming/scripts/frame-template.html +270 -0
  369. package/project-templates/phaser/.claude/skills/brainstorming/scripts/helper.js +177 -0
  370. package/project-templates/phaser/.claude/skills/brainstorming/scripts/server.cjs +354 -0
  371. package/project-templates/phaser/.claude/skills/brainstorming/scripts/start-server.sh +148 -0
  372. package/project-templates/phaser/.claude/skills/brainstorming/scripts/stop-server.sh +56 -0
  373. package/project-templates/phaser/.claude/skills/brainstorming/scripts/wait-for-selection.sh +62 -0
  374. package/project-templates/phaser/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  375. package/project-templates/phaser/.claude/skills/brainstorming/visual-companion.md +309 -0
  376. package/project-templates/phaser/.claude/skills/playcraft-ad-psychology/SKILL.md +182 -0
  377. package/project-templates/phaser/.claude/skills/playcraft-art-style-guide/SKILL.md +123 -0
  378. package/project-templates/phaser/.claude/skills/playcraft-asset-state-sheet/SKILL.md +205 -0
  379. package/project-templates/phaser/.claude/skills/playcraft-audio-generation/SKILL.md +280 -0
  380. package/project-templates/phaser/.claude/skills/playcraft-batch-pipeline/SKILL.md +184 -0
  381. package/project-templates/phaser/.claude/skills/playcraft-build-optimizer/SKILL.md +306 -0
  382. package/project-templates/phaser/.claude/skills/playcraft-image-generation/SKILL.md +298 -0
  383. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +123 -0
  384. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +254 -0
  385. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +324 -0
  386. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +97 -0
  387. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +118 -0
  388. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +137 -0
  389. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +397 -0
  390. package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +296 -0
  391. package/project-templates/phaser/.claude/skills/playcraft-image-ops/SKILL.md +122 -0
  392. package/project-templates/phaser/.claude/skills/playcraft-image-processing/SKILL.md +219 -0
  393. package/project-templates/phaser/.claude/skills/playcraft-masking/SKILL.md +373 -0
  394. package/project-templates/phaser/.claude/skills/playcraft-playable-optimization/SKILL.md +161 -0
  395. package/project-templates/phaser/.claude/skills/playcraft-research/SKILL.md +215 -0
  396. package/project-templates/phaser/.claude/skills/playcraft-skill-recommender/SKILL.md +331 -0
  397. package/project-templates/phaser/.claude/skills/playcraft-sprite-generation/SKILL.md +423 -0
  398. package/project-templates/phaser/.claude/skills/playcraft-sprite-remix/SKILL.md +158 -0
  399. package/project-templates/phaser/.claude/skills/playcraft-sprite-sheet/SKILL.md +100 -0
  400. package/project-templates/phaser/.claude/skills/playcraft-storyboard/SKILL.md +167 -0
  401. package/project-templates/phaser/.claude/skills/playcraft-style-qa/SKILL.md +270 -0
  402. package/project-templates/phaser/.claude/skills/playcraft-text-rendering/SKILL.md +236 -0
  403. package/project-templates/phaser/.claude/skills/playcraft-vfx-animation/SKILL.md +130 -0
  404. package/project-templates/phaser/.claude/skills/playwright-cli/SKILL.md +390 -0
  405. package/project-templates/phaser/.claude/skills/playwright-cli/references/element-attributes.md +23 -0
  406. package/project-templates/phaser/.claude/skills/playwright-cli/references/playwright-tests.md +39 -0
  407. package/project-templates/phaser/.claude/skills/playwright-cli/references/request-mocking.md +87 -0
  408. package/project-templates/phaser/.claude/skills/playwright-cli/references/running-code.md +240 -0
  409. package/project-templates/phaser/.claude/skills/playwright-cli/references/session-management.md +226 -0
  410. package/project-templates/phaser/.claude/skills/playwright-cli/references/spec-driven-testing.md +308 -0
  411. package/project-templates/phaser/.claude/skills/playwright-cli/references/storage-state.md +275 -0
  412. package/project-templates/phaser/.claude/skills/playwright-cli/references/test-generation.md +134 -0
  413. package/project-templates/phaser/.claude/skills/playwright-cli/references/tracing.md +142 -0
  414. package/project-templates/phaser/.claude/skills/playwright-cli/references/video-recording.md +153 -0
  415. package/project-templates/phaser/.claude/skills/session-analyzer/SKILL.md +386 -0
  416. package/project-templates/phaser/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +182 -0
  417. package/project-templates/phaser/.claude/skills/session-analyzer/scripts/find-turns.mjs +72 -0
  418. package/project-templates/phaser/.claude/skills/session-analyzer/scripts/heavy-output.mjs +121 -0
  419. package/project-templates/phaser/.claude/skills/session-analyzer/scripts/resolve-session.mjs +103 -0
  420. package/project-templates/phaser/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +127 -0
  421. package/project-templates/phaser/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +106 -0
  422. package/project-templates/phaser/.claude/skills/session-analyzer/scripts/time-gaps.mjs +128 -0
  423. package/project-templates/phaser/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +67 -0
  424. package/project-templates/phaser/.claude/snapshot.mjs +257 -0
  425. package/project-templates/phaser/.cursor/hooks.json +17 -0
  426. package/project-templates/phaser/.cursor/rules/playcraft-orchestrator.mdc +142 -0
  427. package/project-templates/phaser/.cursor/rules/playcraft-subagent-boundary.mdc +29 -0
  428. package/project-templates/phaser/.playcraft-engine +1 -0
  429. package/project-templates/phaser/CHANGELOG.md +105 -0
  430. package/project-templates/phaser/CLAUDE.md +84 -0
  431. package/project-templates/phaser/assets/audio/bgm/.gitkeep +0 -0
  432. package/project-templates/phaser/assets/audio/sfx/.gitkeep +0 -0
  433. package/project-templates/phaser/assets/bundles/.gitkeep +0 -0
  434. package/project-templates/phaser/assets/images/bg/.gitkeep +0 -0
  435. package/project-templates/phaser/assets/images/reference/.gitkeep +0 -0
  436. package/project-templates/phaser/assets/images/storyboard/.gitkeep +0 -0
  437. package/project-templates/phaser/assets/images/tiles/.gitkeep +0 -0
  438. package/project-templates/phaser/assets/images/ui/.gitkeep +0 -0
  439. package/project-templates/phaser/assets/images/vfx/.gitkeep +0 -0
  440. package/project-templates/phaser/assets/models/.gitkeep +0 -0
  441. package/project-templates/phaser/docs/DESIGN.md +65 -0
  442. package/project-templates/phaser/docs/intent-clarifications.md +58 -0
  443. package/project-templates/phaser/docs/pipeline-overview.md +69 -0
  444. package/project-templates/phaser/docs/project-state.md +211 -0
  445. package/project-templates/phaser/docs/scaffold-manifest.md +10 -0
  446. package/project-templates/phaser/docs/team/agent-conduct.md +67 -0
  447. package/project-templates/phaser/docs/team/agent-runtime-matrix.md +80 -0
  448. package/project-templates/phaser/docs/team/atom-tree-format.md +114 -0
  449. package/project-templates/phaser/docs/team/collaboration.md +391 -0
  450. package/project-templates/phaser/docs/team/core-model.md +85 -0
  451. package/project-templates/phaser/docs/team/game-designer-migration-todo.md +8 -0
  452. package/project-templates/phaser/docs/team/harness-legacy-deprecation.md +23 -0
  453. package/project-templates/phaser/docs/team/platform-capabilities.md +18 -0
  454. package/project-templates/phaser/docs/team/workflow-changelog.md +137 -0
  455. package/project-templates/phaser/docs/team/workflow-consistency-checklist.md +240 -0
  456. package/project-templates/phaser/focus-lab/.gitkeep +0 -0
  457. package/project-templates/phaser/game/adapters/phaser.ts +52 -0
  458. package/project-templates/phaser/game/config/.gitkeep +0 -0
  459. package/project-templates/phaser/game/config/game-config.ts +46 -0
  460. package/project-templates/phaser/game/contract/publish.ts +141 -0
  461. package/project-templates/phaser/game/contract/types.ts +34 -0
  462. package/project-templates/phaser/game/gameplay/.gitkeep +0 -0
  463. package/project-templates/phaser/game/index.ts +81 -0
  464. package/project-templates/phaser/game/runtime-contract.ts +28 -0
  465. package/project-templates/phaser/game/scenes/.gitkeep +0 -0
  466. package/project-templates/phaser/globals.d.ts +51 -0
  467. package/project-templates/phaser/index.css +34 -0
  468. package/project-templates/phaser/index.html +18 -0
  469. package/project-templates/phaser/main.ts +9 -0
  470. package/project-templates/phaser/package.json +79 -0
  471. package/project-templates/phaser/skills/README.md +52 -0
  472. package/project-templates/phaser/skills/_shared/agent-prep.md +136 -0
  473. package/project-templates/phaser/skills/_shared/requirements.txt +1 -0
  474. package/project-templates/phaser/skills/_shared/scripts/build-evidence-gate.mjs +87 -0
  475. package/project-templates/phaser/skills/_shared/scripts/context-budget.mjs +215 -0
  476. package/project-templates/phaser/skills/_shared/scripts/create-playable.mjs +140 -0
  477. package/project-templates/phaser/skills/_shared/scripts/dev-start.mjs +151 -0
  478. package/project-templates/phaser/skills/_shared/scripts/disclosure-config.mjs +480 -0
  479. package/project-templates/phaser/skills/_shared/scripts/ensure-project.mjs +532 -0
  480. package/project-templates/phaser/skills/_shared/scripts/gen-placeholder-audio.py +345 -0
  481. package/project-templates/phaser/skills/_shared/scripts/gen-placeholder.py +244 -0
  482. package/project-templates/phaser/skills/_shared/scripts/harness-smoke.mjs +257 -0
  483. package/project-templates/phaser/skills/_shared/scripts/lib/validator-artifacts.mjs +129 -0
  484. package/project-templates/phaser/skills/_shared/scripts/placeholder-pack-sheet.mjs +67 -0
  485. package/project-templates/phaser/skills/_shared/scripts/placeholder_audio_lib.py +142 -0
  486. package/project-templates/phaser/skills/_shared/scripts/placeholder_lib.py +993 -0
  487. package/project-templates/phaser/skills/_shared/scripts/render-project-state.mjs +278 -0
  488. package/project-templates/phaser/skills/_shared/scripts/ring-config.mjs +252 -0
  489. package/project-templates/phaser/skills/_shared/scripts/skills-check.mjs +176 -0
  490. package/project-templates/phaser/skills/_shared/scripts/slice-set.mjs +77 -0
  491. package/project-templates/phaser/skills/_shared/scripts/state-advance-ring.mjs +63 -0
  492. package/project-templates/phaser/skills/_shared/scripts/state-get.mjs +215 -0
  493. package/project-templates/phaser/skills/_shared/scripts/state-handoff.mjs +120 -0
  494. package/project-templates/phaser/skills/_shared/scripts/state-set.mjs +113 -0
  495. package/project-templates/phaser/skills/_shared/scripts/state-store.mjs +893 -0
  496. package/project-templates/phaser/skills/_shared/scripts/sync-template-skills.mjs +112 -0
  497. package/project-templates/phaser/skills/_shared/scripts/verify-asset-code-sync.mjs +265 -0
  498. package/project-templates/phaser/skills/_shared/scripts/verify-atom-conformance.mjs +181 -0
  499. package/project-templates/phaser/skills/_shared/scripts/verify-env.mjs +285 -0
  500. package/project-templates/phaser/skills/_shared/scripts/verify-placeholders.mjs +161 -0
  501. package/project-templates/phaser/skills/_shared/scripts/workflow-doctor.mjs +348 -0
  502. package/project-templates/phaser/skills/playable-animate/SKILL.md +52 -0
  503. package/project-templates/phaser/skills/playable-animate/agents/openai.yaml +4 -0
  504. package/project-templates/phaser/skills/playable-animate/references/board-refresh.md +30 -0
  505. package/project-templates/phaser/skills/playable-animate/references/diagnosis-checklist.md +25 -0
  506. package/project-templates/phaser/skills/playable-animate/references/entrance-sequencing.md +33 -0
  507. package/project-templates/phaser/skills/playable-animate/references/interaction-timing.md +23 -0
  508. package/project-templates/phaser/skills/playable-art/SKILL.md +203 -0
  509. package/project-templates/phaser/skills/playable-art/agents/openai.yaml +4 -0
  510. package/project-templates/phaser/skills/playable-art/references/alpha-postprocess.md +46 -0
  511. package/project-templates/phaser/skills/playable-art/references/cta-art-spec.md +59 -0
  512. package/project-templates/phaser/skills/playable-art/references/layout-proportions.md +30 -0
  513. package/project-templates/phaser/skills/playable-art/references/prompt-patterns.md +83 -0
  514. package/project-templates/phaser/skills/playable-art/references/reference-comparison.md +30 -0
  515. package/project-templates/phaser/skills/playable-art/references/sheet-import-v2.md +91 -0
  516. package/project-templates/phaser/skills/playable-art/scripts/build-atlas.py +332 -0
  517. package/project-templates/phaser/skills/playable-art/scripts/chroma-key.py +50 -0
  518. package/project-templates/phaser/skills/playable-art/scripts/extract-alpha-split.py +65 -0
  519. package/project-templates/phaser/skills/playable-art/scripts/extract-dual-bg-alpha.py +118 -0
  520. package/project-templates/phaser/skills/playable-art/scripts/import-gpt-art-sheets-v2.py +403 -0
  521. package/project-templates/phaser/skills/playable-art/scripts/import-gpt-art-sheets.py +280 -0
  522. package/project-templates/phaser/skills/playable-art/scripts/import-gpt-tile-sheet.py +114 -0
  523. package/project-templates/phaser/skills/playable-art/scripts/import_gpt_art_pipeline.py +321 -0
  524. package/project-templates/phaser/skills/playable-art/scripts/import_gpt_art_sheets_v2_test.py +126 -0
  525. package/project-templates/phaser/skills/playable-art/scripts/placeholder_common.py +98 -0
  526. package/project-templates/phaser/skills/playable-art/scripts/remove-bg.py +91 -0
  527. package/project-templates/phaser/skills/playable-art/scripts/review-processed-assets.py +468 -0
  528. package/project-templates/phaser/skills/playable-art/scripts/review_processed_assets_test.py +116 -0
  529. package/project-templates/phaser/skills/playable-art/scripts/verify-alpha-clean.py +105 -0
  530. package/project-templates/phaser/skills/playable-debug/SKILL.md +116 -0
  531. package/project-templates/phaser/skills/playable-debug/agents/openai.yaml +4 -0
  532. package/project-templates/phaser/skills/playable-debug/references/debug-config.md +40 -0
  533. package/project-templates/phaser/skills/playable-focus/SKILL.md +148 -0
  534. package/project-templates/phaser/skills/playable-focus/scripts/bootstrap-playground.mjs +84 -0
  535. package/project-templates/phaser/skills/playable-focus/scripts/extract-focus-frames.mjs +87 -0
  536. package/project-templates/phaser/skills/playable-focus/scripts/generate-focus-reports.mjs +121 -0
  537. package/project-templates/phaser/skills/playable-focus/scripts/playground.mjs +390 -0
  538. package/project-templates/phaser/skills/playable-focus/templates/focus-lab/frame.html +39 -0
  539. package/project-templates/phaser/skills/playable-focus/templates/focus-lab/harness/clean-stage.js +114 -0
  540. package/project-templates/phaser/skills/playable-focus/templates/focus-lab/harness/frame-lab-scene.js +94 -0
  541. package/project-templates/phaser/skills/playable-focus/templates/focus-lab/harness/particle-lab-scene.js +224 -0
  542. package/project-templates/phaser/skills/playable-focus/templates/focus-lab/model.html +16 -0
  543. package/project-templates/phaser/skills/playable-focus/templates/focus-lab/particle.html +38 -0
  544. package/project-templates/phaser/skills/playable-focus/templates/focus-lab/shatter.html +47 -0
  545. package/project-templates/phaser/skills/playable-fx-decompose/SKILL.md +212 -0
  546. package/project-templates/phaser/skills/playable-fx-decompose/examples.md +89 -0
  547. package/project-templates/phaser/skills/playable-fx-decompose/reference.md +201 -0
  548. package/project-templates/phaser/skills/playable-fx-decompose/requirements.txt +2 -0
  549. package/project-templates/phaser/skills/playable-fx-decompose/scripts/build-blank-tile-crack.py +192 -0
  550. package/project-templates/phaser/skills/playable-fx-decompose/scripts/fix-shard-debris-frames.py +60 -0
  551. package/project-templates/phaser/skills/playable-fx-decompose/scripts/generate-shard-debris.py +276 -0
  552. package/project-templates/phaser/skills/playable-fx-peak/SKILL.md +237 -0
  553. package/project-templates/phaser/skills/playable-fx-peak/requirements.txt +2 -0
  554. package/project-templates/phaser/skills/playable-fx-peak/scripts/fix-frame-edges.py +70 -0
  555. package/project-templates/phaser/skills/playable-fx-peak/scripts/repack-frame-sequence.py +526 -0
  556. package/project-templates/phaser/skills/playable-fx-peak/scripts/validate-frame-anchors.py +130 -0
  557. package/project-templates/phaser/skills/playable-placeholder/SKILL.md +239 -0
  558. package/project-templates/phaser/skills/playable-record/SKILL.md +166 -0
  559. package/project-templates/phaser/skills/playable-record/scripts/lib/build-artifact.mjs +92 -0
  560. package/project-templates/phaser/skills/playable-record/scripts/lib/dev-server.mjs +104 -0
  561. package/project-templates/phaser/skills/playable-record/scripts/lib/record-audio-bridge.js +141 -0
  562. package/project-templates/phaser/skills/playable-record/scripts/lib/slice-structure-verify.mjs +199 -0
  563. package/project-templates/phaser/skills/playable-record/scripts/record-playable.mjs +583 -0
  564. package/project-templates/phaser/skills/playable-record/scripts/verify-build.mjs +185 -0
  565. package/project-templates/phaser/skills/playable-record/scripts/verify-contract.mjs +261 -0
  566. package/project-templates/phaser/skills/playable-record/scripts/verify-firstwin.mjs +398 -0
  567. package/project-templates/phaser/skills/playable-record/scripts/verify-fusion.mjs +320 -0
  568. package/project-templates/phaser/skills/playable-record/scripts/verify-lifecycle.mjs +78 -0
  569. package/project-templates/phaser/skills/playable-record/scripts/verify-slice-artist.mjs +53 -0
  570. package/project-templates/phaser/skills/playable-record/scripts/verify-slice-ta.mjs +53 -0
  571. package/project-templates/phaser/skills/playable-record/scripts/verify-slice.mjs +142 -0
  572. package/project-templates/phaser/skills/playable-record/scripts/verify-visual-ring.mjs +250 -0
  573. package/project-templates/phaser/skills/playable-record/scripts/verify-visual-ring1.mjs +190 -0
  574. package/project-templates/phaser/skills/playable-record/scripts/verify-vlm-video.mjs +227 -0
  575. package/project-templates/phaser/skills/playable-validate/SKILL.md +260 -0
  576. package/project-templates/phaser/skills/playable-validate/references/checklist-detail.md +36 -0
  577. package/project-templates/phaser/skills/playable-validate/references/single-html-bundle.md +76 -0
  578. package/project-templates/phaser/skills/playable-validate/validation-rules.md +200 -0
  579. package/project-templates/phaser/skills/playable-verify-ui/SKILL.md +295 -0
  580. package/project-templates/phaser/skills/playable-verify-ui/examples/asset-manifest.example.json +36 -0
  581. package/project-templates/phaser/skills/playable-verify-ui/examples/mahjong-match.profile.yaml +334 -0
  582. package/project-templates/phaser/skills/playable-verify-ui/profiles/template.profile.yaml +84 -0
  583. package/project-templates/phaser/skills/playable-verify-ui/reference.md +165 -0
  584. package/project-templates/phaser/skills/playable-verify-ui/references/phaser-patterns.md +67 -0
  585. package/project-templates/phaser/skills/playable-verify-ui/references/scaling-decision-tree.md +51 -0
  586. package/project-templates/phaser/skills/playable-verify-ui/requirements.txt +7 -0
  587. package/project-templates/phaser/skills/playable-verify-ui/schema/asset-manifest.schema.json +68 -0
  588. package/project-templates/phaser/skills/playable-verify-ui/schema/playable-ui-profile.schema.json +171 -0
  589. package/project-templates/phaser/skills/playable-verify-ui/scripts/asset_manifest.py +231 -0
  590. package/project-templates/phaser/skills/playable-verify-ui/scripts/lib/__init__.py +1 -0
  591. package/project-templates/phaser/skills/playable-verify-ui/scripts/lib/config_expr.js +218 -0
  592. package/project-templates/phaser/skills/playable-verify-ui/scripts/lib/node_utils.js +51 -0
  593. package/project-templates/phaser/skills/playable-verify-ui/scripts/lib/profile_loader.py +59 -0
  594. package/project-templates/phaser/skills/playable-verify-ui/scripts/lib/report.py +106 -0
  595. package/project-templates/phaser/skills/playable-verify-ui/scripts/lib/runtime_contract.js +116 -0
  596. package/project-templates/phaser/skills/playable-verify-ui/scripts/measure_frame_content.py +77 -0
  597. package/project-templates/phaser/skills/playable-verify-ui/scripts/run_all.py +265 -0
  598. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_asset_sizes.py +77 -0
  599. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_assets_present.py +62 -0
  600. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_atlas_budget.py +84 -0
  601. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_atlas_frames.py +79 -0
  602. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_bg_clean.py +365 -0
  603. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_doc_asset_sync.py +90 -0
  604. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +59 -0
  605. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_game_config_load.js +54 -0
  606. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +64 -0
  607. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_layout_regions.js +110 -0
  608. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_profile_schema.py +37 -0
  609. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_ring1_visual.py +211 -0
  610. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_runtime_contract.js +58 -0
  611. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_script_load_order.js +73 -0
  612. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_slice_plan.py +238 -0
  613. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_slice_plan_test.py +125 -0
  614. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_text_overlap.py +333 -0
  615. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_tile_consistency.py +323 -0
  616. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_vision_evidence.py +894 -0
  617. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_vision_evidence_test.py +122 -0
  618. package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_visual.py +422 -0
  619. package/project-templates/phaser/ta-workspace/scripts/.gitkeep +0 -0
  620. package/project-templates/phaser/templates/artist-log.template.md +182 -0
  621. package/project-templates/phaser/templates/atom-tree.template.json +133 -0
  622. package/project-templates/phaser/templates/design-brief.template.md +92 -0
  623. package/project-templates/phaser/templates/design-lens-checklist.reference.md +117 -0
  624. package/project-templates/phaser/templates/design-methodology.md +99 -0
  625. package/project-templates/phaser/templates/design.template.md +50 -0
  626. package/project-templates/phaser/templates/developer-log.template.md +258 -0
  627. package/project-templates/phaser/templates/five-axis-framework.md +194 -0
  628. package/project-templates/phaser/templates/gen-placeholder-audio.template.mjs +63 -0
  629. package/project-templates/phaser/templates/gen-placeholders.template.mjs +81 -0
  630. package/project-templates/phaser/templates/intent-clarifications.template.md +58 -0
  631. package/project-templates/phaser/templates/layout-spec.template.md +198 -0
  632. package/project-templates/phaser/templates/plan.template.md +80 -0
  633. package/project-templates/phaser/templates/project-state.template.md +136 -0
  634. package/project-templates/phaser/templates/review-report.template.md +275 -0
  635. package/project-templates/phaser/templates/style-exploration.template.md +93 -0
  636. package/project-templates/phaser/templates/ta-log.template.md +394 -0
  637. package/project-templates/phaser/tsconfig.json +20 -0
  638. package/project-templates/phaser/vite.config.ts +27 -0
  639. package/project-templates/threejs/.claude/agents/artist.md +101 -0
  640. package/project-templates/threejs/.claude/agents/developer.md +123 -0
  641. package/project-templates/threejs/.claude/agents/game-designer.md +192 -0
  642. package/project-templates/threejs/.claude/agents/refs/README.md +77 -0
  643. package/project-templates/threejs/.claude/agents/refs/artist-art-style-catalog.md +533 -0
  644. package/project-templates/threejs/.claude/agents/refs/artist-color-audio-recipes.md +153 -0
  645. package/project-templates/threejs/.claude/agents/refs/artist-deliverable-spec.md +230 -0
  646. package/project-templates/threejs/.claude/agents/refs/artist-dimension-axis.md +27 -0
  647. package/project-templates/threejs/.claude/agents/refs/artist-handoff-v2-checklist.md +76 -0
  648. package/project-templates/threejs/.claude/agents/refs/artist-legacy-fallback.md +14 -0
  649. package/project-templates/threejs/.claude/agents/refs/artist-master-composite-recipes.md +208 -0
  650. package/project-templates/threejs/.claude/agents/refs/artist-style-exploration-flow.md +55 -0
  651. package/project-templates/threejs/.claude/agents/refs/developer-dev-handoff.md +152 -0
  652. package/project-templates/threejs/.claude/agents/refs/developer-impl-cookbook.md +432 -0
  653. package/project-templates/threejs/.claude/agents/refs/developer-legacy-fallback.md +15 -0
  654. package/project-templates/threejs/.claude/agents/refs/developer-phase1-flow.md +404 -0
  655. package/project-templates/threejs/.claude/agents/refs/framework-5-component-filter.md +252 -0
  656. package/project-templates/threejs/.claude/agents/refs/framework-game-feel-juice.md +266 -0
  657. package/project-templates/threejs/.claude/agents/refs/framework-mda.md +147 -0
  658. package/project-templates/threejs/.claude/agents/refs/game-designer-gameplay-sufficiency.md +123 -0
  659. package/project-templates/threejs/.claude/agents/refs/game-designer-legacy-fallback.md +15 -0
  660. package/project-templates/threejs/.claude/agents/refs/game-designer-workflow-detail.md +69 -0
  661. package/project-templates/threejs/.claude/agents/refs/implementation-plan-guide.md +309 -0
  662. package/project-templates/threejs/.claude/agents/refs/reviewer-convergence-eval.md +350 -0
  663. package/project-templates/threejs/.claude/agents/refs/reviewer-legacy-fallback.md +14 -0
  664. package/project-templates/threejs/.claude/agents/refs/reviewer-six-dimension-eval.md +6 -0
  665. package/project-templates/threejs/.claude/agents/refs/skill-selector-guide.md +465 -0
  666. package/project-templates/threejs/.claude/agents/refs/skill-value-layers.md +145 -0
  667. package/project-templates/threejs/.claude/agents/refs/storyboard-generation-guide.md +110 -0
  668. package/project-templates/threejs/.claude/agents/refs/ta-3d-flip-recipe.md +88 -0
  669. package/project-templates/threejs/.claude/agents/refs/ta-atlas-deliverable-standard.md +67 -0
  670. package/project-templates/threejs/.claude/agents/refs/ta-batch-pipeline-recipes.md +120 -0
  671. package/project-templates/threejs/.claude/agents/refs/ta-image-generation-detail.md +300 -0
  672. package/project-templates/threejs/.claude/agents/refs/ta-image-ops-reference.md +495 -0
  673. package/project-templates/threejs/.claude/agents/refs/ta-pipeline-cookbook.md +1141 -0
  674. package/project-templates/threejs/.claude/agents/refs/ta-tools-reference.md +111 -0
  675. package/project-templates/threejs/.claude/agents/refs/ta-vfx-preset-catalog.md +365 -0
  676. package/project-templates/threejs/.claude/agents/refs/technical-artist-legacy-fallback.md +14 -0
  677. package/project-templates/threejs/.claude/agents/reviewer.md +116 -0
  678. package/project-templates/threejs/.claude/agents/technical-artist.md +100 -0
  679. package/project-templates/threejs/.claude/hooks/README.md +120 -0
  680. package/project-templates/threejs/.claude/hooks/asr-coverage-validate.mjs +381 -0
  681. package/project-templates/threejs/.claude/hooks/block-developer-preflight.mjs +185 -0
  682. package/project-templates/threejs/.claude/hooks/block-ring1-tasklist.mjs +95 -0
  683. package/project-templates/threejs/.claude/hooks/gate-dispatcher.mjs +78 -0
  684. package/project-templates/threejs/.claude/hooks/gate-runner.mjs +557 -0
  685. package/project-templates/threejs/.claude/hooks/gate.mjs +171 -0
  686. package/project-templates/threejs/.claude/hooks/snapshot-milestone.mjs +247 -0
  687. package/project-templates/threejs/.claude/hooks/user-prompt.mjs +194 -0
  688. package/project-templates/threejs/.claude/hooks/validate-atom-tree-selection.mjs +281 -0
  689. package/project-templates/threejs/.claude/hooks/validate-workflow-stop.mjs +1889 -0
  690. package/project-templates/threejs/.claude/settings.json +81 -0
  691. package/project-templates/threejs/.claude/skills/brainstorming/SKILL.md +161 -0
  692. package/project-templates/threejs/.claude/skills/brainstorming/scripts/frame-template.html +270 -0
  693. package/project-templates/threejs/.claude/skills/brainstorming/scripts/helper.js +177 -0
  694. package/project-templates/threejs/.claude/skills/brainstorming/scripts/server.cjs +354 -0
  695. package/project-templates/threejs/.claude/skills/brainstorming/scripts/start-server.sh +148 -0
  696. package/project-templates/threejs/.claude/skills/brainstorming/scripts/stop-server.sh +56 -0
  697. package/project-templates/threejs/.claude/skills/brainstorming/scripts/wait-for-selection.sh +62 -0
  698. package/project-templates/threejs/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  699. package/project-templates/threejs/.claude/skills/brainstorming/visual-companion.md +309 -0
  700. package/project-templates/threejs/.claude/skills/playcraft-ad-psychology/SKILL.md +182 -0
  701. package/project-templates/threejs/.claude/skills/playcraft-art-style-guide/SKILL.md +123 -0
  702. package/project-templates/threejs/.claude/skills/playcraft-asset-state-sheet/SKILL.md +205 -0
  703. package/project-templates/threejs/.claude/skills/playcraft-audio-generation/SKILL.md +280 -0
  704. package/project-templates/threejs/.claude/skills/playcraft-batch-pipeline/SKILL.md +184 -0
  705. package/project-templates/threejs/.claude/skills/playcraft-build-optimizer/SKILL.md +306 -0
  706. package/project-templates/threejs/.claude/skills/playcraft-image-generation/SKILL.md +298 -0
  707. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +123 -0
  708. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +254 -0
  709. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +324 -0
  710. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +97 -0
  711. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +118 -0
  712. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +137 -0
  713. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +397 -0
  714. package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +296 -0
  715. package/project-templates/threejs/.claude/skills/playcraft-image-ops/SKILL.md +122 -0
  716. package/project-templates/threejs/.claude/skills/playcraft-image-processing/SKILL.md +219 -0
  717. package/project-templates/threejs/.claude/skills/playcraft-masking/SKILL.md +373 -0
  718. package/project-templates/threejs/.claude/skills/playcraft-playable-optimization/SKILL.md +161 -0
  719. package/project-templates/threejs/.claude/skills/playcraft-research/SKILL.md +215 -0
  720. package/project-templates/threejs/.claude/skills/playcraft-skill-recommender/SKILL.md +331 -0
  721. package/project-templates/threejs/.claude/skills/playcraft-sprite-generation/SKILL.md +423 -0
  722. package/project-templates/threejs/.claude/skills/playcraft-sprite-remix/SKILL.md +158 -0
  723. package/project-templates/threejs/.claude/skills/playcraft-sprite-sheet/SKILL.md +100 -0
  724. package/project-templates/threejs/.claude/skills/playcraft-storyboard/SKILL.md +167 -0
  725. package/project-templates/threejs/.claude/skills/playcraft-style-qa/SKILL.md +270 -0
  726. package/project-templates/threejs/.claude/skills/playcraft-text-rendering/SKILL.md +236 -0
  727. package/project-templates/threejs/.claude/skills/playcraft-vfx-animation/SKILL.md +130 -0
  728. package/project-templates/threejs/.claude/skills/playwright-cli/SKILL.md +390 -0
  729. package/project-templates/threejs/.claude/skills/playwright-cli/references/element-attributes.md +23 -0
  730. package/project-templates/threejs/.claude/skills/playwright-cli/references/playwright-tests.md +39 -0
  731. package/project-templates/threejs/.claude/skills/playwright-cli/references/request-mocking.md +87 -0
  732. package/project-templates/threejs/.claude/skills/playwright-cli/references/running-code.md +240 -0
  733. package/project-templates/threejs/.claude/skills/playwright-cli/references/session-management.md +226 -0
  734. package/project-templates/threejs/.claude/skills/playwright-cli/references/spec-driven-testing.md +308 -0
  735. package/project-templates/threejs/.claude/skills/playwright-cli/references/storage-state.md +275 -0
  736. package/project-templates/threejs/.claude/skills/playwright-cli/references/test-generation.md +134 -0
  737. package/project-templates/threejs/.claude/skills/playwright-cli/references/tracing.md +142 -0
  738. package/project-templates/threejs/.claude/skills/playwright-cli/references/video-recording.md +153 -0
  739. package/project-templates/threejs/.claude/skills/session-analyzer/SKILL.md +386 -0
  740. package/project-templates/threejs/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +182 -0
  741. package/project-templates/threejs/.claude/skills/session-analyzer/scripts/find-turns.mjs +72 -0
  742. package/project-templates/threejs/.claude/skills/session-analyzer/scripts/heavy-output.mjs +121 -0
  743. package/project-templates/threejs/.claude/skills/session-analyzer/scripts/resolve-session.mjs +103 -0
  744. package/project-templates/threejs/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +127 -0
  745. package/project-templates/threejs/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +106 -0
  746. package/project-templates/threejs/.claude/skills/session-analyzer/scripts/time-gaps.mjs +128 -0
  747. package/project-templates/threejs/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +67 -0
  748. package/project-templates/threejs/.claude/snapshot.mjs +257 -0
  749. package/project-templates/threejs/.cursor/hooks.json +17 -0
  750. package/project-templates/threejs/.cursor/rules/playcraft-orchestrator.mdc +142 -0
  751. package/project-templates/threejs/.cursor/rules/playcraft-subagent-boundary.mdc +29 -0
  752. package/project-templates/threejs/.playcraft-engine +1 -0
  753. package/project-templates/threejs/CHANGELOG.md +105 -0
  754. package/project-templates/threejs/CLAUDE.md +84 -0
  755. package/project-templates/threejs/assets/audio/bgm/.gitkeep +0 -0
  756. package/project-templates/threejs/assets/audio/sfx/.gitkeep +0 -0
  757. package/project-templates/threejs/assets/bundles/.gitkeep +0 -0
  758. package/project-templates/threejs/assets/images/bg/.gitkeep +0 -0
  759. package/project-templates/threejs/assets/images/reference/.gitkeep +0 -0
  760. package/project-templates/threejs/assets/images/storyboard/.gitkeep +0 -0
  761. package/project-templates/threejs/assets/images/tiles/.gitkeep +0 -0
  762. package/project-templates/threejs/assets/images/ui/.gitkeep +0 -0
  763. package/project-templates/threejs/assets/images/vfx/.gitkeep +0 -0
  764. package/project-templates/threejs/assets/models/.gitkeep +0 -0
  765. package/project-templates/threejs/docs/DESIGN.md +65 -0
  766. package/project-templates/threejs/docs/intent-clarifications.md +58 -0
  767. package/project-templates/threejs/docs/pipeline-overview.md +69 -0
  768. package/project-templates/threejs/docs/project-state.md +211 -0
  769. package/project-templates/threejs/docs/scaffold-manifest.md +10 -0
  770. package/project-templates/threejs/docs/team/agent-conduct.md +67 -0
  771. package/project-templates/threejs/docs/team/agent-runtime-matrix.md +80 -0
  772. package/project-templates/threejs/docs/team/atom-tree-format.md +114 -0
  773. package/project-templates/threejs/docs/team/collaboration.md +391 -0
  774. package/project-templates/threejs/docs/team/core-model.md +85 -0
  775. package/project-templates/threejs/docs/team/game-designer-migration-todo.md +8 -0
  776. package/project-templates/threejs/docs/team/harness-legacy-deprecation.md +23 -0
  777. package/project-templates/threejs/docs/team/platform-capabilities.md +18 -0
  778. package/project-templates/threejs/docs/team/workflow-changelog.md +137 -0
  779. package/project-templates/threejs/docs/team/workflow-consistency-checklist.md +240 -0
  780. package/project-templates/threejs/focus-lab/.gitkeep +0 -0
  781. package/project-templates/threejs/game/config/.gitkeep +0 -0
  782. package/project-templates/threejs/game/config/game-config.ts +46 -0
  783. package/project-templates/threejs/game/contract/publish.ts +141 -0
  784. package/project-templates/threejs/game/contract/types.ts +34 -0
  785. package/project-templates/threejs/game/gameplay/.gitkeep +0 -0
  786. package/project-templates/threejs/game/index.ts +76 -0
  787. package/project-templates/threejs/game/runtime-contract.ts +15 -0
  788. package/project-templates/threejs/game/scenes/.gitkeep +0 -0
  789. package/project-templates/threejs/globals.d.ts +51 -0
  790. package/project-templates/threejs/index.css +34 -0
  791. package/project-templates/threejs/index.html +18 -0
  792. package/project-templates/threejs/main.ts +9 -0
  793. package/project-templates/threejs/package.json +79 -0
  794. package/project-templates/threejs/skills/README.md +52 -0
  795. package/project-templates/threejs/skills/_shared/agent-prep.md +136 -0
  796. package/project-templates/threejs/skills/_shared/requirements.txt +1 -0
  797. package/project-templates/threejs/skills/_shared/scripts/build-evidence-gate.mjs +87 -0
  798. package/project-templates/threejs/skills/_shared/scripts/context-budget.mjs +215 -0
  799. package/project-templates/threejs/skills/_shared/scripts/create-playable.mjs +140 -0
  800. package/project-templates/threejs/skills/_shared/scripts/dev-start.mjs +151 -0
  801. package/project-templates/threejs/skills/_shared/scripts/disclosure-config.mjs +480 -0
  802. package/project-templates/threejs/skills/_shared/scripts/ensure-project.mjs +532 -0
  803. package/project-templates/threejs/skills/_shared/scripts/gen-placeholder-audio.py +345 -0
  804. package/project-templates/threejs/skills/_shared/scripts/gen-placeholder.py +244 -0
  805. package/project-templates/threejs/skills/_shared/scripts/harness-smoke.mjs +257 -0
  806. package/project-templates/threejs/skills/_shared/scripts/lib/validator-artifacts.mjs +129 -0
  807. package/project-templates/threejs/skills/_shared/scripts/placeholder-pack-sheet.mjs +67 -0
  808. package/project-templates/threejs/skills/_shared/scripts/placeholder_audio_lib.py +142 -0
  809. package/project-templates/threejs/skills/_shared/scripts/placeholder_lib.py +993 -0
  810. package/project-templates/threejs/skills/_shared/scripts/render-project-state.mjs +278 -0
  811. package/project-templates/threejs/skills/_shared/scripts/ring-config.mjs +252 -0
  812. package/project-templates/threejs/skills/_shared/scripts/skills-check.mjs +176 -0
  813. package/project-templates/threejs/skills/_shared/scripts/slice-set.mjs +77 -0
  814. package/project-templates/threejs/skills/_shared/scripts/state-advance-ring.mjs +63 -0
  815. package/project-templates/threejs/skills/_shared/scripts/state-get.mjs +215 -0
  816. package/project-templates/threejs/skills/_shared/scripts/state-handoff.mjs +120 -0
  817. package/project-templates/threejs/skills/_shared/scripts/state-set.mjs +113 -0
  818. package/project-templates/threejs/skills/_shared/scripts/state-store.mjs +893 -0
  819. package/project-templates/threejs/skills/_shared/scripts/sync-template-skills.mjs +112 -0
  820. package/project-templates/threejs/skills/_shared/scripts/verify-asset-code-sync.mjs +265 -0
  821. package/project-templates/threejs/skills/_shared/scripts/verify-atom-conformance.mjs +181 -0
  822. package/project-templates/threejs/skills/_shared/scripts/verify-env.mjs +285 -0
  823. package/project-templates/threejs/skills/_shared/scripts/verify-placeholders.mjs +161 -0
  824. package/project-templates/threejs/skills/_shared/scripts/workflow-doctor.mjs +348 -0
  825. package/project-templates/threejs/skills/playable-animate/SKILL.md +52 -0
  826. package/project-templates/threejs/skills/playable-animate/agents/openai.yaml +4 -0
  827. package/project-templates/threejs/skills/playable-animate/references/board-refresh.md +30 -0
  828. package/project-templates/threejs/skills/playable-animate/references/diagnosis-checklist.md +25 -0
  829. package/project-templates/threejs/skills/playable-animate/references/entrance-sequencing.md +33 -0
  830. package/project-templates/threejs/skills/playable-animate/references/interaction-timing.md +23 -0
  831. package/project-templates/threejs/skills/playable-art/SKILL.md +203 -0
  832. package/project-templates/threejs/skills/playable-art/agents/openai.yaml +4 -0
  833. package/project-templates/threejs/skills/playable-art/references/alpha-postprocess.md +46 -0
  834. package/project-templates/threejs/skills/playable-art/references/cta-art-spec.md +59 -0
  835. package/project-templates/threejs/skills/playable-art/references/layout-proportions.md +30 -0
  836. package/project-templates/threejs/skills/playable-art/references/prompt-patterns.md +83 -0
  837. package/project-templates/threejs/skills/playable-art/references/reference-comparison.md +30 -0
  838. package/project-templates/threejs/skills/playable-art/references/sheet-import-v2.md +91 -0
  839. package/project-templates/threejs/skills/playable-art/scripts/build-atlas.py +332 -0
  840. package/project-templates/threejs/skills/playable-art/scripts/chroma-key.py +50 -0
  841. package/project-templates/threejs/skills/playable-art/scripts/extract-alpha-split.py +65 -0
  842. package/project-templates/threejs/skills/playable-art/scripts/extract-dual-bg-alpha.py +118 -0
  843. package/project-templates/threejs/skills/playable-art/scripts/import-gpt-art-sheets-v2.py +403 -0
  844. package/project-templates/threejs/skills/playable-art/scripts/import-gpt-art-sheets.py +280 -0
  845. package/project-templates/threejs/skills/playable-art/scripts/import-gpt-tile-sheet.py +114 -0
  846. package/project-templates/threejs/skills/playable-art/scripts/import_gpt_art_pipeline.py +321 -0
  847. package/project-templates/threejs/skills/playable-art/scripts/import_gpt_art_sheets_v2_test.py +126 -0
  848. package/project-templates/threejs/skills/playable-art/scripts/placeholder_common.py +98 -0
  849. package/project-templates/threejs/skills/playable-art/scripts/remove-bg.py +91 -0
  850. package/project-templates/threejs/skills/playable-art/scripts/review-processed-assets.py +468 -0
  851. package/project-templates/threejs/skills/playable-art/scripts/review_processed_assets_test.py +116 -0
  852. package/project-templates/threejs/skills/playable-art/scripts/verify-alpha-clean.py +105 -0
  853. package/project-templates/threejs/skills/playable-debug/SKILL.md +116 -0
  854. package/project-templates/threejs/skills/playable-debug/agents/openai.yaml +4 -0
  855. package/project-templates/threejs/skills/playable-debug/references/debug-config.md +40 -0
  856. package/project-templates/threejs/skills/playable-focus/SKILL.md +148 -0
  857. package/project-templates/threejs/skills/playable-focus/scripts/bootstrap-playground.mjs +84 -0
  858. package/project-templates/threejs/skills/playable-focus/scripts/extract-focus-frames.mjs +87 -0
  859. package/project-templates/threejs/skills/playable-focus/scripts/generate-focus-reports.mjs +121 -0
  860. package/project-templates/threejs/skills/playable-focus/scripts/playground.mjs +390 -0
  861. package/project-templates/threejs/skills/playable-focus/templates/focus-lab/frame.html +39 -0
  862. package/project-templates/threejs/skills/playable-focus/templates/focus-lab/harness/clean-stage.js +114 -0
  863. package/project-templates/threejs/skills/playable-focus/templates/focus-lab/harness/frame-lab-scene.js +94 -0
  864. package/project-templates/threejs/skills/playable-focus/templates/focus-lab/harness/particle-lab-scene.js +224 -0
  865. package/project-templates/threejs/skills/playable-focus/templates/focus-lab/model.html +16 -0
  866. package/project-templates/threejs/skills/playable-focus/templates/focus-lab/particle.html +38 -0
  867. package/project-templates/threejs/skills/playable-focus/templates/focus-lab/shatter.html +47 -0
  868. package/project-templates/threejs/skills/playable-fx-decompose/SKILL.md +212 -0
  869. package/project-templates/threejs/skills/playable-fx-decompose/examples.md +89 -0
  870. package/project-templates/threejs/skills/playable-fx-decompose/reference.md +201 -0
  871. package/project-templates/threejs/skills/playable-fx-decompose/requirements.txt +2 -0
  872. package/project-templates/threejs/skills/playable-fx-decompose/scripts/build-blank-tile-crack.py +192 -0
  873. package/project-templates/threejs/skills/playable-fx-decompose/scripts/fix-shard-debris-frames.py +60 -0
  874. package/project-templates/threejs/skills/playable-fx-decompose/scripts/generate-shard-debris.py +276 -0
  875. package/project-templates/threejs/skills/playable-fx-peak/SKILL.md +237 -0
  876. package/project-templates/threejs/skills/playable-fx-peak/requirements.txt +2 -0
  877. package/project-templates/threejs/skills/playable-fx-peak/scripts/fix-frame-edges.py +70 -0
  878. package/project-templates/threejs/skills/playable-fx-peak/scripts/repack-frame-sequence.py +526 -0
  879. package/project-templates/threejs/skills/playable-fx-peak/scripts/validate-frame-anchors.py +130 -0
  880. package/project-templates/threejs/skills/playable-placeholder/SKILL.md +239 -0
  881. package/project-templates/threejs/skills/playable-record/SKILL.md +166 -0
  882. package/project-templates/threejs/skills/playable-record/scripts/lib/build-artifact.mjs +92 -0
  883. package/project-templates/threejs/skills/playable-record/scripts/lib/dev-server.mjs +104 -0
  884. package/project-templates/threejs/skills/playable-record/scripts/lib/record-audio-bridge.js +141 -0
  885. package/project-templates/threejs/skills/playable-record/scripts/lib/slice-structure-verify.mjs +199 -0
  886. package/project-templates/threejs/skills/playable-record/scripts/record-playable.mjs +583 -0
  887. package/project-templates/threejs/skills/playable-record/scripts/verify-build.mjs +185 -0
  888. package/project-templates/threejs/skills/playable-record/scripts/verify-contract.mjs +261 -0
  889. package/project-templates/threejs/skills/playable-record/scripts/verify-firstwin.mjs +398 -0
  890. package/project-templates/threejs/skills/playable-record/scripts/verify-fusion.mjs +320 -0
  891. package/project-templates/threejs/skills/playable-record/scripts/verify-lifecycle.mjs +78 -0
  892. package/project-templates/threejs/skills/playable-record/scripts/verify-slice-artist.mjs +53 -0
  893. package/project-templates/threejs/skills/playable-record/scripts/verify-slice-ta.mjs +53 -0
  894. package/project-templates/threejs/skills/playable-record/scripts/verify-slice.mjs +142 -0
  895. package/project-templates/threejs/skills/playable-record/scripts/verify-visual-ring.mjs +250 -0
  896. package/project-templates/threejs/skills/playable-record/scripts/verify-visual-ring1.mjs +190 -0
  897. package/project-templates/threejs/skills/playable-record/scripts/verify-vlm-video.mjs +227 -0
  898. package/project-templates/threejs/skills/playable-validate/SKILL.md +260 -0
  899. package/project-templates/threejs/skills/playable-validate/references/checklist-detail.md +36 -0
  900. package/project-templates/threejs/skills/playable-validate/references/single-html-bundle.md +76 -0
  901. package/project-templates/threejs/skills/playable-validate/validation-rules.md +200 -0
  902. package/project-templates/threejs/skills/playable-verify-ui/SKILL.md +295 -0
  903. package/project-templates/threejs/skills/playable-verify-ui/examples/asset-manifest.example.json +36 -0
  904. package/project-templates/threejs/skills/playable-verify-ui/examples/mahjong-match.profile.yaml +334 -0
  905. package/project-templates/threejs/skills/playable-verify-ui/profiles/template.profile.yaml +84 -0
  906. package/project-templates/threejs/skills/playable-verify-ui/reference.md +165 -0
  907. package/project-templates/threejs/skills/playable-verify-ui/references/phaser-patterns.md +67 -0
  908. package/project-templates/threejs/skills/playable-verify-ui/references/scaling-decision-tree.md +51 -0
  909. package/project-templates/threejs/skills/playable-verify-ui/requirements.txt +7 -0
  910. package/project-templates/threejs/skills/playable-verify-ui/schema/asset-manifest.schema.json +68 -0
  911. package/project-templates/threejs/skills/playable-verify-ui/schema/playable-ui-profile.schema.json +171 -0
  912. package/project-templates/threejs/skills/playable-verify-ui/scripts/asset_manifest.py +231 -0
  913. package/project-templates/threejs/skills/playable-verify-ui/scripts/lib/__init__.py +1 -0
  914. package/project-templates/threejs/skills/playable-verify-ui/scripts/lib/config_expr.js +218 -0
  915. package/project-templates/threejs/skills/playable-verify-ui/scripts/lib/node_utils.js +51 -0
  916. package/project-templates/threejs/skills/playable-verify-ui/scripts/lib/profile_loader.py +59 -0
  917. package/project-templates/threejs/skills/playable-verify-ui/scripts/lib/report.py +106 -0
  918. package/project-templates/threejs/skills/playable-verify-ui/scripts/lib/runtime_contract.js +116 -0
  919. package/project-templates/threejs/skills/playable-verify-ui/scripts/measure_frame_content.py +77 -0
  920. package/project-templates/threejs/skills/playable-verify-ui/scripts/run_all.py +265 -0
  921. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_asset_sizes.py +77 -0
  922. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_assets_present.py +62 -0
  923. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_atlas_budget.py +84 -0
  924. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_atlas_frames.py +79 -0
  925. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_bg_clean.py +365 -0
  926. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_doc_asset_sync.py +90 -0
  927. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +59 -0
  928. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_game_config_load.js +54 -0
  929. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +64 -0
  930. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_layout_regions.js +110 -0
  931. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_profile_schema.py +37 -0
  932. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_ring1_visual.py +211 -0
  933. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_runtime_contract.js +58 -0
  934. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_script_load_order.js +73 -0
  935. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_slice_plan.py +238 -0
  936. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_slice_plan_test.py +125 -0
  937. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_text_overlap.py +333 -0
  938. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_tile_consistency.py +323 -0
  939. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_vision_evidence.py +894 -0
  940. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_vision_evidence_test.py +122 -0
  941. package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_visual.py +422 -0
  942. package/project-templates/threejs/ta-workspace/scripts/.gitkeep +0 -0
  943. package/project-templates/threejs/templates/artist-log.template.md +182 -0
  944. package/project-templates/threejs/templates/atom-tree.template.json +133 -0
  945. package/project-templates/threejs/templates/design-brief.template.md +92 -0
  946. package/project-templates/threejs/templates/design-lens-checklist.reference.md +117 -0
  947. package/project-templates/threejs/templates/design-methodology.md +99 -0
  948. package/project-templates/threejs/templates/design.template.md +50 -0
  949. package/project-templates/threejs/templates/developer-log.template.md +258 -0
  950. package/project-templates/threejs/templates/five-axis-framework.md +194 -0
  951. package/project-templates/threejs/templates/gen-placeholder-audio.template.mjs +63 -0
  952. package/project-templates/threejs/templates/gen-placeholders.template.mjs +81 -0
  953. package/project-templates/threejs/templates/intent-clarifications.template.md +58 -0
  954. package/project-templates/threejs/templates/layout-spec.template.md +198 -0
  955. package/project-templates/threejs/templates/plan.template.md +80 -0
  956. package/project-templates/threejs/templates/project-state.template.md +136 -0
  957. package/project-templates/threejs/templates/review-report.template.md +275 -0
  958. package/project-templates/threejs/templates/style-exploration.template.md +93 -0
  959. package/project-templates/threejs/templates/ta-log.template.md +394 -0
  960. package/project-templates/threejs/tsconfig.json +20 -0
  961. package/project-templates/threejs/vite.config.ts +27 -0
  962. package/dist/atom-plan/validate-atom-plan.js +0 -298
  963. package/dist/playable/base-builder.js +0 -265
  964. package/dist/playable/builder.js +0 -1462
  965. package/dist/playable/converter.js +0 -150
  966. package/dist/playable/index.js +0 -3
  967. package/dist/playable/platforms/base.js +0 -12
  968. package/dist/playable/platforms/facebook.js +0 -37
  969. package/dist/playable/platforms/index.js +0 -24
  970. package/dist/playable/platforms/snapchat.js +0 -59
  971. package/dist/playable/playable-builder.js +0 -521
  972. package/dist/playable/types.js +0 -1
  973. package/dist/playable/vite/config-builder.js +0 -136
  974. package/dist/playable/vite/platform-configs.js +0 -102
  975. package/dist/playable/vite/plugin-model-compression.js +0 -63
  976. package/dist/playable/vite/plugin-platform.js +0 -65
  977. package/dist/playable/vite/plugin-playcanvas.js +0 -454
  978. package/dist/playable/vite-builder.js +0 -125
  979. package/project-template/.claude/agents/designer.md +0 -124
  980. package/project-template/.claude/agents/pm.md +0 -166
  981. package/project-template/.claude/agents/refs/designer-style-exploration-flow.md +0 -67
  982. package/project-template/.claude/agents/refs/pm-workflow-detail.md +0 -567
  983. package/project-template/.claude/hooks/validate-atom-plan.mjs +0 -224
  984. package/project-template/.claude/settings.local.json +0 -4
  985. package/project-template/.claude/skills/playcraft-workflow/SKILL.md +0 -485
  986. package/project-template/docs/team/atom-plan-format.md +0 -173
  987. package/project-template/templates/atom-plan.template.json +0 -44
  988. package/project-template/templates/atom-plan.template.md +0 -108
  989. /package/dist/{atom-plan → asr}/validate-asr-coverage.js +0 -0
  990. /package/{project-template → engine-packs/phaser}/game/config/.gitkeep +0 -0
  991. /package/{project-template → engine-packs/phaser}/game/gameplay/.gitkeep +0 -0
  992. /package/{project-template → engine-packs/phaser}/game/scenes/.gitkeep +0 -0
  993. /package/{project-template/logs → engine-packs/threejs/game/config}/.gitkeep +0 -0
  994. /package/{project-template/ta-workspace/logs → engine-packs/threejs/game/gameplay}/.gitkeep +0 -0
  995. /package/{project-template/ta-workspace/tmp → engine-packs/threejs/game/scenes}/.gitkeep +0 -0
  996. /package/project-template/.claude/agents/refs/{designer-art-style-catalog.md → artist-art-style-catalog.md} +0 -0
@@ -0,0 +1,1889 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PlayCraft workflow STOP validator — shared by Claude Code (SubagentStop) and Cursor (subagentStop).
4
+ * Ensures Artist / TA / Developer filled required workflow logs and valid project-state handoff before subagent may stop.
5
+ *
6
+ * Exit 0 = pass
7
+ * Exit 2 = block (Claude: decision block; Cursor: block stop)
8
+ * stdout (Claude): {"decision":"block","reason":"..."} on failure
9
+ */
10
+
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import { createRequire } from 'node:module';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { validateArtistGate2b } from './asr-coverage-validate.mjs';
16
+ import { diagnose } from '../../skills/_shared/scripts/workflow-doctor.mjs';
17
+ import {
18
+ reviewDoDKeysForRing,
19
+ } from '../../skills/_shared/scripts/ring-config.mjs';
20
+ import { normalizeSliceState, readState, stateExists, isSliceResolved } from '../../skills/_shared/scripts/state-store.mjs';
21
+ import { validateBuildArtifactForSlice } from '../../skills/playable-record/scripts/lib/build-artifact.mjs';
22
+
23
+ const __filename = fileURLToPath(import.meta.url);
24
+ const require = createRequire(import.meta.url);
25
+
26
+ const INTAKE_HEADING = '## Upstream Intake';
27
+ const SKILL_PREFLIGHT_HEADING = '## Skill Preflight';
28
+ const PLAN_HEADING = {
29
+ 'technical-artist': '## Production Plan',
30
+ developer: SKILL_PREFLIGHT_HEADING,
31
+ };
32
+ const PLAN_HEADING_BY_LOOP = {
33
+ developer: {
34
+ build: SKILL_PREFLIGHT_HEADING,
35
+ },
36
+ };
37
+ const PLAN_REQUIRED_SUBS = {
38
+ developer: {
39
+ build: [],
40
+ },
41
+ 'technical-artist': ['### Coverage Plan', '### Atlas Assembly Plan', '### Risk Checklist'],
42
+ };
43
+ const PLACEHOLDER_RE = /\{\{[^}]+\}\}/;
44
+ const MIN_TAKEAWAY_LEN = 8;
45
+
46
+ /** Required upstream docs scale with growth ring — ring 1 stays minimal (DESIGN only; design-brief is optional scratch). */
47
+ const DEV_REQUIRED_DOCS_BY_RING = {
48
+ 1: ['docs/project-state.md', 'docs/DESIGN.md'],
49
+ 2: [
50
+ 'docs/project-state.md',
51
+ 'docs/design-brief.md',
52
+ 'docs/DESIGN.md',
53
+ 'docs/layout-spec.md',
54
+ 'docs/atom-tree.json',
55
+ 'docs/implementation-plan.md',
56
+ ],
57
+ 3: [
58
+ 'docs/project-state.md',
59
+ 'docs/design-brief.md',
60
+ 'docs/DESIGN.md',
61
+ 'docs/layout-spec.md',
62
+ 'docs/atom-tree.json',
63
+ 'docs/implementation-plan.md',
64
+ 'docs/style-exploration.md',
65
+ ],
66
+ 4: [
67
+ 'docs/project-state.md',
68
+ 'docs/design-brief.md',
69
+ 'docs/DESIGN.md',
70
+ 'docs/layout-spec.md',
71
+ 'docs/atom-tree.json',
72
+ 'docs/implementation-plan.md',
73
+ 'docs/style-exploration.md',
74
+ 'logs/artist-log.md',
75
+ ],
76
+ };
77
+
78
+ const TA_REQUIRED_DOCS_BY_RING = {
79
+ 4: [
80
+ 'docs/project-state.md',
81
+ 'docs/design-brief.md',
82
+ 'docs/layout-spec.md',
83
+ 'docs/atom-tree.json',
84
+ 'docs/style-exploration.md',
85
+ 'logs/artist-log.md',
86
+ ],
87
+ };
88
+
89
+ // Artist: Skill Preflight enforced in ring 3+ build and ring 4 Wave 1.
90
+ const ARTIST_REQUIRED_DOCS = [];
91
+
92
+ const ROLE_LOG = {
93
+ 'technical-artist': { log: 'logs/ta-log.md', label: 'Technical Artist' },
94
+ developer: { log: 'logs/developer-log.md', label: 'Developer' },
95
+ artist: { log: 'logs/artist-log.md', label: 'Artist' },
96
+ reviewer: { log: 'logs/review-report.md', label: 'Reviewer' },
97
+ 'game-designer': { log: null, label: 'Game Designer' },
98
+ 'skill-selector': { log: null, label: 'Temporary Atom Selector' },
99
+ };
100
+
101
+ const DESIGNER_REQUIRED_DOCS_BY_RING = {
102
+ // Ring 1 = progressive disclosure: a single concise docs/DESIGN.md is the only
103
+ // required deliverable. design-brief.md is optional scratch (validated only if present).
104
+ 1: ['docs/DESIGN.md'],
105
+ 2: [
106
+ 'docs/design-brief.md',
107
+ 'docs/DESIGN.md',
108
+ 'docs/layout-spec.md',
109
+ 'docs/atom-tree.json',
110
+ ],
111
+ 3: [
112
+ 'docs/design-brief.md',
113
+ 'docs/DESIGN.md',
114
+ 'docs/layout-spec.md',
115
+ 'docs/atom-tree.json',
116
+ 'docs/style-exploration.md',
117
+ ],
118
+ 4: [
119
+ 'docs/design-brief.md',
120
+ 'docs/DESIGN.md',
121
+ 'docs/layout-spec.md',
122
+ 'docs/atom-tree.json',
123
+ 'docs/style-exploration.md',
124
+ ],
125
+ };
126
+
127
+ const MIN_DOC_LEN = 120;
128
+
129
+ // `[*_\s]*` tolerates markdown emphasis in the §6 template form `- **Fusion type**:`.
130
+ const FUSION_TYPE_RE =
131
+ /Fusion\s+type[*_\s]*:\s*(Single|A-Visual|B1-Passive|B2-Active|C-Sequential|D-Parallel)/i;
132
+ const HIGH_RISK_FUSION_TYPES = new Set(['C-Sequential', 'D-Parallel']);
133
+
134
+ /**
135
+ * True only when a real pseudo-fusion / risk-accepted decision is documented.
136
+ *
137
+ * Judged by the *value* of the "Pseudo-fusion decision:" field — not the field
138
+ * label, which always contains "pseudo-fusion" in the §6 template (and the
139
+ * placeholder hint contains "risk accepted: …"). A naive substring match would
140
+ * make this high-risk gate a no-op for every template-derived DESIGN.md.
141
+ * @param {string} content
142
+ * @returns {boolean}
143
+ */
144
+ function hasFusionDowngradeDecision(content) {
145
+ // Drop unfilled {…} template placeholders so their hint text cannot satisfy the gate.
146
+ const filled = content.replace(/\{[^}]*\}/g, '');
147
+ // `[*_\s]*` tolerates markdown emphasis in `- **Pseudo-fusion decision**:`.
148
+ const field = filled.match(/Pseudo-?fusion decision[*_\s]*:\s*(.*)/i);
149
+ if (field) {
150
+ const value = field[1].trim();
151
+ const lower = value.toLowerCase();
152
+ const isEmpty = lower === '' || lower === 'n/a' || lower === 'na' || lower === 'none';
153
+ if (!isEmpty) return true;
154
+ // Field present but n/a → no decision documented; fall through to scan the rest.
155
+ }
156
+ // No structured field (or n/a): scan with the bare label stripped so it cannot self-satisfy.
157
+ const scrubbed = filled.replace(/Pseudo-?fusion decision[*_\s]*:/gi, '');
158
+ return /pseudo-?fusion/i.test(scrubbed) || /risk\s+accepted/i.test(scrubbed);
159
+ }
160
+
161
+ /**
162
+ * Ring 1 DESIGN.md must declare fusion classification (Single is valid default).
163
+ * @param {string} content
164
+ * @returns {string[]}
165
+ */
166
+ function validateDesignFusionSection(content) {
167
+ const errors = [];
168
+ const match = content.match(FUSION_TYPE_RE);
169
+ if (!match) {
170
+ errors.push(
171
+ 'DESIGN.md: missing "Fusion type:" — add ## 6. Gameplay Fusion with Fusion type: Single (or A/B/C/D type for fusion intent)',
172
+ );
173
+ return errors;
174
+ }
175
+
176
+ const fusionType = match[1];
177
+ const normalized =
178
+ fusionType.startsWith('B1') ? 'B1-Passive' : fusionType.startsWith('B2') ? 'B2-Active' : fusionType;
179
+ if (HIGH_RISK_FUSION_TYPES.has(normalized) && !hasFusionDowngradeDecision(content)) {
180
+ errors.push(
181
+ `DESIGN.md: Fusion type ${normalized} is high risk — document pseudo-fusion downgrade or explicit "risk accepted" reason in ## 6. Gameplay Fusion`,
182
+ );
183
+ }
184
+
185
+ return errors;
186
+ }
187
+
188
+ function hasAnyPattern(content, patterns) {
189
+ return patterns.some((pattern) => pattern.test(content));
190
+ }
191
+
192
+ function validatePlayableSeedDesign(content) {
193
+ const errors = [];
194
+ const checks = [
195
+ {
196
+ label: 'player action',
197
+ patterns: [/Player Action/i, /玩家.*(动作|操作)/, /Player does/i],
198
+ },
199
+ {
200
+ label: 'immediate feedback',
201
+ patterns: [/Immediate Feedback/i, /即时反馈/, /First feedback/i, /100ms/i],
202
+ },
203
+ {
204
+ label: 'progress signal',
205
+ patterns: [/Progress Signal/i, /进展信号/, /Progression signal/i, /3-5/],
206
+ },
207
+ {
208
+ label: 'first win',
209
+ patterns: [/First[- ]Level Win/i, /First Win/i, /首胜|首关.*赢|首关.*胜/],
210
+ },
211
+ {
212
+ label: 'semantic placeholders',
213
+ patterns: [/Semantic Placeholders/i, /语义占位/],
214
+ },
215
+ {
216
+ label: 'engine hint',
217
+ patterns: [/Engine Hint/i, /Engine/i, /引擎/],
218
+ },
219
+ ];
220
+ for (const check of checks) {
221
+ if (!hasAnyPattern(content, check.patterns)) {
222
+ errors.push(`DESIGN.md playable seed: missing ${check.label}`);
223
+ }
224
+ }
225
+ if (/^##\s+(ENDCARD|CTA|Endcard|Call[- ]to[- ]Action|Hook\s*\+\s*CTA)/im.test(content)) {
226
+ errors.push(
227
+ 'Ring 1 DESIGN.md must not include ENDCARD/CTA design sections — defer hook/CTA production to Ring 3+ (spine closeout slice only when orchestrator marks it)',
228
+ );
229
+ }
230
+ return errors;
231
+ }
232
+
233
+ function validateSkillUseHeuristic(slice) {
234
+ const errors = [];
235
+ const skillUse = String(slice.skillUse || '').trim();
236
+ const fromScratch = String(slice.fromScratchReason || '').trim();
237
+ if (!skillUse) return errors;
238
+ const claimsDefines = /\bdefines\b|定义了|实现了本 slice|defines the core|本 skill.*核心/i.test(skillUse);
239
+ if (fromScratch && claimsDefines) {
240
+ errors.push(
241
+ `slice "${slice.sliceId}": skillUse must not claim a skill defines the core mechanic when fromScratchReason is set`,
242
+ );
243
+ } else if (claimsDefines) {
244
+ errors.push(
245
+ `slice "${slice.sliceId}": skillUse must describe borrowed layout/lifecycle/input patterns only — do not claim a skill "defines" the core mechanic without playcraft skills read proof`,
246
+ );
247
+ }
248
+ if (fromScratch && /\b(implement|实现|covers?|覆盖)\b.*\b(core|核心|mechanic|机制)\b/i.test(skillUse)) {
249
+ errors.push(
250
+ `slice "${slice.sliceId}": skillUse must not claim skill coverage of the core mechanic when fromScratchReason is present`,
251
+ );
252
+ }
253
+ return errors;
254
+ }
255
+
256
+ function normalizeContextRole(role) {
257
+ const value = normalizeRole(role);
258
+ if (value === 'ref-flow' || value === 'temporary-ref-flow') return 'skill-selector';
259
+ return value;
260
+ }
261
+
262
+ function validateHandoffContext(projectDir, role) {
263
+ const errors = [];
264
+ const state = readState(projectDir);
265
+ const handoff = readHandoff(projectDir);
266
+ const shouldRequire = String(handoff.subagent_stop || '').toLowerCase() === 'true';
267
+ if (!shouldRequire) return errors;
268
+
269
+ const context = state.handoffContext || {};
270
+ const last = context.lastRole || {};
271
+ const next = context.nextRole || {};
272
+ const normalizedRole = normalizeContextRole(role);
273
+ const contextRole = normalizeContextRole(last.role);
274
+ if (!contextRole || contextRole === 'none') {
275
+ errors.push('handoffContext.lastRole.role is required on STOP');
276
+ } else if (contextRole !== normalizedRole) {
277
+ errors.push(`handoffContext.lastRole.role must match STOP role (${role}), found ${last.role}`);
278
+ }
279
+ if (!last.summary || String(last.summary).length < 20 || /No sub-agent has completed/i.test(last.summary)) {
280
+ errors.push('handoffContext.lastRole.summary must capture the completed work in one concrete sentence');
281
+ }
282
+ if (!Array.isArray(last.artifactRefs) || last.artifactRefs.length === 0) {
283
+ errors.push('handoffContext.lastRole.artifactRefs must list produced contract/evidence paths');
284
+ }
285
+ if (!next.role || String(next.role).toLowerCase() === 'none') {
286
+ errors.push('handoffContext.nextRole.role is required for orchestrator routing');
287
+ }
288
+ if (!Array.isArray(next.mustKnow) || next.mustKnow.length === 0) {
289
+ errors.push('handoffContext.nextRole.mustKnow must include the next role working-memory facts');
290
+ }
291
+ return errors;
292
+ }
293
+
294
+ function validateSkillsMatchSliceRefs(projectDir, ss) {
295
+ const errors = [];
296
+ const atomPath = path.join(projectDir, 'docs/atom-tree.json');
297
+ if (!fs.existsSync(atomPath)) {
298
+ errors.push('slice-axis slice seed requires docs/atom-tree.json with meta.skillsMatch snapshot');
299
+ return errors;
300
+ }
301
+ let atom;
302
+ try {
303
+ atom = JSON.parse(fs.readFileSync(atomPath, 'utf8'));
304
+ } catch {
305
+ errors.push('docs/atom-tree.json must be valid JSON');
306
+ return errors;
307
+ }
308
+ const meta = atom?.meta || {};
309
+ const items = Array.isArray(meta.skillsMatch?.items) ? meta.skillsMatch.items : [];
310
+ if (items.length === 0) {
311
+ errors.push(
312
+ 'atom-tree.meta.skillsMatch.items must be non-empty (run playcraft skills match before slice seed)',
313
+ );
314
+ }
315
+ const selectedAtoms = Array.isArray(meta.selectedAtoms)
316
+ ? meta.selectedAtoms.map((a) => String(a).trim()).filter(Boolean)
317
+ : [];
318
+ const noMatchReason = String(meta.compactSelectionNoMatchReason || '').trim();
319
+ if (selectedAtoms.length === 0 && !noMatchReason) {
320
+ errors.push(
321
+ 'atom-tree.meta.selectedAtoms must be non-empty or meta.compactSelectionNoMatchReason must explain why no compact atoms were selected',
322
+ );
323
+ }
324
+ const devSlices = ss.slices.filter((s) => s.role === 'developer');
325
+ for (const slice of devSlices) {
326
+ const skillRefs = Array.isArray(slice.skillRefs)
327
+ ? slice.skillRefs.map((r) => String(r).trim()).filter(Boolean)
328
+ : [];
329
+ const skillUse = String(slice.skillUse || '').trim();
330
+ const fromScratchReason = String(slice.fromScratchReason || '').trim();
331
+ if (skillRefs.length > 0) {
332
+ if (!skillUse) {
333
+ errors.push(`slice "${slice.sliceId}": skillRefs requires non-empty skillUse`);
334
+ }
335
+ } else if (!fromScratchReason) {
336
+ errors.push(
337
+ `slice "${slice.sliceId}": must include skillRefs + skillUse (skill-informed) or fromScratchReason (no matched skill)`,
338
+ );
339
+ }
340
+ errors.push(...validateSkillUseHeuristic(slice));
341
+ }
342
+ const activated = meta.activatedSlices;
343
+ if (!Array.isArray(activated) || activated.length === 0) {
344
+ errors.push('atom-tree.meta.activatedSlices must list sliceIds currently driving dispatch');
345
+ } else {
346
+ for (const id of activated) {
347
+ if (!ss.slices.some((s) => s.sliceId === id)) {
348
+ errors.push(`atom-tree.meta.activatedSlices "${id}" is not listed in sliceState.slices`);
349
+ }
350
+ }
351
+ }
352
+ return errors;
353
+ }
354
+
355
+ function validateGameDesignerSliceQueueSeed(projectDir) {
356
+ const errors = [];
357
+ const ss = normalizeSliceState(readState(projectDir).sliceState || {});
358
+ if (!ss.slices.length) return errors;
359
+
360
+ const ring = Math.min(Math.max(readIteration(projectDir) || 1, 1), 4);
361
+ const devSlices = ss.slices.filter((s) => s.role === 'developer');
362
+ if (devSlices.length === 0) {
363
+ errors.push('sliceState.slices must include at least one developer slice after Game Designer seed');
364
+ }
365
+ if (!ss.activeSliceId || ss.activeSliceId === 'none') {
366
+ errors.push('sliceState.activeSliceId must point at the first dispatchable slice after Game Designer seed');
367
+ } else {
368
+ const active = ss.slices.find((s) => s.sliceId === ss.activeSliceId);
369
+ if (!active) {
370
+ errors.push(`sliceState.activeSliceId "${ss.activeSliceId}" does not match any slice`);
371
+ } else if (active.role !== 'developer' && ring === 1) {
372
+ errors.push(`Ring 1 first active slice should be developer-owned (got "${active.role}")`);
373
+ }
374
+ }
375
+ for (const slice of devSlices) {
376
+ if (!String(slice.doWhat || '').trim()) {
377
+ errors.push(`slice "${slice.sliceId}": missing doWhat`);
378
+ }
379
+ if (!String(slice.verifier || '').trim()) {
380
+ errors.push(`slice "${slice.sliceId}": missing verifier`);
381
+ }
382
+ if (!Array.isArray(slice.acceptance) || slice.acceptance.length === 0) {
383
+ errors.push(`slice "${slice.sliceId}": missing acceptance criteria`);
384
+ }
385
+ }
386
+
387
+ errors.push(...validateSkillsMatchSliceRefs(projectDir, ss));
388
+
389
+ if (ring === 1) {
390
+ for (const rel of ['docs/layout-spec.md', 'docs/style-exploration.md']) {
391
+ if (fs.existsSync(path.join(projectDir, rel))) {
392
+ errors.push(`Ring 1 Game Designer must not create ${rel} yet`);
393
+ }
394
+ }
395
+ const briefPath = path.join(projectDir, 'docs/design-brief.md');
396
+ if (!fs.existsSync(briefPath)) {
397
+ errors.push('slice-axis Ring 1 requires docs/design-brief.md (progressive discovery log)');
398
+ }
399
+ }
400
+ if (ring === 2) {
401
+ if (devSlices.length < 2) {
402
+ errors.push('Ring 2 slice deepening requires at least 2 developer periphery slices');
403
+ }
404
+ if (devSlices.length > 4) {
405
+ errors.push('Ring 2 slice deepening allows at most 4 developer slices');
406
+ }
407
+ const briefPath = path.join(projectDir, 'docs/design-brief.md');
408
+ if (!fs.existsSync(briefPath)) {
409
+ errors.push('slice-axis Ring 2 requires docs/design-brief.md (periphery discovery log)');
410
+ }
411
+ if (fs.existsSync(path.join(projectDir, 'docs/style-exploration.md'))) {
412
+ errors.push('Ring 2 Game Designer clarify must not create docs/style-exploration.md yet (no Artist production)');
413
+ }
414
+ }
415
+ return errors;
416
+ }
417
+
418
+ function validateGameDesignerSeedContext(projectDir) {
419
+ const errors = [];
420
+ const state = readState(projectDir);
421
+ const ss = normalizeSliceState(state.sliceState || {});
422
+ if (ss.slices.length > 0) {
423
+ return validateGameDesignerSliceQueueSeed(projectDir);
424
+ }
425
+
426
+ const context = state.handoffContext || {};
427
+ const shared = context.sharedDecisions || {};
428
+ const next = context.nextRole || {};
429
+ if (!shared.coreAction || shared.coreAction === 'unset') {
430
+ errors.push('handoffContext.sharedDecisions.coreAction must describe the Ring 1 player action');
431
+ }
432
+ if (!Array.isArray(shared.semanticPlaceholders) || shared.semanticPlaceholders.length === 0) {
433
+ errors.push('handoffContext.sharedDecisions.semanticPlaceholders must list Ring 1 semantic placeholders');
434
+ }
435
+ const nextRole = normalizeContextRole(next.role);
436
+ if (nextRole !== 'skill-selector') {
437
+ errors.push('handoffContext.nextRole.role should route to temporary ref-flow atom-selection subagent');
438
+ }
439
+ return errors;
440
+ }
441
+
442
+ function requiredDocsForRole(role, iteration) {
443
+ const ring = Math.min(Math.max(iteration || 1, 1), 4);
444
+ if (role === 'developer') {
445
+ return DEV_REQUIRED_DOCS_BY_RING[ring] || DEV_REQUIRED_DOCS_BY_RING[1];
446
+ }
447
+ if (role === 'technical-artist') {
448
+ return TA_REQUIRED_DOCS_BY_RING[ring] || [];
449
+ }
450
+ return ARTIST_REQUIRED_DOCS;
451
+ }
452
+
453
+ /** @param {string} raw */
454
+ function parseHookInput(raw) {
455
+ if (!raw.trim()) return {};
456
+ try {
457
+ return JSON.parse(raw);
458
+ } catch {
459
+ return {};
460
+ }
461
+ }
462
+
463
+ /** @param {string} s */
464
+ function normalizeRole(s) {
465
+ return String(s || '')
466
+ .toLowerCase()
467
+ .replace(/\s+/g, '-')
468
+ .replace(/_/g, '-');
469
+ }
470
+
471
+ /**
472
+ * @param {Record<string, unknown>} input
473
+ * @returns {'technical-artist' | 'developer' | 'artist' | 'reviewer' | 'game-designer' | 'skill-selector' | null}
474
+ */
475
+ function detectRole(input) {
476
+ const candidates = [
477
+ input.agent_type,
478
+ input.agent_name,
479
+ input.subagent_name,
480
+ input.subagent_type,
481
+ input.subagent,
482
+ input.role,
483
+ ].filter(Boolean);
484
+
485
+ for (const c of candidates) {
486
+ const n = normalizeRole(String(c));
487
+ if (n.includes('technical-artist') || n === 'ta') return 'technical-artist';
488
+ if (n === 'developer-lead' || n === 'developer' || n === 'dev') return 'developer';
489
+ if (n === 'artist') return 'artist';
490
+ if (n === 'reviewer' || n === 'review') return 'reviewer';
491
+ if (n === 'game-designer' || n === 'game designer' || n === 'designer') return 'game-designer';
492
+ if (n === 'skill-selector' || n === 'skill selector' || n === 'ref-flow') return 'skill-selector';
493
+ }
494
+
495
+ const blobs = [
496
+ input.last_assistant_message,
497
+ input.last_message,
498
+ input.message,
499
+ input.output,
500
+ ]
501
+ .filter((v) => typeof v === 'string')
502
+ .join('\n');
503
+
504
+ const stopMatch = blobs.match(/---\s*PLAYCRAFT_STOP\s*---[\s\S]*?role:\s*([^\s\n]+)/i);
505
+ if (stopMatch) {
506
+ const r = normalizeRole(stopMatch[1]);
507
+ if (r in ROLE_LOG) return /** @type {keyof ROLE_LOG} */ (r);
508
+ }
509
+
510
+ return null;
511
+ }
512
+
513
+ /**
514
+ * @param {string} projectDir
515
+ * @returns {Record<string, string>}
516
+ */
517
+ function readHandoff(projectDir) {
518
+ const statePath = path.join(projectDir, 'docs/project-state.md');
519
+ if (!fs.existsSync(statePath)) return {};
520
+ const content = fs.readFileSync(statePath, 'utf8');
521
+ const fence = content.match(/```yaml\n([\s\S]*?)```/);
522
+ if (!fence) return {};
523
+ /** @type {Record<string, string>} */
524
+ const handoff = {};
525
+ for (const line of fence[1].split('\n')) {
526
+ const m = line.match(/^(\w+):\s*(.+)$/);
527
+ if (m) handoff[m[1]] = m[2].trim();
528
+ }
529
+ return handoff;
530
+ }
531
+
532
+ /**
533
+ * @param {string} content
534
+ */
535
+ function parseRingGateCanAskUser(content) {
536
+ const m = content.match(/\|\s*ringGateCanAskUser\s*\|\s*(\S+)/i);
537
+ return m ? m[1].toLowerCase() : null;
538
+ }
539
+
540
+ /**
541
+ * @param {string} projectDir
542
+ * @param {string} relPath
543
+ */
544
+ function validateDocFile(projectDir, relPath) {
545
+ const errors = [];
546
+ const abs = path.join(projectDir, relPath);
547
+ if (!fs.existsSync(abs)) {
548
+ errors.push(`Missing required doc: ${relPath}`);
549
+ return errors;
550
+ }
551
+ const content = fs.readFileSync(abs, 'utf8');
552
+ if (content.length < MIN_DOC_LEN) {
553
+ errors.push(`${relPath}: too short — fill with real contract content (≥${MIN_DOC_LEN} chars)`);
554
+ }
555
+ const placeholders = content.match(/\{\{[^}]+\}\}/g) || [];
556
+ if (placeholders.length >= 3 || (PLACEHOLDER_RE.test(content) && placeholders.length > 0)) {
557
+ errors.push(`${relPath}: still contains {{placeholders}} — replace with real data`);
558
+ }
559
+ return errors;
560
+ }
561
+
562
+ /**
563
+ * @param {string} content
564
+ * @returns {Record<string, string>}
565
+ */
566
+ function parseReviewReportDoD(content) {
567
+ const start = content.indexOf('## Ring DoD Fields');
568
+ if (start === -1) return {};
569
+ const after = content.slice(start);
570
+ const nextHeading = after.search(/\n##\s+/);
571
+ const section = nextHeading === -1 ? after : after.slice(0, nextHeading);
572
+ /** @type {Record<string, string>} */
573
+ const fields = {};
574
+ for (const line of section.split('\n')) {
575
+ const trimmed = line.trim();
576
+ if (!trimmed.startsWith('|')) continue;
577
+ if (/^\|\s*---/.test(trimmed)) continue;
578
+ if (/^\|\s*Field/i.test(trimmed)) continue;
579
+ const parts = trimmed
580
+ .split('|')
581
+ .map((p) => p.trim())
582
+ .filter((_, i, arr) => i > 0 && i < arr.length - 1);
583
+ if (parts.length < 2) continue;
584
+ const key = parts[0];
585
+ if (/^(field|owner|verifier)$/i.test(key)) continue;
586
+ const value = parts.length >= 3 ? parts[parts.length - 1] : parts[1];
587
+ fields[key] = value;
588
+ }
589
+ return fields;
590
+ }
591
+
592
+ /**
593
+ * @param {string} projectDir
594
+ */
595
+ function validateReviewerHandoff(projectDir) {
596
+ const errors = [];
597
+ const handoff = readHandoff(projectDir);
598
+ const waitingFor = (handoff.waiting_for || '').toLowerCase();
599
+ const nextAction = handoff.next_orchestrator_action || '';
600
+
601
+ if (waitingFor === 'user_gate') {
602
+ errors.push(
603
+ 'Reviewer must not set waiting_for=user_gate — use waiting_for=orchestrator and next_orchestrator_action with npm run ring:gate',
604
+ );
605
+ }
606
+ if (/AskUserQuestion/i.test(nextAction) && !/ring:gate/i.test(nextAction)) {
607
+ errors.push(
608
+ 'Reviewer next_orchestrator_action must not route AskUserQuestion — orchestrator runs ring:gate first',
609
+ );
610
+ }
611
+ if (/ring:gate/i.test(nextAction) && waitingFor !== 'orchestrator') {
612
+ errors.push('When routing ring:gate, waiting_for must be orchestrator');
613
+ }
614
+ if (handoff.subagent_stop !== 'true') {
615
+ errors.push('Reviewer STOP requires subagent_stop: true');
616
+ }
617
+ if (!nextAction || nextAction === 'null') {
618
+ errors.push('Reviewer STOP requires next_orchestrator_action');
619
+ }
620
+ return errors;
621
+ }
622
+
623
+ /**
624
+ * @param {string} projectDir
625
+ * @param {number} iteration
626
+ */
627
+ function validateReviewReportDoD(projectDir, iteration) {
628
+ const errors = [];
629
+ const reportPath = path.join(projectDir, 'logs/review-report.md');
630
+ if (!fs.existsSync(reportPath)) {
631
+ return ['Reviewer: create logs/review-report.md from templates/review-report.template.md before STOP'];
632
+ }
633
+ const content = fs.readFileSync(reportPath, 'utf8');
634
+ if (!content.includes('## Ring DoD Fields')) {
635
+ errors.push('review-report.md: missing "## Ring DoD Fields" section');
636
+ return errors;
637
+ }
638
+ const fields = parseReviewReportDoD(content);
639
+ const required = reviewDoDKeysForRing(iteration);
640
+ if (required.length === 0) {
641
+ return errors;
642
+ }
643
+ for (const key of required) {
644
+ const val = (fields[key] || '').toLowerCase();
645
+ if (!val || val.includes('{{') || val === 'pending' || val === 'n-a' || val === 'n/a') {
646
+ errors.push(
647
+ `review-report.md Reviewer evidence: ${key} must be pass or fail before STOP (script fields stay pending for ring:gate)`,
648
+ );
649
+ }
650
+ }
651
+ if (!content.includes('## Verdict')) {
652
+ errors.push('review-report.md: missing "## Verdict" section');
653
+ }
654
+ return errors;
655
+ }
656
+
657
+ const UI_DIFF_RUBRIC_IDS = [
658
+ 'bg_clean',
659
+ 'no_double_board',
660
+ 'no_hud_ghost',
661
+ 'no_fake_ui',
662
+ 'tile_silhouette',
663
+ 'text_no_overlap',
664
+ 'mc_layout',
665
+ ];
666
+
667
+ const EVIDENCE_SCREENSHOT_RE = /\.(png|jpe?g|webp)\b/i;
668
+
669
+ /**
670
+ * Parse the Structured Visual Rubric table into `id → { result, evidence }`.
671
+ * @param {string} section
672
+ * @returns {Map<string, { result: string, evidence: string }>}
673
+ */
674
+ function parseRubricRows(section) {
675
+ const rows = new Map();
676
+ for (const line of section.split('\n')) {
677
+ const trimmed = line.trim();
678
+ if (!trimmed.startsWith('|')) continue;
679
+ if (/^\|[\s|:-]+\|?$/.test(trimmed)) continue;
680
+ const parts = trimmed
681
+ .split('|')
682
+ .map((p) => p.trim())
683
+ .filter((_, i, arr) => i > 0 && i < arr.length - 1);
684
+ if (parts.length < 3) continue;
685
+ const id = parts[0].replace(/`/g, '').trim().toLowerCase();
686
+ if (!id || id === 'check id' || id === 'check') continue;
687
+ rows.set(id, { result: parts[1], evidence: parts.slice(2).join(' | ').trim() });
688
+ }
689
+ return rows;
690
+ }
691
+
692
+ /**
693
+ * Ring 4 ui_diff must include a structured visual rubric where each check row is
694
+ * actually filled — an explicit PASS/FAIL verdict and evidence that cites a
695
+ * screenshot path. Earlier this only checked that each row *id* appeared anywhere
696
+ * in the file, so a report full of `{{PASS/FAIL}}` / `{{...}}` placeholders passed
697
+ * (the test11 "forced look-at-the-screenshot" gate degraded into a format check).
698
+ * @param {string} content
699
+ */
700
+ function validateUiDiffVisualRubric(content) {
701
+ const errors = [];
702
+ if (!/ui_diff|Structured Visual Rubric/i.test(content)) {
703
+ return errors;
704
+ }
705
+ if (!/###\s+Structured Visual Rubric/i.test(content)) {
706
+ errors.push(
707
+ 'review-report.md ui_diff: missing "### Structured Visual Rubric" — Read each screenshot and fill PASS/FAIL + evidence',
708
+ );
709
+ return errors;
710
+ }
711
+
712
+ const section = extractMarkdownSection(content, '### Structured Visual Rubric') || '';
713
+ const rows = parseRubricRows(section);
714
+
715
+ for (const id of UI_DIFF_RUBRIC_IDS) {
716
+ const row = rows.get(id);
717
+ if (!row) {
718
+ errors.push(`review-report.md Structured Visual Rubric: missing check row "${id}"`);
719
+ continue;
720
+ }
721
+ const result = row.result || '';
722
+ if (PLACEHOLDER_RE.test(result) || !/\b(PASS|FAIL)\b/i.test(result)) {
723
+ errors.push(
724
+ `review-report.md Structured Visual Rubric: "${id}" Result must be an explicit PASS or FAIL (found "${result || 'empty'}")`,
725
+ );
726
+ }
727
+ const evidence = row.evidence || '';
728
+ if (!evidence || PLACEHOLDER_RE.test(evidence)) {
729
+ errors.push(
730
+ `review-report.md Structured Visual Rubric: "${id}" Evidence is empty or a {{placeholder}} — describe what you saw in the screenshot`,
731
+ );
732
+ } else if (!EVIDENCE_SCREENSHOT_RE.test(evidence) && !/logs\//i.test(evidence)) {
733
+ errors.push(
734
+ `review-report.md Structured Visual Rubric: "${id}" Evidence must cite a screenshot path (e.g. logs/ui-diff-game.png — what you see)`,
735
+ );
736
+ }
737
+ }
738
+ return errors;
739
+ }
740
+
741
+ /**
742
+ * uiReviewStatus in project-state must align with uiDiff in review-report (B4 atomic writeback guard).
743
+ * @param {string} projectDir
744
+ * @param {string} reportContent
745
+ */
746
+ function validateUiReviewStatusConsistency(projectDir, reportContent) {
747
+ const errors = [];
748
+ const fields = parseReviewReportDoD(reportContent);
749
+ const uiDiff = (fields.uiDiff || '').toLowerCase();
750
+ if (!uiDiff || uiDiff.includes('{{') || uiDiff === 'pending' || uiDiff === 'n-a') {
751
+ return errors;
752
+ }
753
+ const uiReviewStatusRaw = readState(projectDir).devPreview?.uiReviewStatus;
754
+ if (!uiReviewStatusRaw) return errors;
755
+ const uiReviewStatus = String(uiReviewStatusRaw).toLowerCase();
756
+ const passSet = new Set(['pass', 'passed']);
757
+ const failSet = new Set(['fail', 'failed']);
758
+ const uiDiffPass = passSet.has(uiDiff);
759
+ const uiDiffFail = failSet.has(uiDiff);
760
+ const statusPass = passSet.has(uiReviewStatus);
761
+ const statusFail = failSet.has(uiReviewStatus);
762
+ if (uiDiffPass && statusFail) {
763
+ errors.push(
764
+ 'project-state uiReviewStatus=failed but review-report uiDiff=pass — align checkpoint with visual rubric verdict',
765
+ );
766
+ }
767
+ if (uiDiffFail && statusPass) {
768
+ errors.push(
769
+ 'project-state uiReviewStatus=passed but review-report uiDiff=fail — align checkpoint with visual rubric verdict',
770
+ );
771
+ }
772
+ return errors;
773
+ }
774
+
775
+ /**
776
+ * @param {string} projectDir
777
+ */
778
+ function validateReviewerStop(projectDir) {
779
+ const iteration = readIteration(projectDir) || 1;
780
+ const loop = readLoop(projectDir);
781
+ const handoff = readHandoff(projectDir);
782
+ const nextAction = handoff.next_orchestrator_action || '';
783
+ const errors = [...validateReviewerHandoff(projectDir), ...validateHandoffContext(projectDir, 'reviewer')];
784
+
785
+ if (loop === 'verify' && /ring:gate/i.test(nextAction)) {
786
+ errors.push(...validateReviewReportDoD(projectDir, iteration));
787
+ const reportPath = path.join(projectDir, 'logs/review-report.md');
788
+ if (fs.existsSync(reportPath)) {
789
+ const reportContent = fs.readFileSync(reportPath, 'utf8');
790
+ if (iteration >= 4) {
791
+ errors.push(...validateUiDiffVisualRubric(reportContent));
792
+ errors.push(...validateUiReviewStatusConsistency(projectDir, reportContent));
793
+ }
794
+ }
795
+ const loadCheck = readState(projectDir).devPreview?.loadCheck;
796
+ if (loadCheck && String(loadCheck).toLowerCase() === 'pending' && iteration < 4) {
797
+ errors.push('Dev Preview loadCheck still pending — complete load_check before routing ring:gate');
798
+ }
799
+ } else if (!fs.existsSync(path.join(projectDir, 'logs/review-report.md'))) {
800
+ errors.push('Reviewer: create logs/review-report.md before STOP');
801
+ }
802
+
803
+ return errors;
804
+ }
805
+
806
+ /**
807
+ * @param {string} projectDir
808
+ */
809
+ function validateGameDesignerStop(projectDir) {
810
+ const iteration = readIteration(projectDir) || 1;
811
+ const loop = readLoop(projectDir);
812
+ const errors = [];
813
+ const handoff = readHandoff(projectDir);
814
+
815
+ if (handoff.subagent_stop !== 'true') {
816
+ errors.push('Game Designer STOP requires subagent_stop: true');
817
+ }
818
+ if ((handoff.waiting_for || '').toLowerCase() !== 'orchestrator') {
819
+ errors.push('Game Designer STOP requires waiting_for: orchestrator');
820
+ }
821
+ if (!handoff.next_orchestrator_action || handoff.next_orchestrator_action === 'null') {
822
+ errors.push('Game Designer STOP requires next_orchestrator_action (e.g. Spawn temporary ref-flow atom-selection subagent for ring 1 atom-tree selection)');
823
+ }
824
+
825
+ if (loop !== 'clarify') {
826
+ return errors;
827
+ }
828
+
829
+ const ring = Math.min(Math.max(iteration, 1), 4);
830
+ const state = readState(projectDir);
831
+ const ss = normalizeSliceState(state.sliceState || {});
832
+ const sliceAxisClarify = ss.slices.length > 0 && (ring === 1 || ring === 2);
833
+
834
+ const requiredDocs = sliceAxisClarify
835
+ ? ['docs/DESIGN.md', 'docs/design-brief.md']
836
+ : DESIGNER_REQUIRED_DOCS_BY_RING[ring] || DESIGNER_REQUIRED_DOCS_BY_RING[1];
837
+ for (const doc of requiredDocs) {
838
+ errors.push(...validateDocFile(projectDir, doc));
839
+ }
840
+
841
+ if (ring === 1) {
842
+ const designPath = path.join(projectDir, 'docs/DESIGN.md');
843
+ if (fs.existsSync(designPath)) {
844
+ errors.push(...validatePlayableSeedDesign(fs.readFileSync(designPath, 'utf8')));
845
+ }
846
+ }
847
+
848
+ if (sliceAxisClarify) {
849
+ errors.push(...validateHandoffContext(projectDir, 'game-designer'));
850
+ errors.push(...validateGameDesignerSeedContext(projectDir));
851
+ const refs = state.handoffContext?.lastRole?.artifactRefs || [];
852
+ if (!refs.some((r) => String(r).includes('atom-tree.json'))) {
853
+ errors.push('handoffContext.lastRole.artifactRefs must include docs/atom-tree.json');
854
+ }
855
+ } else if (ring === 1) {
856
+ errors.push(...validateHandoffContext(projectDir, 'game-designer'));
857
+ errors.push(...validateGameDesignerSeedContext(projectDir));
858
+ }
859
+
860
+ const briefPath = path.join(projectDir, 'docs/design-brief.md');
861
+ if (fs.existsSync(briefPath)) {
862
+ const brief = fs.readFileSync(briefPath, 'utf8');
863
+ if (!/##\s+(Brainstorm|Core Gameplay|Discovery|Core Discovery)/i.test(brief)) {
864
+ errors.push('design-brief.md: missing Discovery / Brainstorm / Core Gameplay section with process record');
865
+ }
866
+ }
867
+
868
+ return errors;
869
+ }
870
+
871
+ /**
872
+ * @param {string} projectDir
873
+ */
874
+ function validateSkillSelectorStop(projectDir) {
875
+ const errors = [];
876
+ const handoff = readHandoff(projectDir);
877
+ errors.push(...validateHandoffContext(projectDir, 'skill-selector'));
878
+
879
+ if (handoff.subagent_stop !== 'true') {
880
+ errors.push('Temporary atom-selection STOP requires subagent_stop: true');
881
+ }
882
+ if ((handoff.waiting_for || '').toLowerCase() !== 'orchestrator') {
883
+ errors.push('Temporary atom-selection STOP requires waiting_for: orchestrator');
884
+ }
885
+ const next = handoff.next_orchestrator_action || '';
886
+ if (!next || next === 'null') {
887
+ errors.push('Temporary atom-selection STOP requires next_orchestrator_action');
888
+ } else if (!/init-template|scaffold|developer/i.test(next)) {
889
+ errors.push('Temporary atom-selection STOP should route orchestrator to scaffold gate, then @developer');
890
+ }
891
+
892
+ const atomTreePath = path.join(projectDir, 'docs/atom-tree.json');
893
+ if (!fs.existsSync(atomTreePath)) {
894
+ errors.push('Temporary atom-selection STOP requires docs/atom-tree.json');
895
+ return errors;
896
+ }
897
+
898
+ let tree;
899
+ try {
900
+ tree = JSON.parse(fs.readFileSync(atomTreePath, 'utf8'));
901
+ } catch (e) {
902
+ errors.push(`docs/atom-tree.json parse failed: ${e.message}`);
903
+ return errors;
904
+ }
905
+
906
+ const engineDecision = tree?.meta?.engineDecision;
907
+ if (!engineDecision || typeof engineDecision !== 'object') {
908
+ errors.push('docs/atom-tree.json missing meta.engineDecision');
909
+ } else {
910
+ if (!engineDecision.adapter || engineDecision.adapter === 'unset') {
911
+ errors.push('meta.engineDecision.adapter must be set for init-template');
912
+ }
913
+ if (!engineDecision.engineAtom) {
914
+ errors.push('meta.engineDecision.engineAtom must be set');
915
+ }
916
+ }
917
+
918
+ const selectedAtoms = tree?.meta?.selectedAtoms;
919
+ if (!Array.isArray(selectedAtoms) || selectedAtoms.length === 0) {
920
+ errors.push('meta.selectedAtoms must be a non-empty compact atom list');
921
+ }
922
+ if (!tree?.engine) {
923
+ errors.push('docs/atom-tree.json missing top-level engine atom');
924
+ }
925
+
926
+ return errors;
927
+ }
928
+
929
+ /** @param {string} cell */
930
+ function isReadChecked(cell) {
931
+ const t = cell.trim();
932
+ if (/[✓✔☑]/.test(t)) return true;
933
+ if (/\[x\]/i.test(t)) return true;
934
+ if (/^(yes|done|true|read)$/i.test(t)) return true;
935
+ return false;
936
+ }
937
+
938
+ /**
939
+ * @param {string} content
940
+ * @returns {{ rows: { doc: string, read: string, takeaway: string }[], errors: string[] }}
941
+ */
942
+ function parseUpstreamIntake(content) {
943
+ const errors = [];
944
+ const start = content.indexOf(INTAKE_HEADING);
945
+ if (start === -1) {
946
+ return { rows: [], errors: [`Missing "${INTAKE_HEADING}" section`] };
947
+ }
948
+
949
+ const after = content.slice(start + INTAKE_HEADING.length);
950
+ const nextHeading = after.search(/\n##\s+/);
951
+ const section = nextHeading === -1 ? after : after.slice(0, nextHeading);
952
+
953
+ const rows = [];
954
+ const unmatchedRows = [];
955
+ for (const line of section.split('\n')) {
956
+ const trimmed = line.trim();
957
+ if (!trimmed.startsWith('|')) continue;
958
+ if (/^\|\s*---/.test(trimmed)) continue;
959
+ if (/^\|\s*Doc(ument)?\s*\|/i.test(trimmed)) continue;
960
+
961
+ const parts = trimmed
962
+ .split('|')
963
+ .map((p) => p.trim())
964
+ .filter((_, i, arr) => i > 0 && i < arr.length - 1);
965
+
966
+ if (parts.length < 3) continue;
967
+
968
+ const doc = parts[0].replace(/^`/, '').replace(/`$/, '').trim();
969
+ if (!doc.startsWith('docs/') && !doc.startsWith('logs/')) {
970
+ unmatchedRows.push(doc || '(empty)');
971
+ continue;
972
+ }
973
+
974
+ rows.push({ doc, read: parts[1], takeaway: parts[2] });
975
+ }
976
+
977
+ if (rows.length === 0) {
978
+ if (unmatchedRows.length > 0) {
979
+ errors.push(
980
+ `Upstream Intake table has ${unmatchedRows.length} row(s) but none start with "docs/" or "logs/" in the first column. Found: ${unmatchedRows.slice(0, 5).join(', ')}${unmatchedRows.length > 5 ? ` ... and ${unmatchedRows.length - 5} more` : ''}. Expected format: | \`docs/project-state.md\` | ✓ | one-line takeaway |`
981
+ );
982
+ } else {
983
+ errors.push(
984
+ 'Upstream Intake table has no data rows. Expected format:\n| `docs/project-state.md` | ✓ | one-line takeaway |\n| `docs/implementation-plan.md` | ✓ | ... |\n(Repeat for each required doc — see templates/developer-log.template.md for the full list.)'
985
+ );
986
+ }
987
+ }
988
+
989
+ return { rows, errors };
990
+ }
991
+
992
+ /**
993
+ * @param {string} content
994
+ * @param {string[]} requiredDocs
995
+ */
996
+ function validateIntakeContent(content, requiredDocs) {
997
+ const { rows, errors } = parseUpstreamIntake(content);
998
+ const byDoc = new Map(rows.map((r) => [r.doc, r]));
999
+
1000
+ for (const doc of requiredDocs) {
1001
+ const row = byDoc.get(doc);
1002
+ if (!row) {
1003
+ errors.push(`Missing row for ${doc}`);
1004
+ continue;
1005
+ }
1006
+ if (!isReadChecked(row.read)) {
1007
+ errors.push(`${doc}: mark Read column (✓) after reading`);
1008
+ }
1009
+ const takeaway = row.takeaway.trim();
1010
+ if (!takeaway || PLACEHOLDER_RE.test(takeaway) || takeaway.length < MIN_TAKEAWAY_LEN) {
1011
+ errors.push(`${doc}: add a concrete one-line takeaway (≥${MIN_TAKEAWAY_LEN} chars, no {{placeholders}})`);
1012
+ }
1013
+ }
1014
+
1015
+ return errors;
1016
+ }
1017
+
1018
+ /**
1019
+ * @param {string} content
1020
+ * @param {'technical-artist' | 'developer'} role
1021
+ * @param {'clarify'|'build'|'verify'|null} [loop]
1022
+ */
1023
+ function validatePlan(content, role, loop) {
1024
+ let heading = PLAN_HEADING[role];
1025
+ /** @type {string[]} */
1026
+ let requiredSubs = [];
1027
+
1028
+ if (role === 'developer') {
1029
+ if (loop && PLAN_HEADING_BY_LOOP.developer[loop]) {
1030
+ heading = PLAN_HEADING_BY_LOOP.developer[loop];
1031
+ requiredSubs = PLAN_REQUIRED_SUBS.developer[loop] ?? [];
1032
+ }
1033
+ } else if (role === 'technical-artist') {
1034
+ requiredSubs = PLAN_REQUIRED_SUBS['technical-artist'] ?? [];
1035
+ }
1036
+
1037
+ const errors = [];
1038
+ const start = content.indexOf(heading);
1039
+ if (start === -1) {
1040
+ return [`Missing "${heading}" section — write plan before STOP (loop: ${loop || 'unknown'})`];
1041
+ }
1042
+
1043
+ const after = content.slice(start + heading.length);
1044
+ const nextHeading = after.search(/\n##\s+/);
1045
+ const section = nextHeading === -1 ? after : after.slice(0, nextHeading);
1046
+
1047
+ for (const sub of requiredSubs) {
1048
+ if (!section.includes(sub)) {
1049
+ errors.push(`${heading}: missing ${sub}`);
1050
+ }
1051
+ }
1052
+
1053
+ const checkboxes = section.match(/- \[[ xX]\]/gi) || [];
1054
+ const unchecked = checkboxes.filter((c) => /\[ \]/i.test(c));
1055
+ if (checkboxes.length > 0 && unchecked.length > 0) {
1056
+ errors.push(`${heading} Risk Checklist: ${unchecked.length} unchecked item(s)`);
1057
+ }
1058
+
1059
+ if (PLACEHOLDER_RE.test(section)) {
1060
+ errors.push(`${heading}: contains {{placeholders}} — fill with real data`);
1061
+ }
1062
+
1063
+ return errors;
1064
+ }
1065
+
1066
+ const LOOP_NAMES = '(clarify|build|verify)';
1067
+ const LOOP_YAML_RE = new RegExp(`^\\s*loop:\\s*${LOOP_NAMES}\\s*$`, 'im');
1068
+ const ITERATION_YAML_RE = /^\s*iteration:\s*(\d+)\s*$/im;
1069
+
1070
+ /**
1071
+ * @param {string} projectDir
1072
+ * @returns {number|null}
1073
+ */
1074
+ function readIteration(projectDir) {
1075
+ const statePath = path.join(projectDir, 'docs/project-state.md');
1076
+ if (!fs.existsSync(statePath)) return null;
1077
+ const content = fs.readFileSync(statePath, 'utf8');
1078
+ const m = content.match(ITERATION_YAML_RE);
1079
+ if (!m) return null;
1080
+ const n = Number.parseInt(m[1], 10);
1081
+ return Number.isFinite(n) && n >= 1 && n <= 4 ? n : null;
1082
+ }
1083
+
1084
+ /**
1085
+ * @param {string} projectDir
1086
+ * @returns {'clarify'|'build'|'verify'|null}
1087
+ */
1088
+ function readLoop(projectDir) {
1089
+ const statePath = path.join(projectDir, 'docs/project-state.md');
1090
+ if (!fs.existsSync(statePath)) return null;
1091
+ const content = fs.readFileSync(statePath, 'utf8');
1092
+ const m = content.match(LOOP_YAML_RE);
1093
+ return m ? /** @type {'clarify'|'build'|'verify'} */ (m[1].toLowerCase()) : null;
1094
+ }
1095
+
1096
+ /** @deprecated use readLoop — maps build→development for legacy test imports only */
1097
+ function readStage(projectDir) {
1098
+ const loop = readLoop(projectDir);
1099
+ if (loop === 'build') return 'development';
1100
+ if (loop === 'verify') return 'ui_review';
1101
+ return 'pm';
1102
+ }
1103
+
1104
+ function readGatePending(projectDir) {
1105
+ return null;
1106
+ }
1107
+
1108
+ /**
1109
+ * Validate Artist Skill Preflight section (Phase 2 / production only).
1110
+ * @param {string} content
1111
+ */
1112
+ function validateArtistSkillPreflight(content) {
1113
+ const errors = [];
1114
+ const start = content.indexOf(SKILL_PREFLIGHT_HEADING);
1115
+ if (start === -1) {
1116
+ return [`Missing "${SKILL_PREFLIGHT_HEADING}" section — fill before Phase 2 STOP`];
1117
+ }
1118
+ const after = content.slice(start + SKILL_PREFLIGHT_HEADING.length);
1119
+ const nextHeading = after.search(/\n##\s+/);
1120
+ const section = nextHeading === -1 ? after : after.slice(0, nextHeading);
1121
+
1122
+ // Each non-header data row: | Skill | 已读 | 关键决策摘要 |
1123
+ const rows = [];
1124
+ for (const line of section.split('\n')) {
1125
+ const trimmed = line.trim();
1126
+ if (!trimmed.startsWith('|')) continue;
1127
+ if (/^\|\s*---/.test(trimmed)) continue;
1128
+ if (/^\|\s*Skill/i.test(trimmed)) continue;
1129
+
1130
+ const parts = trimmed
1131
+ .split('|')
1132
+ .map((p) => p.trim())
1133
+ .filter((_, i, arr) => i > 0 && i < arr.length - 1);
1134
+ if (parts.length < 3) continue;
1135
+ rows.push({ skill: parts[0], read: parts[1], summary: parts[2] });
1136
+ }
1137
+
1138
+ if (rows.length === 0) {
1139
+ errors.push(`${SKILL_PREFLIGHT_HEADING}: table has no data rows`);
1140
+ return errors;
1141
+ }
1142
+ for (const row of rows) {
1143
+ if (!isReadChecked(row.read)) {
1144
+ errors.push(`${SKILL_PREFLIGHT_HEADING}: "${row.skill}" — mark Read column (✓) after reading`);
1145
+ }
1146
+ if (!row.summary || PLACEHOLDER_RE.test(row.summary) || row.summary.length < MIN_TAKEAWAY_LEN) {
1147
+ errors.push(
1148
+ `${SKILL_PREFLIGHT_HEADING}: "${row.skill}" — add a concrete decision summary (≥${MIN_TAKEAWAY_LEN} chars, no {{placeholders}})`,
1149
+ );
1150
+ }
1151
+ }
1152
+ return errors;
1153
+ }
1154
+
1155
+ /**
1156
+ * Static runtime-contract gate for Developer: the recording/debug harness needs
1157
+ * game/ to keep exposing its hooks. Feature-detected — silently skipped if the
1158
+ * harness scanner is absent, and never hard-fails the hook itself on scanner error.
1159
+ * @param {string} projectDir
1160
+ * @returns {string[]}
1161
+ */
1162
+ function validateDeveloperRuntimeContract(projectDir) {
1163
+ const pending = path.join(projectDir, 'game', '.playcraft-scaffold-pending');
1164
+ if (fs.existsSync(pending)) return [];
1165
+
1166
+ const libPath = path.join(
1167
+ projectDir,
1168
+ 'skills/playable-verify-ui/scripts/lib/runtime_contract.js',
1169
+ );
1170
+ if (!fs.existsSync(libPath)) return [];
1171
+ try {
1172
+ const { scanRuntimeContract } = require(libPath);
1173
+ const res = scanRuntimeContract(projectDir);
1174
+ if (!res.ok) {
1175
+ return res.missing.map((m) => `runtime contract broken — ${m}`);
1176
+ }
1177
+ } catch {
1178
+ // Scanner failure must not block an otherwise-valid STOP.
1179
+ return [];
1180
+ }
1181
+ return [];
1182
+ }
1183
+
1184
+ /**
1185
+ * @param {string} projectDir
1186
+ * @param {'technical-artist' | 'developer' | 'artist'} role
1187
+ */
1188
+ function validateNonRegression(projectDir, iteration) {
1189
+ const errors = [];
1190
+ if (!iteration || iteration <= 1) return errors;
1191
+ const archived = readState(projectDir).archivedRings || {};
1192
+ if (Object.keys(archived).length === 0) {
1193
+ errors.push(`Non-regression: archivedRings empty while iteration=${iteration}`);
1194
+ return errors;
1195
+ }
1196
+ for (let ring = 1; ring < iteration; ring += 1) {
1197
+ if (!(ring in archived) && !(String(ring) in archived)) {
1198
+ errors.push(`Non-regression: ring ${ring} not archived`);
1199
+ }
1200
+ }
1201
+ return errors;
1202
+ }
1203
+
1204
+ /**
1205
+ * slice-axis-v1: roles that EXECUTE (implement/verify) a dispatched slice. If one of
1206
+ * these is stopped on a slice it does not own, that is a dispatch mismatch (the
1207
+ * orchestrator spawned the wrong executor) and STOP is blocked. Non-executor roles
1208
+ * (game-designer seeds the queue, reviewer/skill-selector) may legitimately STOP at a
1209
+ * handoff point while another role's slice is pending, so they are not mismatch-gated.
1210
+ * Knife 4 only wires developer; TA/artist slice execution can be added later.
1211
+ */
1212
+ const SLICE_EXECUTOR_ROLES = new Set(['developer']);
1213
+
1214
+ /** The active slice object, or null when none / drained. */
1215
+ function getActiveSliceFor(projectDir) {
1216
+ if (!stateExists(projectDir)) return null;
1217
+ try {
1218
+ const ss = readState(projectDir).sliceState || {};
1219
+ if (!ss.activeSliceId || ss.activeSliceId === 'none') return null;
1220
+ return (ss.slices || []).find((s) => s.sliceId === ss.activeSliceId) || null;
1221
+ } catch {
1222
+ return null;
1223
+ }
1224
+ }
1225
+
1226
+ function hasUnresolvedActiveSlice(projectDir) {
1227
+ const active = getActiveSliceFor(projectDir);
1228
+ return !!active && !isSliceResolved(active);
1229
+ }
1230
+
1231
+ /**
1232
+ * slice-axis-v1: derive the dispatched role from the active slice when there is no
1233
+ * explicit role marker on the hook input (top-level `claude -p` has no
1234
+ * `subagent_type` / PLAYCRAFT_STOP). The dispatched role IS the owner of the
1235
+ * active, UNRESOLVED slice — this guarantees an unverified (or failed-but-unrouted)
1236
+ * slice cannot slip past STOP even without subagent context. Returns null when the
1237
+ * active slice is resolved / absent, so those states fall back to normal detection.
1238
+ */
1239
+ function roleFromActiveSlice(projectDir) {
1240
+ const active = getActiveSliceFor(projectDir);
1241
+ if (!active || isSliceResolved(active)) return null;
1242
+ return normalizeRole(active.role || 'developer') || 'developer';
1243
+ }
1244
+
1245
+ function validateActiveSliceResolved(projectDir, role) {
1246
+ const active = getActiveSliceFor(projectDir);
1247
+ if (!active) return [];
1248
+ if (active.role && active.role !== role) return [];
1249
+ if (isSliceResolved(active)) return [];
1250
+ if (active.status === 'failed') {
1251
+ return [
1252
+ `slice "${active.sliceId}" is "failed" but has no route target — run \`npm run slice:set -- status=failed route=<role> reason="<failKey>"\` so the orchestrator has a precise recovery target. A failed slice without routeTo.role + routeTo.reason is unresolved.`,
1253
+ ];
1254
+ }
1255
+ const buildHint =
1256
+ active.role === 'developer' || !active.role
1257
+ ? '`npm run build` (writes validate/build.json)'
1258
+ : `\`npm run ${active.verifier || 'verify:build'}\``;
1259
+ return [
1260
+ `slice "${active.sliceId}" is "${active.status}" — before STOP run ${buildHint}, then \`npm run slice:set -- status=verified verification="build.json ok"\` on pass, or \`npm run slice:set -- status=failed route=<role> reason="..."\` to route. The active slice must be verified/failed/routed, not left ${active.status}.`,
1261
+ ];
1262
+ }
1263
+
1264
+ /**
1265
+ * slice-axis-v1: after a slice is resolved the queue may drain (activeSliceId=none).
1266
+ * The stopping role must not fall back to legacy ring STOP (e.g. review-report.md) when
1267
+ * it just wrote a structured verdict via slice:set — lastResolvedForStop bridges drain → STOP.
1268
+ */
1269
+ function validateResolvedSliceStop(projectDir, role) {
1270
+ if (!stateExists(projectDir)) return null;
1271
+ try {
1272
+ const ss = readState(projectDir).sliceState || {};
1273
+ const marker = ss.lastResolvedForStop;
1274
+ if (!marker || marker.role !== role || !marker.sliceId) return null;
1275
+ const slice = (ss.slices || []).find((s) => s.sliceId === marker.sliceId);
1276
+ if (slice && isSliceResolved(slice)) {
1277
+ if (role === 'developer' && slice.status === 'verified') {
1278
+ const buildErrors = validateBuildArtifactForSlice(projectDir, slice);
1279
+ if (buildErrors.length) return buildErrors;
1280
+ }
1281
+ return [];
1282
+ }
1283
+ } catch {
1284
+ return null;
1285
+ }
1286
+ return null;
1287
+ }
1288
+
1289
+ function validateRole(projectDir, role) {
1290
+ // slice-axis-v1: when the stopping role OWNS an unresolved active slice, the slice
1291
+ // state machine is the ONLY STOP gate — checked FIRST, ahead of every role-specific
1292
+ // branch (reviewer/game-designer/skill-selector included), so a slice owned by ANY
1293
+ // role (e.g. a future game-designer-owned slice) goes through the slice gate, not
1294
+ // the legacy role STOP. Ring-era intake ceremony (developer-log Read✓/takeaway,
1295
+ // plan, runtime-contract dup, ring-1 self-gate) is bypassed — verify:slice already
1296
+ // judges the runtime contract semantically. With no unresolved active slice we fall
1297
+ // through to the legacy ring flow, so existing ring:loop projects and tests are
1298
+ // unaffected.
1299
+ const sliceOwner = roleFromActiveSlice(projectDir);
1300
+ if (sliceOwner) {
1301
+ if (sliceOwner === role) {
1302
+ return validateActiveSliceResolved(projectDir, role);
1303
+ }
1304
+ // dispatch mismatch (hardening): an EXECUTOR role was stopped on a slice it does
1305
+ // not own → the orchestrator spawned the wrong executor. Block with a precise
1306
+ // re-dispatch hint. Non-executor roles fall through (legitimate handoff STOP).
1307
+ if (SLICE_EXECUTOR_ROLES.has(role)) {
1308
+ const active = getActiveSliceFor(projectDir);
1309
+ return [
1310
+ `dispatch mismatch — active slice "${active?.sliceId}" is owned by "${sliceOwner}", but the stopping role is "${role}". Only @${sliceOwner} may resolve this slice; re-dispatch to @${sliceOwner} (or fix sliceState.slices[].role).`,
1311
+ ];
1312
+ }
1313
+ }
1314
+
1315
+ const resolvedSliceStop = validateResolvedSliceStop(projectDir, role);
1316
+ if (resolvedSliceStop !== null) return resolvedSliceStop;
1317
+
1318
+ if (role === 'reviewer') {
1319
+ return validateReviewerStop(projectDir);
1320
+ }
1321
+ if (role === 'game-designer') {
1322
+ return validateGameDesignerStop(projectDir);
1323
+ }
1324
+ if (role === 'skill-selector') {
1325
+ return validateSkillSelectorStop(projectDir);
1326
+ }
1327
+
1328
+ const cfg = ROLE_LOG[role];
1329
+ const iteration = readIteration(projectDir) || 1;
1330
+ const loop = readLoop(projectDir);
1331
+ const logPath = path.join(projectDir, cfg.log);
1332
+ const requiredDocs = requiredDocsForRole(role, iteration);
1333
+
1334
+ if (!fs.existsSync(logPath)) {
1335
+ return [
1336
+ `${cfg.label}: create ${cfg.log} from templates/${path.basename(cfg.log).replace('.md', '.template.md')} before STOP.`,
1337
+ ];
1338
+ }
1339
+
1340
+ const content = fs.readFileSync(logPath, 'utf8');
1341
+ const regressionErrors = validateNonRegression(projectDir, iteration);
1342
+ const contextErrors = validateHandoffContext(projectDir, role);
1343
+
1344
+ if (role === 'artist') {
1345
+ if (iteration >= 3 && loop === 'build') {
1346
+ return [...validateArtistSkillPreflight(content), ...regressionErrors, ...contextErrors];
1347
+ }
1348
+ if (iteration >= 3 && loop === 'verify') {
1349
+ return [...validateArtistGate2b(projectDir), ...regressionErrors, ...contextErrors];
1350
+ }
1351
+ return [...regressionErrors, ...contextErrors];
1352
+ }
1353
+
1354
+ const intakeErrors =
1355
+ requiredDocs.length > 0 ? validateIntakeContent(content, requiredDocs) : [];
1356
+ const planErrors = validatePlan(content, role, loop);
1357
+ const contractErrors =
1358
+ role === 'developer' && loop === 'build'
1359
+ ? validateDeveloperRuntimeContract(projectDir)
1360
+ : [];
1361
+
1362
+ if (role === 'developer' && loop === 'build') {
1363
+ const checkpointErrors =
1364
+ iteration >= 2 ? validateDeveloperCheckpoints(projectDir, content) : [];
1365
+ const sliceErrors = validateActiveSliceResolved(projectDir, role);
1366
+ let ring1SelfGateErrors = [];
1367
+ // When a slice is active, slice verification supersedes the legacy ring-1
1368
+ // whole-project self-gate (which is what the experiment showed lets semantic
1369
+ // failures escape). Fall back to the ring-1 self-gate only with no active slice.
1370
+ if (iteration === 1 && sliceErrors.length === 0 && !hasUnresolvedActiveSlice(projectDir)) {
1371
+ const ring1 = validateDeveloperRing1SelfGate(projectDir, content, readHandoff(projectDir));
1372
+ ring1SelfGateErrors = ring1.errors;
1373
+ for (const w of ring1.warnings ?? []) {
1374
+ process.stderr.write(`[playcraft-workflow-stop] advisory: ${w}\n`);
1375
+ }
1376
+ }
1377
+ return [...regressionErrors, ...contextErrors, ...intakeErrors, ...planErrors, ...checkpointErrors, ...contractErrors, ...sliceErrors, ...ring1SelfGateErrors];
1378
+ }
1379
+
1380
+ if (role === 'technical-artist' && iteration >= 4) {
1381
+ const taErrors = [];
1382
+ const handoff = readHandoff(projectDir);
1383
+ const next = handoff.next_orchestrator_action || '';
1384
+ if (/reviewer ui_diff/i.test(next) && !/developer/i.test(next)) {
1385
+ taErrors.push(
1386
+ 'TA STOP must route to @developer (Ring 4 production integration) before @reviewer ui_diff',
1387
+ );
1388
+ }
1389
+ return [...regressionErrors, ...contextErrors, ...intakeErrors, ...planErrors, ...taErrors];
1390
+ }
1391
+
1392
+ return [...regressionErrors, ...contextErrors, ...intakeErrors, ...planErrors, ...contractErrors];
1393
+ }
1394
+
1395
+ /** Selected atoms that must be linked into .claude/skills/ (excludes bundled playcraft-* skills). */
1396
+ function readSelectedAtomsForLink(projectDir) {
1397
+ const jsonPath = path.join(projectDir, 'docs/atom-tree.json');
1398
+ if (!fs.existsSync(jsonPath)) return [];
1399
+ try {
1400
+ const tree = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
1401
+ const atoms = Array.isArray(tree?.meta?.selectedAtoms) ? tree.meta.selectedAtoms : [];
1402
+ return atoms
1403
+ .map((a) => (typeof a === 'string' ? a.trim() : ''))
1404
+ .filter((a) => a && !a.startsWith('{{') && !a.startsWith('playcraft-'));
1405
+ } catch {
1406
+ return [];
1407
+ }
1408
+ }
1409
+
1410
+ /** Extract a markdown section body from `heading` up to the next same-or-higher-level heading. */
1411
+ function extractMarkdownSection(content, heading) {
1412
+ const start = content.indexOf(heading);
1413
+ if (start === -1) return null;
1414
+ const level = (heading.match(/^#+/) || ['##'])[0].length;
1415
+ const after = content.slice(start + heading.length);
1416
+ const lines = after.split('\n');
1417
+ const body = [];
1418
+ for (let i = 1; i < lines.length; i += 1) {
1419
+ const m = lines[i].match(/^(#+)\s/);
1420
+ if (m && m[1].length <= level) break;
1421
+ body.push(lines[i]);
1422
+ }
1423
+ return body.join('\n');
1424
+ }
1425
+
1426
+ /** Count filled (non-template, non-header, non-separator) markdown table data rows. */
1427
+ function countFilledTableRows(section) {
1428
+ let count = 0;
1429
+ for (const line of section.split('\n')) {
1430
+ const trimmed = line.trim();
1431
+ if (!trimmed.startsWith('|')) continue;
1432
+ if (/^\|[\s|:-]+\|?$/.test(trimmed)) continue;
1433
+ if (/selectedAtomRef|Read path|怎么用|Asset contract path|Tier \/ Source/.test(trimmed)) continue;
1434
+ if (PLACEHOLDER_RE.test(trimmed)) continue;
1435
+ count += 1;
1436
+ }
1437
+ return count;
1438
+ }
1439
+
1440
+ /**
1441
+ * Shared skill-link + Skill Preflight + placeholder materialization evidence gate.
1442
+ * Applied at every ring's Developer build STOP so "use placeholders / implement per skill"
1443
+ * is enforced from ring 1 onward, not just documented.
1444
+ * @param {string} projectDir
1445
+ * @param {string} content developer-log.md content
1446
+ * @returns {string[]}
1447
+ */
1448
+ function validateSkillAndPlaceholderEvidence(projectDir, content) {
1449
+ const errors = [];
1450
+ const atoms = readSelectedAtomsForLink(projectDir);
1451
+
1452
+ // 1. Skill link evidence: selected atoms materialized under .claude/skills/
1453
+ const skillsDir = path.join(projectDir, '.claude/skills');
1454
+ const unlinked = atoms.filter((atom) => !fs.existsSync(path.join(skillsDir, atom)));
1455
+ if (unlinked.length > 0) {
1456
+ errors.push(
1457
+ `Skill link: selected atom skill(s) not linked into .claude/skills/: ${unlinked.join(', ')} — re-save docs/atom-tree.json (auto-links) or run playcraft skills link --from-atom-tree --prune`,
1458
+ );
1459
+ }
1460
+
1461
+ // 2. Skill Preflight filled (one non-template row per atom)
1462
+ const preflight = extractMarkdownSection(content, '## Skill Preflight');
1463
+ if (!preflight || countFilledTableRows(preflight) < Math.max(1, atoms.length)) {
1464
+ errors.push(
1465
+ 'Skill Preflight: developer-log.md § Skill Preflight is not filled — add one non-template row per selected atom (read .claude/skills/<atom>/SKILL.md: what it provides + how you use it)',
1466
+ );
1467
+ }
1468
+
1469
+ // 3. Placeholder materialization evidence (placeholder-first, not code-draw-only)
1470
+ const placeholder = extractMarkdownSection(content, '### Placeholder materialization');
1471
+ if (!placeholder) {
1472
+ errors.push(
1473
+ 'Placeholder: developer-log.md § Placeholder materialization missing — fill the materialization table + "Placeholder run result" (run npm run placeholder:gen)',
1474
+ );
1475
+ } else {
1476
+ if (countFilledTableRows(placeholder) < 1) {
1477
+ errors.push(
1478
+ 'Placeholder: § Placeholder materialization table has no filled row — record each placeholder asset (path / Tier / size / generated) before STOP; Ring 1 onward forbids pure code-drawn visuals',
1479
+ );
1480
+ }
1481
+ if (!/Placeholder run result[^\n]*\b(success|partial)\b/i.test(placeholder)) {
1482
+ errors.push(
1483
+ 'Placeholder: § Placeholder materialization "Placeholder run result" must be success or partial (run npm run placeholder:gen and record the outcome)',
1484
+ );
1485
+ }
1486
+ }
1487
+
1488
+ // 4. verify:placeholders recorded as pass/OK in developer-log
1489
+ if (!/verify:placeholders[\s\S]{0,120}\b(OK|pass|passed)\b/i.test(content)) {
1490
+ errors.push(
1491
+ 'Placeholder: run npm run verify:placeholders and log pass/OK in developer-log § Dev self-check',
1492
+ );
1493
+ }
1494
+
1495
+ return errors;
1496
+ }
1497
+
1498
+ /**
1499
+ * The recorded devUrl must carry the `?debug=1` flag so opening it shows the
1500
+ * on-screen one-click clear (debug) button by default. Only flags when a devUrl
1501
+ * http(s) link is actually present (no false positive when none recorded yet).
1502
+ * @param {string} content developer-log.md content
1503
+ * @returns {string[]}
1504
+ */
1505
+ function validateDevUrlDebugFlag(content) {
1506
+ const errors = [];
1507
+ const m = content.match(/devUrl[^\n]*?(https?:\/\/[^\s)<>"']+)/i);
1508
+ if (m && !/[?&]debug=1\b/.test(m[1])) {
1509
+ errors.push(
1510
+ `devUrl: recorded devUrl (${m[1]}) must include ?debug=1 — every dev url is opened with debug=1 so the one-click clear debug button shows by default`,
1511
+ );
1512
+ }
1513
+ return errors;
1514
+ }
1515
+
1516
+ /**
1517
+ * Ring 1 build STOP: Developer self-gates objective DoD via scripts (no Reviewer invoke).
1518
+ * @param {string} projectDir
1519
+ * @param {string} content
1520
+ * @param {Record<string, string>} handoff
1521
+ * @returns {string[]}
1522
+ */
1523
+ function validateDeveloperRing1SelfGate(projectDir, content, handoff) {
1524
+ const errors = [];
1525
+ const warnings = [];
1526
+ const nextAction = handoff.next_orchestrator_action || '';
1527
+ if (!/ring:gate/i.test(nextAction)) {
1528
+ errors.push(
1529
+ 'Ring 1 build STOP: next_orchestrator_action must route orchestrator to npm run ring:gate -- --iteration 1 after per-slice verify:slice + terminal slice queue (ring:gate aggregates; it does not replace verify:slice)',
1530
+ );
1531
+ }
1532
+ if (!/\bverify:contract\b[\s\S]{0,120}\b(OK|pass|passed|satisfied)\b/i.test(content)) {
1533
+ errors.push(
1534
+ 'Ring 1 build STOP: run npm run verify:contract and log pass/OK in developer-log § Dev self-check',
1535
+ );
1536
+ }
1537
+ if (!/\bverify:lifecycle\b[\s\S]{0,120}\b(OK|pass|passed)\b/i.test(content)) {
1538
+ errors.push(
1539
+ 'Ring 1 build STOP: run npm run verify:lifecycle and log pass/OK in developer-log § Dev self-check',
1540
+ );
1541
+ }
1542
+ if (!/\bverify:firstwin\b[\s\S]{0,120}\b(OK|pass|passed)\b/i.test(content)) {
1543
+ errors.push(
1544
+ 'Ring 1 build STOP: run npm run verify:firstwin and log pass/OK in developer-log § Dev self-check',
1545
+ );
1546
+ }
1547
+ if (!/\bverify:placeholders\b[\s\S]{0,120}\b(OK|pass|passed)\b/i.test(content)) {
1548
+ errors.push(
1549
+ 'Ring 1 build STOP: run npm run verify:placeholders and log pass/OK in developer-log § Dev self-check',
1550
+ );
1551
+ }
1552
+ errors.push(...validateSkillAndPlaceholderEvidence(projectDir, content));
1553
+ errors.push(...validateDevUrlDebugFlag(content));
1554
+ const devStatus = readState(projectDir).devPreview?.devStatus;
1555
+ if (String(devStatus || '').toLowerCase() !== 'ready') {
1556
+ errors.push('Ring 1 build STOP: set devStatus: ready (npm run state:set -- devStatus=ready) before STOP');
1557
+ }
1558
+ const scenesDir = path.join(projectDir, 'game/scenes');
1559
+ if (fs.existsSync(scenesDir)) {
1560
+ const SCENE_LINE_WARN = 400;
1561
+ for (const name of fs.readdirSync(scenesDir)) {
1562
+ if (!/\.(ts|js)$/.test(name)) continue;
1563
+ const abs = path.join(scenesDir, name);
1564
+ if (!fs.statSync(abs).isFile()) continue;
1565
+ const lineCount = fs.readFileSync(abs, 'utf8').split('\n').length;
1566
+ if (lineCount > SCENE_LINE_WARN) {
1567
+ warnings.push(
1568
+ `Scene file ${name} has ${lineCount} lines (>${SCENE_LINE_WARN}) — split into game/gameplay/ submodules (hard target ≤350 lines/scene)`,
1569
+ );
1570
+ }
1571
+ }
1572
+ }
1573
+ return { errors, warnings };
1574
+ }
1575
+
1576
+ /**
1577
+ * Developer STOP checkpoint — 4 items:
1578
+ * 0. Feature Checklist existence (developer-log.md § Feature Checklist or § Feature Review)
1579
+ * 1. Delivery Checklist coverage (implementation-plan.md § 8 vs developer-log.md § Feature Review)
1580
+ * 2. Scaffold asset adoption (scaffold-manifest.md vs developer-log.md § Read / Load Log) + scaffold decision documented
1581
+ * 3. Upstream blockers closed (no open blocker without resolution)
1582
+ * @param {string} projectDir
1583
+ * @param {string} devLogContent
1584
+ */
1585
+ function validateDeveloperCheckpoints(projectDir, devLogContent) {
1586
+ const errors = [];
1587
+
1588
+ // --- 0. Feature Checklist existence ---
1589
+ const hasFeatureChecklist =
1590
+ devLogContent.includes('## Feature Checklist') ||
1591
+ devLogContent.includes('## Feature Review');
1592
+ const hasFeatureItem =
1593
+ /^\s*[-*]\s*\[[ xX]\]/.test(devLogContent.split('\n').find((l) => l.includes('## Feature')));
1594
+ if (!hasFeatureChecklist) {
1595
+ errors.push(
1596
+ 'Feature Checklist: missing "## Feature Checklist" or "## Feature Review" section in developer-log.md — create it from implementation-plan.md features before claiming readiness',
1597
+ );
1598
+ }
1599
+
1600
+ // --- 1. Delivery Checklist coverage ---
1601
+ const implPlanPath = path.join(projectDir, 'docs/implementation-plan.md');
1602
+ if (fs.existsSync(implPlanPath)) {
1603
+ const implPlan = fs.readFileSync(implPlanPath, 'utf8');
1604
+ const checklistStart = implPlan.indexOf('## 8. Delivery Checklist');
1605
+ if (checklistStart !== -1) {
1606
+ const checklistSection = implPlan.slice(checklistStart);
1607
+ // Extract unchecked items: lines starting with "- [ ]"
1608
+ const uncheckedItems = [];
1609
+ for (const line of checklistSection.split('\n')) {
1610
+ const trimmed = line.trim();
1611
+ if (/^- \[ \]/.test(trimmed)) {
1612
+ // Extract item text (strip "- [ ] " prefix and "— source: ..." suffix)
1613
+ const itemText = trimmed
1614
+ .replace(/^- \[ \]\s*/, '')
1615
+ .replace(/\s*—\s*source:.*$/, '')
1616
+ .trim();
1617
+ uncheckedItems.push(itemText);
1618
+ }
1619
+ }
1620
+ if (uncheckedItems.length > 0) {
1621
+ // Check if Feature Review section exists and has content
1622
+ const featureReviewStart = devLogContent.indexOf('## Feature Review');
1623
+ if (featureReviewStart === -1) {
1624
+ errors.push(
1625
+ `Delivery Checklist: ${uncheckedItems.length} unchecked item(s) — add "## Feature Review" section to developer-log.md and mark each item done/partial(reason)`,
1626
+ );
1627
+ } else {
1628
+ // Items exist but are unchecked — developer must address them
1629
+ errors.push(
1630
+ `Delivery Checklist: ${uncheckedItems.length} unchecked item(s) in implementation-plan.md § 8 — mark each [x] or add partial/blocked note in developer-log.md § Feature Review:\n ${uncheckedItems.slice(0, 5).join('\n ')}${uncheckedItems.length > 5 ? `\n ... and ${uncheckedItems.length - 5} more` : ''}`,
1631
+ );
1632
+ }
1633
+ }
1634
+ }
1635
+ }
1636
+
1637
+ // --- 2. Scaffold asset adoption + decision documentation ---
1638
+ // Check scaffold decision is documented in Skill Preflight (whether or not scaffold was executed)
1639
+ const skillPreflightStart = devLogContent.indexOf('## Skill Preflight');
1640
+ const hasScaffoldDecision =
1641
+ devLogContent.toLowerCase().includes('scaffold-candidate') ||
1642
+ devLogContent.toLowerCase().includes('skip-with-reason') ||
1643
+ devLogContent.toLowerCase().includes('scaffold decision') ||
1644
+ devLogContent.toLowerCase().includes('本轮 scaffold');
1645
+ if (skillPreflightStart !== -1 && !hasScaffoldDecision) {
1646
+ errors.push(
1647
+ 'Scaffold decision: no scaffold decision found in developer-log.md § Skill Preflight — document per-atom decision (scaffold-candidate / skip-with-reason) and add a summary line (e.g. "本轮 scaffold: none, because ...")',
1648
+ );
1649
+ }
1650
+
1651
+ const manifestPath = path.join(projectDir, 'docs/scaffold-manifest.md');
1652
+ if (fs.existsSync(manifestPath)) {
1653
+ const manifest = fs.readFileSync(manifestPath, 'utf8');
1654
+ // Count scaffold entries (lines with asset paths, e.g. "- game/assets/...")
1655
+ const scaffoldAssets = [];
1656
+ for (const line of manifest.split('\n')) {
1657
+ const trimmed = line.trim();
1658
+ if (/^-\s+game\/assets\//.test(trimmed) || /^-\s+`game\/assets\//.test(trimmed)) {
1659
+ scaffoldAssets.push(trimmed.replace(/^-\s+`?/, '').replace(/`?$/, '').trim());
1660
+ }
1661
+ }
1662
+ if (scaffoldAssets.length > 0) {
1663
+ // Check if developer-log has any load evidence
1664
+ const hasLoadLog =
1665
+ devLogContent.includes('## Read / Load Log') ||
1666
+ devLogContent.includes('this.load.') ||
1667
+ devLogContent.includes('load.image') ||
1668
+ devLogContent.includes('load.atlas') ||
1669
+ devLogContent.includes('load.audio');
1670
+ const hasAdoptionNote =
1671
+ devLogContent.toLowerCase().includes('scaffold') &&
1672
+ (devLogContent.toLowerCase().includes('unused') ||
1673
+ devLogContent.toLowerCase().includes('replaced') ||
1674
+ devLogContent.toLowerCase().includes('not used') ||
1675
+ devLogContent.toLowerCase().includes('adoption'));
1676
+ if (!hasLoadLog && !hasAdoptionNote) {
1677
+ errors.push(
1678
+ `Scaffold asset adoption: ${scaffoldAssets.length} scaffold asset(s) found in docs/scaffold-manifest.md but no load evidence or adoption note in developer-log.md — add load records or explain why assets were not used`,
1679
+ );
1680
+ }
1681
+ }
1682
+ }
1683
+
1684
+ // --- 3. Upstream blockers closed ---
1685
+ const blockersStart = devLogContent.indexOf('## Upstream blockers');
1686
+ if (blockersStart !== -1) {
1687
+ const after = devLogContent.slice(blockersStart + '## Upstream blockers'.length);
1688
+ const nextHeading = after.search(/\n##\s+/);
1689
+ const section = nextHeading === -1 ? after : after.slice(0, nextHeading);
1690
+ const openBlockers = [];
1691
+ for (const line of section.split('\n')) {
1692
+ const trimmed = line.trim();
1693
+ if (!trimmed.startsWith('|')) continue;
1694
+ if (/^\|\s*---/.test(trimmed)) continue;
1695
+ if (/^\|\s*(Blocker|blocker|Item|item|Path)\b/i.test(trimmed)) continue;
1696
+ const parts = trimmed
1697
+ .split('|')
1698
+ .map((p) => p.trim())
1699
+ .filter((_, i, arr) => i > 0 && i < arr.length - 1);
1700
+ // Template: | Path(0) | Issue(1) | Resolution(2) | routeTo(3) | Status(4) |
1701
+ if (parts.length < 5) continue;
1702
+ const status = (parts[4] || '').toLowerCase();
1703
+ const resolution = (parts[2] || '').trim();
1704
+ // Valid resolutions that genuinely close a blocker:
1705
+ // fixed, replaced(<reason>), escalated — anything else is a note, not a resolution.
1706
+ const isValidResolution =
1707
+ /^(fixed|replaced|escalated)\b/i.test(resolution) ||
1708
+ resolution === '—' ||
1709
+ resolution === '-' ||
1710
+ resolution === '';
1711
+ if (status === 'open' || !isValidResolution) {
1712
+ openBlockers.push(parts[0]);
1713
+ }
1714
+ }
1715
+ if (openBlockers.length > 0) {
1716
+ errors.push(
1717
+ `Upstream blockers: ${openBlockers.length} open blocker(s) without resolution — set resolution to fixed/replaced(reason)/escalated:\n ${openBlockers.join('\n ')}`,
1718
+ );
1719
+ }
1720
+ }
1721
+
1722
+ // --- 4. Skill link + Skill Preflight + placeholder materialization evidence (ring 2+ parity with ring 1) ---
1723
+ errors.push(...validateSkillAndPlaceholderEvidence(projectDir, devLogContent));
1724
+
1725
+ // --- 5. devUrl carries ?debug=1 (one-click clear debug button shown by default) ---
1726
+ errors.push(...validateDevUrlDebugFlag(devLogContent));
1727
+
1728
+ return errors;
1729
+ }
1730
+
1731
+ /** @param {string} projectDir */
1732
+ async function validateWorkflowState(projectDir) {
1733
+ const result = await diagnose(projectDir, { strict: 'stop' });
1734
+ return result.ok ? [] : result.errors;
1735
+ }
1736
+
1737
+ /** @param {string} projectDir */
1738
+ function resolveProjectDir(projectDir) {
1739
+ if (!stateExists(projectDir)) {
1740
+ return { skip: true, reason: 'no docs/project-state.json or .md' };
1741
+ }
1742
+ return { skip: false };
1743
+ }
1744
+
1745
+ function emitBlock(reason) {
1746
+ const payload = { decision: 'block', reason };
1747
+ process.stdout.write(`${JSON.stringify(payload)}\n`);
1748
+ process.stderr.write(`[playcraft-workflow-stop] ${reason}\n`);
1749
+ process.exit(2);
1750
+ }
1751
+
1752
+ async function readStdin() {
1753
+ const chunks = [];
1754
+ for await (const chunk of process.stdin) {
1755
+ chunks.push(chunk);
1756
+ }
1757
+ return Buffer.concat(chunks).toString('utf8');
1758
+ }
1759
+
1760
+ export {
1761
+ detectRole,
1762
+ roleFromActiveSlice,
1763
+ validateActiveSliceResolved,
1764
+ validateResolvedSliceStop,
1765
+ hasUnresolvedActiveSlice,
1766
+ isSliceResolved,
1767
+ parseUpstreamIntake,
1768
+ validateIntakeContent,
1769
+ validatePlan,
1770
+ validateArtistSkillPreflight,
1771
+ validateDeveloperRuntimeContract,
1772
+ validateRole,
1773
+ validateReviewerStop,
1774
+ validateGameDesignerStop,
1775
+ validateSkillSelectorStop,
1776
+ validateUiDiffVisualRubric,
1777
+ validatePlayableSeedDesign,
1778
+ validateHandoffContext,
1779
+ validateGameDesignerSeedContext,
1780
+ validateGameDesignerSliceQueueSeed,
1781
+ validateSkillsMatchSliceRefs,
1782
+ validateDesignFusionSection,
1783
+ validateReviewerHandoff,
1784
+ validateReviewReportDoD,
1785
+ readHandoff,
1786
+ readIteration,
1787
+ readLoop,
1788
+ readStage,
1789
+ readGatePending,
1790
+ requiredDocsForRole,
1791
+ validateWorkflowState,
1792
+ DEV_REQUIRED_DOCS_BY_RING,
1793
+ TA_REQUIRED_DOCS_BY_RING,
1794
+ ARTIST_REQUIRED_DOCS,
1795
+ DESIGNER_REQUIRED_DOCS_BY_RING,
1796
+ reviewDoDKeysForRing,
1797
+ PLAN_HEADING,
1798
+ PLAN_REQUIRED_SUBS,
1799
+ ROLE_LOG,
1800
+ validateSkillAndPlaceholderEvidence,
1801
+ validateDevUrlDebugFlag,
1802
+ readSelectedAtomsForLink,
1803
+ extractMarkdownSection,
1804
+ countFilledTableRows,
1805
+ };
1806
+
1807
+ async function main() {
1808
+ const stdin = await readStdin();
1809
+ const input = parseHookInput(stdin);
1810
+
1811
+ const projectDir =
1812
+ process.env.CLAUDE_PROJECT_DIR ||
1813
+ process.env.CURSOR_PROJECT_DIR ||
1814
+ process.cwd();
1815
+
1816
+ const { skip } = resolveProjectDir(projectDir);
1817
+ if (skip) {
1818
+ process.exit(0);
1819
+ }
1820
+
1821
+ // slice-axis-v1: an explicit role marker (subagent_type / PLAYCRAFT_STOP
1822
+ // self-declaration) wins; otherwise derive the dispatched role from an
1823
+ // unresolved active slice so a top-level `claude -p` developer cannot STOP past
1824
+ // an unverified slice even without subagent context.
1825
+ const role = detectRole(input) || roleFromActiveSlice(projectDir);
1826
+ if (!role) {
1827
+ process.exit(0);
1828
+ }
1829
+
1830
+ // slice mode = this role owns an unresolved active slice → the slice state
1831
+ // machine is the only gate; ring intake/handoff ceremony is bypassed.
1832
+ const sliceOwner = roleFromActiveSlice(projectDir);
1833
+ const sliceMode = sliceOwner === role;
1834
+ // dispatch mismatch = an executor role was stopped on someone else's slice.
1835
+ const sliceMismatch = !!sliceOwner && sliceOwner !== role && SLICE_EXECUTOR_ROLES.has(role);
1836
+
1837
+ const errors = validateRole(projectDir, role);
1838
+ if (errors.length > 0) {
1839
+ const cfg = ROLE_LOG[role];
1840
+ if (sliceMode || sliceMismatch) {
1841
+ const label = cfg?.label || role;
1842
+ const head = sliceMismatch ? 'dispatch mismatch' : 'resolve the active slice before STOP';
1843
+ emitBlock(`${label} STOP blocked — ${head}:\n- ${errors.join('\n- ')}`);
1844
+ }
1845
+ const loop = readLoop(projectDir);
1846
+ const iteration = readIteration(projectDir) || 1;
1847
+ const section =
1848
+ role === 'reviewer'
1849
+ ? '§ Ring DoD Fields + handoff (no user_gate)'
1850
+ : role === 'game-designer'
1851
+ ? `§ ring ${iteration} clarify docs`
1852
+ : role === 'skill-selector'
1853
+ ? '§ atom-tree selection + handoff'
1854
+ : role === 'artist'
1855
+ ? loop === 'verify' && iteration >= 3
1856
+ ? '§ Image Production Preflight / ASR Coverage Matrix'
1857
+ : '§ Skill Preflight'
1858
+ : '§ Upstream Intake / Plan';
1859
+ const logRef = cfg.log ? `${cfg.log} ` : '';
1860
+ const reason = `${cfg.label} STOP blocked — fix ${logRef}${section}:\n- ${errors.join('\n- ')}`;
1861
+ emitBlock(reason);
1862
+ }
1863
+
1864
+ // In slice mode the active slice is the sole authority — skip the ring/handoff
1865
+ // workflow-state diagnosis (it carries ring-era expectations that do not apply
1866
+ // to a single dispatched slice). Legacy (no active slice) keeps full diagnosis.
1867
+ if (!sliceMode) {
1868
+ const stateErrors = await validateWorkflowState(projectDir);
1869
+ if (stateErrors.length > 0) {
1870
+ const reason = `Workflow STOP blocked — fix docs/project-state.md handoff / ring state:\n- ${stateErrors.join('\n- ')}`;
1871
+ emitBlock(reason);
1872
+ }
1873
+ }
1874
+
1875
+ process.exit(0);
1876
+ }
1877
+
1878
+ // Use basename comparison — on Windows, CLAUDE_PROJECT_DIR may be POSIX-style
1879
+ // (/d/project/...), and path.resolve would mangle it to D:\d\project\...,
1880
+ // never matching the Windows-native __filename.
1881
+ const isMain =
1882
+ process.argv[1] && path.basename(process.argv[1]) === path.basename(__filename);
1883
+
1884
+ if (isMain) {
1885
+ main().catch((err) => {
1886
+ process.stderr.write(`[playcraft-workflow-stop] validator error: ${err.message}\n`);
1887
+ process.exit(1);
1888
+ });
1889
+ }