@playcraft/cli 0.0.44 → 0.0.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atom-tree/build-skeleton.js +180 -0
- package/dist/atom-tree/fusion-hints.js +86 -0
- package/dist/atom-tree/selection.js +402 -0
- package/dist/commands/build-all.js +33 -5
- package/dist/commands/build.js +7 -1
- package/dist/commands/create.js +774 -50
- package/dist/commands/image.js +11 -0
- package/dist/commands/prad.js +61 -0
- package/dist/commands/remix.js +20 -0
- package/dist/commands/skills.js +961 -185
- package/dist/commands/tools-detect.js +56 -0
- package/dist/commands/tools-generation.js +21 -0
- package/dist/commands/tools-video-review.js +100 -0
- package/dist/commands/tools.js +4 -0
- package/dist/index.js +2 -0
- package/dist/prad/atom-ref.js +23 -0
- package/dist/prad/check.js +377 -0
- package/dist/prad/check.test.js +49 -0
- package/dist/prad/explain.js +109 -0
- package/dist/prad/load-spec.js +23 -0
- package/dist/prad/paths.js +83 -0
- package/dist/prad/skills-index.js +60 -0
- package/dist/remix/init-template.js +80 -0
- package/dist/scaffold/compose.js +211 -0
- package/engine-packs/phaser/game/adapters/phaser.ts +52 -0
- package/engine-packs/phaser/game/config/game-config.ts +46 -0
- package/engine-packs/phaser/game/contract/publish.ts +141 -0
- package/engine-packs/phaser/game/contract/types.ts +34 -0
- package/engine-packs/phaser/game/index.ts +81 -0
- package/engine-packs/phaser/game/runtime-contract.ts +28 -0
- package/engine-packs/phaser/manifest.json +1 -0
- package/engine-packs/phaser/package.deps.json +1 -0
- package/engine-packs/threejs/game/config/.gitkeep +0 -0
- package/engine-packs/threejs/game/config/game-config.ts +46 -0
- package/engine-packs/threejs/game/contract/publish.ts +141 -0
- package/engine-packs/threejs/game/contract/types.ts +34 -0
- package/engine-packs/threejs/game/gameplay/.gitkeep +0 -0
- package/engine-packs/threejs/game/index.ts +76 -0
- package/engine-packs/threejs/game/runtime-contract.ts +15 -0
- package/engine-packs/threejs/game/scenes/.gitkeep +0 -0
- package/engine-packs/threejs/manifest.json +1 -0
- package/engine-packs/threejs/package.deps.json +8 -0
- package/package.json +18 -4
- package/project-template/.claude/agents/artist.md +101 -0
- package/project-template/.claude/agents/developer.md +90 -93
- package/project-template/.claude/agents/game-designer.md +192 -0
- package/project-template/.claude/agents/refs/README.md +57 -51
- package/project-template/.claude/agents/refs/{designer-color-audio-recipes.md → artist-color-audio-recipes.md} +2 -2
- package/project-template/.claude/agents/refs/{designer-deliverable-spec.md → artist-deliverable-spec.md} +57 -57
- package/project-template/.claude/agents/refs/{designer-dimension-axis.md → artist-dimension-axis.md} +5 -5
- package/project-template/.claude/agents/refs/{designer-handoff-v2-checklist.md → artist-handoff-v2-checklist.md} +15 -15
- package/project-template/.claude/agents/refs/artist-legacy-fallback.md +14 -0
- package/project-template/.claude/agents/refs/{designer-master-composite-recipes.md → artist-master-composite-recipes.md} +3 -3
- package/project-template/.claude/agents/refs/artist-style-exploration-flow.md +55 -0
- package/project-template/.claude/agents/refs/developer-dev-handoff.md +83 -40
- package/project-template/.claude/agents/refs/developer-impl-cookbook.md +365 -67
- package/project-template/.claude/agents/refs/developer-legacy-fallback.md +15 -0
- package/project-template/.claude/agents/refs/developer-phase1-flow.md +131 -85
- package/project-template/.claude/agents/refs/framework-5-component-filter.md +252 -0
- package/project-template/.claude/agents/refs/framework-game-feel-juice.md +266 -0
- package/project-template/.claude/agents/refs/framework-mda.md +147 -0
- package/project-template/.claude/agents/refs/game-designer-gameplay-sufficiency.md +123 -0
- package/project-template/.claude/agents/refs/game-designer-legacy-fallback.md +15 -0
- package/project-template/.claude/agents/refs/game-designer-workflow-detail.md +69 -0
- package/project-template/.claude/agents/refs/implementation-plan-guide.md +316 -0
- package/project-template/.claude/agents/refs/reviewer-convergence-eval.md +247 -33
- package/project-template/.claude/agents/refs/reviewer-legacy-fallback.md +14 -0
- package/project-template/.claude/agents/refs/skill-selector-guide.md +465 -0
- package/project-template/.claude/agents/refs/skill-value-layers.md +145 -0
- package/project-template/.claude/agents/refs/storyboard-generation-guide.md +110 -0
- package/project-template/.claude/agents/refs/ta-atlas-deliverable-standard.md +4 -4
- package/project-template/.claude/agents/refs/ta-pipeline-cookbook.md +101 -104
- package/project-template/.claude/agents/refs/technical-artist-legacy-fallback.md +14 -0
- package/project-template/.claude/agents/reviewer.md +81 -95
- package/project-template/.claude/agents/technical-artist.md +71 -95
- package/project-template/.claude/hooks/README.md +83 -35
- package/project-template/.claude/hooks/asr-coverage-validate.mjs +19 -19
- package/project-template/.claude/hooks/block-developer-preflight.mjs +185 -0
- package/project-template/.claude/hooks/block-ring1-tasklist.mjs +95 -0
- package/project-template/.claude/hooks/gate-dispatcher.mjs +78 -0
- package/project-template/.claude/hooks/gate-runner.mjs +557 -0
- package/project-template/.claude/hooks/gate.mjs +170 -0
- package/project-template/.claude/hooks/snapshot-milestone.mjs +246 -0
- package/project-template/.claude/hooks/user-prompt.mjs +193 -0
- package/project-template/.claude/hooks/validate-atom-tree-selection.mjs +281 -0
- package/project-template/.claude/hooks/validate-workflow-stop.mjs +1535 -95
- package/project-template/.claude/settings.json +54 -9
- package/project-template/.claude/skills/brainstorming/SKILL.md +161 -0
- package/project-template/.claude/skills/brainstorming/scripts/frame-template.html +270 -0
- package/project-template/.claude/skills/brainstorming/scripts/helper.js +177 -0
- package/project-template/.claude/skills/brainstorming/scripts/server.cjs +354 -0
- package/project-template/.claude/skills/brainstorming/scripts/start-server.sh +148 -0
- package/project-template/.claude/skills/brainstorming/scripts/stop-server.sh +56 -0
- package/project-template/.claude/skills/brainstorming/scripts/wait-for-selection.sh +62 -0
- package/project-template/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/project-template/.claude/skills/brainstorming/visual-companion.md +309 -0
- package/project-template/.claude/skills/playcraft-ad-psychology/SKILL.md +3 -3
- package/project-template/.claude/skills/playcraft-art-style-guide/SKILL.md +3 -3
- package/project-template/.claude/skills/playcraft-asset-state-sheet/SKILL.md +35 -25
- package/project-template/.claude/skills/playcraft-audio-generation/SKILL.md +15 -15
- package/project-template/.claude/skills/playcraft-build-optimizer/SKILL.md +5 -5
- package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +5 -5
- package/project-template/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +5 -5
- package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +116 -27
- package/project-template/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +1 -1
- package/project-template/.claude/skills/playcraft-image-processing/SKILL.md +219 -0
- package/project-template/.claude/skills/playcraft-playable-optimization/SKILL.md +161 -0
- package/project-template/.claude/skills/playcraft-research/SKILL.md +24 -21
- package/project-template/.claude/skills/playcraft-skill-recommender/SKILL.md +331 -0
- package/project-template/.claude/skills/playcraft-sprite-generation/SKILL.md +48 -48
- package/project-template/.claude/skills/playcraft-sprite-remix/SKILL.md +158 -0
- package/project-template/.claude/skills/playcraft-sprite-sheet/SKILL.md +100 -0
- package/project-template/.claude/skills/playcraft-storyboard/SKILL.md +4 -4
- package/project-template/.claude/skills/playcraft-style-qa/SKILL.md +45 -45
- package/project-template/.claude/skills/playcraft-text-rendering/SKILL.md +27 -27
- package/project-template/.claude/skills/session-analyzer/SKILL.md +386 -0
- package/project-template/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +182 -0
- package/project-template/.claude/skills/session-analyzer/scripts/find-turns.mjs +72 -0
- package/project-template/.claude/skills/session-analyzer/scripts/heavy-output.mjs +121 -0
- package/project-template/.claude/skills/session-analyzer/scripts/resolve-session.mjs +103 -0
- package/project-template/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +127 -0
- package/project-template/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +106 -0
- package/project-template/.claude/skills/session-analyzer/scripts/time-gaps.mjs +128 -0
- package/project-template/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +67 -0
- package/project-template/.claude/snapshot.mjs +350 -0
- package/project-template/.codebuddy/settings.json +53 -0
- package/project-template/.cursor/hooks.json +1 -1
- package/project-template/.cursor/rules/playcraft-orchestrator.mdc +91 -86
- package/project-template/.cursor/rules/playcraft-subagent-boundary.mdc +16 -5
- package/project-template/CHANGELOG.md +105 -0
- package/project-template/CLAUDE.md +58 -254
- package/project-template/docs/DESIGN.md +65 -0
- package/project-template/docs/intent-clarifications.md +58 -0
- package/project-template/docs/pipeline-overview.md +69 -0
- package/project-template/docs/project-state.md +211 -0
- package/project-template/docs/team/agent-conduct.md +51 -105
- package/project-template/docs/team/agent-runtime-matrix.md +60 -74
- package/project-template/docs/team/atom-tree-format.md +169 -0
- package/project-template/docs/team/collaboration.md +319 -225
- package/project-template/docs/team/core-model.md +80 -46
- package/project-template/docs/team/game-designer-migration-todo.md +8 -0
- package/project-template/docs/team/harness-legacy-deprecation.md +23 -0
- package/project-template/docs/team/platform-capabilities.md +16 -13
- package/project-template/docs/team/workflow-changelog.md +95 -23
- package/project-template/docs/team/workflow-consistency-checklist.md +93 -45
- package/project-template/focus-lab/.gitkeep +0 -0
- package/project-template/game/.playcraft-scaffold-pending +0 -0
- package/project-template/game/index.ts +18 -0
- package/project-template/globals.d.ts +51 -0
- package/project-template/index.css +34 -0
- package/project-template/index.html +18 -0
- package/project-template/main.ts +9 -0
- package/project-template/package.json +79 -0
- package/project-template/skills/README.md +52 -0
- package/project-template/skills/_shared/agent-prep.md +136 -0
- package/project-template/skills/_shared/requirements.txt +1 -0
- package/project-template/skills/_shared/scripts/__pycache__/placeholder_audio_lib.cpython-311.pyc +0 -0
- package/project-template/skills/_shared/scripts/__pycache__/placeholder_lib.cpython-311.pyc +0 -0
- package/project-template/skills/_shared/scripts/build-evidence-gate.mjs +87 -0
- package/project-template/skills/_shared/scripts/context-budget.mjs +215 -0
- package/project-template/skills/_shared/scripts/create-playable.mjs +140 -0
- package/project-template/skills/_shared/scripts/dev-start.mjs +151 -0
- package/project-template/skills/_shared/scripts/disclosure-config.mjs +480 -0
- package/project-template/skills/_shared/scripts/ensure-project.mjs +532 -0
- package/project-template/skills/_shared/scripts/gen-placeholder-audio.py +345 -0
- package/project-template/skills/_shared/scripts/gen-placeholder.py +244 -0
- package/project-template/skills/_shared/scripts/harness-smoke.mjs +257 -0
- package/project-template/skills/_shared/scripts/lib/validator-artifacts.mjs +129 -0
- package/project-template/skills/_shared/scripts/placeholder-pack-sheet.mjs +67 -0
- package/project-template/skills/_shared/scripts/placeholder_audio_lib.py +142 -0
- package/project-template/skills/_shared/scripts/placeholder_lib.py +993 -0
- package/project-template/skills/_shared/scripts/render-project-state.mjs +278 -0
- package/project-template/skills/_shared/scripts/ring-config.mjs +252 -0
- package/project-template/skills/_shared/scripts/skills-check.mjs +176 -0
- package/project-template/skills/_shared/scripts/slice-set.mjs +77 -0
- package/project-template/skills/_shared/scripts/state-advance-ring.mjs +63 -0
- package/project-template/skills/_shared/scripts/state-get.mjs +215 -0
- package/project-template/skills/_shared/scripts/state-handoff.mjs +120 -0
- package/project-template/skills/_shared/scripts/state-set.mjs +113 -0
- package/project-template/skills/_shared/scripts/state-store.mjs +893 -0
- package/project-template/skills/_shared/scripts/sync-template-skills.mjs +112 -0
- package/project-template/skills/_shared/scripts/verify-asset-code-sync.mjs +265 -0
- package/project-template/skills/_shared/scripts/verify-atom-conformance.mjs +181 -0
- package/project-template/skills/_shared/scripts/verify-env.mjs +285 -0
- package/project-template/skills/_shared/scripts/verify-placeholders.mjs +161 -0
- package/project-template/skills/_shared/scripts/workflow-doctor.mjs +348 -0
- package/project-template/skills/playable-animate/SKILL.md +52 -0
- package/project-template/skills/playable-animate/agents/openai.yaml +4 -0
- package/project-template/skills/playable-animate/references/board-refresh.md +30 -0
- package/project-template/skills/playable-animate/references/diagnosis-checklist.md +25 -0
- package/project-template/skills/playable-animate/references/entrance-sequencing.md +33 -0
- package/project-template/skills/playable-animate/references/interaction-timing.md +23 -0
- package/project-template/skills/playable-art/SKILL.md +203 -0
- package/project-template/skills/playable-art/agents/openai.yaml +4 -0
- package/project-template/skills/playable-art/references/alpha-postprocess.md +46 -0
- package/project-template/skills/playable-art/references/cta-art-spec.md +59 -0
- package/project-template/skills/playable-art/references/layout-proportions.md +30 -0
- package/project-template/skills/playable-art/references/prompt-patterns.md +83 -0
- package/project-template/skills/playable-art/references/reference-comparison.md +30 -0
- package/project-template/skills/playable-art/references/sheet-import-v2.md +91 -0
- package/project-template/skills/playable-art/scripts/__pycache__/import-gpt-art-sheets-v2.cpython-311.pyc +0 -0
- package/project-template/skills/playable-art/scripts/__pycache__/import_gpt_art_pipeline.cpython-311.pyc +0 -0
- package/project-template/skills/playable-art/scripts/__pycache__/review-processed-assets.cpython-311.pyc +0 -0
- package/project-template/skills/playable-art/scripts/build-atlas.py +332 -0
- package/project-template/skills/playable-art/scripts/chroma-key.py +50 -0
- package/project-template/skills/playable-art/scripts/extract-alpha-split.py +65 -0
- package/project-template/skills/playable-art/scripts/extract-dual-bg-alpha.py +118 -0
- package/project-template/skills/playable-art/scripts/import-gpt-art-sheets-v2.py +403 -0
- package/project-template/skills/playable-art/scripts/import-gpt-art-sheets.py +280 -0
- package/project-template/skills/playable-art/scripts/import-gpt-tile-sheet.py +114 -0
- package/project-template/skills/playable-art/scripts/import_gpt_art_pipeline.py +321 -0
- package/project-template/skills/playable-art/scripts/import_gpt_art_sheets_v2_test.py +126 -0
- package/project-template/skills/playable-art/scripts/placeholder_common.py +98 -0
- package/project-template/skills/playable-art/scripts/remove-bg.py +91 -0
- package/project-template/skills/playable-art/scripts/review-processed-assets.py +468 -0
- package/project-template/skills/playable-art/scripts/review_processed_assets_test.py +116 -0
- package/project-template/skills/playable-art/scripts/verify-alpha-clean.py +105 -0
- package/project-template/skills/playable-debug/SKILL.md +116 -0
- package/project-template/skills/playable-debug/agents/openai.yaml +4 -0
- package/project-template/skills/playable-debug/references/debug-config.md +40 -0
- package/project-template/skills/playable-focus/SKILL.md +148 -0
- package/project-template/skills/playable-focus/scripts/bootstrap-playground.mjs +84 -0
- package/project-template/skills/playable-focus/scripts/extract-focus-frames.mjs +87 -0
- package/project-template/skills/playable-focus/scripts/generate-focus-reports.mjs +121 -0
- package/project-template/skills/playable-focus/scripts/playground.mjs +390 -0
- package/project-template/skills/playable-focus/templates/focus-lab/frame.html +39 -0
- package/project-template/skills/playable-focus/templates/focus-lab/harness/clean-stage.js +114 -0
- package/project-template/skills/playable-focus/templates/focus-lab/harness/frame-lab-scene.js +94 -0
- package/project-template/skills/playable-focus/templates/focus-lab/harness/particle-lab-scene.js +224 -0
- package/project-template/skills/playable-focus/templates/focus-lab/model.html +16 -0
- package/project-template/skills/playable-focus/templates/focus-lab/particle.html +38 -0
- package/project-template/skills/playable-focus/templates/focus-lab/shatter.html +47 -0
- package/project-template/skills/playable-focus/templates/playground/registry.template.json +55 -0
- package/project-template/skills/playable-focus/templates/playground/vfx-catalog.template.json +280 -0
- package/project-template/skills/playable-fx-decompose/SKILL.md +212 -0
- package/project-template/skills/playable-fx-decompose/examples.md +89 -0
- package/project-template/skills/playable-fx-decompose/reference.md +201 -0
- package/project-template/skills/playable-fx-decompose/requirements.txt +2 -0
- package/project-template/skills/playable-fx-decompose/scripts/build-blank-tile-crack.py +192 -0
- package/project-template/skills/playable-fx-decompose/scripts/fix-shard-debris-frames.py +60 -0
- package/project-template/skills/playable-fx-decompose/scripts/generate-shard-debris.py +276 -0
- package/project-template/skills/playable-fx-peak/SKILL.md +237 -0
- package/project-template/skills/playable-fx-peak/requirements.txt +2 -0
- package/project-template/skills/playable-fx-peak/scripts/fix-frame-edges.py +70 -0
- package/project-template/skills/playable-fx-peak/scripts/repack-frame-sequence.py +526 -0
- package/project-template/skills/playable-fx-peak/scripts/validate-frame-anchors.py +130 -0
- package/project-template/skills/playable-placeholder/SKILL.md +239 -0
- package/project-template/skills/playable-record/SKILL.md +166 -0
- package/project-template/skills/playable-record/scripts/lib/build-artifact.mjs +92 -0
- package/project-template/skills/playable-record/scripts/lib/dev-server.mjs +104 -0
- package/project-template/skills/playable-record/scripts/lib/record-audio-bridge.js +141 -0
- package/project-template/skills/playable-record/scripts/lib/slice-structure-verify.mjs +199 -0
- package/project-template/skills/playable-record/scripts/record-playable.mjs +583 -0
- package/project-template/skills/playable-record/scripts/verify-build.mjs +185 -0
- package/project-template/skills/playable-record/scripts/verify-contract.mjs +261 -0
- package/project-template/skills/playable-record/scripts/verify-firstwin.mjs +398 -0
- package/project-template/skills/playable-record/scripts/verify-fusion.mjs +320 -0
- package/project-template/skills/playable-record/scripts/verify-lifecycle.mjs +78 -0
- package/project-template/skills/playable-record/scripts/verify-slice-artist.mjs +53 -0
- package/project-template/skills/playable-record/scripts/verify-slice-ta.mjs +53 -0
- package/project-template/skills/playable-record/scripts/verify-slice.mjs +142 -0
- package/project-template/skills/playable-record/scripts/verify-visual-ring.mjs +250 -0
- package/project-template/skills/playable-record/scripts/verify-visual-ring1.mjs +190 -0
- package/project-template/skills/playable-record/scripts/verify-vlm-video.mjs +227 -0
- package/project-template/skills/playable-validate/SKILL.md +260 -0
- package/project-template/skills/playable-validate/references/checklist-detail.md +36 -0
- package/project-template/skills/playable-validate/references/single-html-bundle.md +76 -0
- package/project-template/skills/playable-validate/validation-rules.md +200 -0
- package/project-template/skills/playable-verify-ui/SKILL.md +295 -0
- package/project-template/skills/playable-verify-ui/examples/asset-manifest.example.json +36 -0
- package/project-template/skills/playable-verify-ui/examples/mahjong-match.profile.yaml +334 -0
- package/project-template/skills/playable-verify-ui/profiles/template.profile.yaml +84 -0
- package/project-template/skills/playable-verify-ui/reference.md +165 -0
- package/project-template/skills/playable-verify-ui/references/phaser-patterns.md +67 -0
- package/project-template/skills/playable-verify-ui/references/scaling-decision-tree.md +51 -0
- package/project-template/skills/playable-verify-ui/requirements.txt +7 -0
- package/project-template/skills/playable-verify-ui/schema/asset-manifest.schema.json +68 -0
- package/project-template/skills/playable-verify-ui/schema/playable-ui-profile.schema.json +171 -0
- package/project-template/skills/playable-verify-ui/scripts/__pycache__/asset_manifest.cpython-311.pyc +0 -0
- package/project-template/skills/playable-verify-ui/scripts/__pycache__/verify_slice_plan.cpython-311.pyc +0 -0
- package/project-template/skills/playable-verify-ui/scripts/__pycache__/verify_vision_evidence.cpython-311.pyc +0 -0
- package/project-template/skills/playable-verify-ui/scripts/asset_manifest.py +231 -0
- package/project-template/skills/playable-verify-ui/scripts/lib/__init__.py +1 -0
- package/project-template/skills/playable-verify-ui/scripts/lib/config_expr.js +218 -0
- package/project-template/skills/playable-verify-ui/scripts/lib/node_utils.js +51 -0
- package/project-template/skills/playable-verify-ui/scripts/lib/profile_loader.py +59 -0
- package/project-template/skills/playable-verify-ui/scripts/lib/report.py +106 -0
- package/project-template/skills/playable-verify-ui/scripts/lib/runtime_contract.js +116 -0
- package/project-template/skills/playable-verify-ui/scripts/measure_frame_content.py +77 -0
- package/project-template/skills/playable-verify-ui/scripts/run_all.py +265 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_asset_sizes.py +77 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_assets_present.py +62 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_atlas_budget.py +84 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_atlas_frames.py +79 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_bg_clean.py +365 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_doc_asset_sync.py +90 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +59 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_game_config_load.js +54 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +64 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_layout_regions.js +110 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_profile_schema.py +37 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_ring1_visual.py +211 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_runtime_contract.js +58 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_script_load_order.js +73 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_slice_plan.py +238 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_slice_plan_test.py +125 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_text_overlap.py +333 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_tile_consistency.py +323 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_vision_evidence.py +894 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_vision_evidence_test.py +122 -0
- package/project-template/skills/playable-verify-ui/scripts/verify_visual.py +422 -0
- package/project-template/templates/{designer-log.template.md → artist-log.template.md} +26 -31
- package/project-template/templates/atom-tree.template.json +133 -0
- package/project-template/templates/design-brief.template.md +59 -162
- package/project-template/templates/design-lens-checklist.reference.md +14 -14
- package/project-template/templates/design-methodology.md +25 -25
- package/project-template/templates/design.template.md +50 -0
- package/project-template/templates/developer-log.template.md +187 -63
- package/project-template/templates/five-axis-framework.md +89 -44
- package/project-template/templates/gen-placeholder-audio.template.mjs +63 -0
- package/project-template/templates/gen-placeholders.template.mjs +81 -0
- package/project-template/templates/intent-clarifications.template.md +7 -7
- package/project-template/templates/layout-spec.template.md +32 -20
- package/project-template/templates/plan.template.md +80 -0
- package/project-template/templates/project-state.template.md +90 -191
- package/project-template/templates/review-report.template.md +208 -24
- package/project-template/templates/style-exploration.template.md +8 -8
- package/project-template/templates/ta-log.template.md +58 -35
- package/project-template/tsconfig.json +20 -0
- package/project-template/vite.config.ts +27 -0
- package/project-templates/phaser/.claude/agents/artist.md +101 -0
- package/project-templates/phaser/.claude/agents/developer.md +123 -0
- package/project-templates/phaser/.claude/agents/game-designer.md +192 -0
- package/project-templates/phaser/.claude/agents/refs/README.md +77 -0
- package/project-templates/phaser/.claude/agents/refs/artist-art-style-catalog.md +533 -0
- package/project-templates/phaser/.claude/agents/refs/artist-color-audio-recipes.md +153 -0
- package/project-templates/phaser/.claude/agents/refs/artist-deliverable-spec.md +230 -0
- package/project-templates/phaser/.claude/agents/refs/artist-dimension-axis.md +27 -0
- package/project-templates/phaser/.claude/agents/refs/artist-handoff-v2-checklist.md +76 -0
- package/project-templates/phaser/.claude/agents/refs/artist-legacy-fallback.md +14 -0
- package/project-templates/phaser/.claude/agents/refs/artist-master-composite-recipes.md +208 -0
- package/project-templates/phaser/.claude/agents/refs/artist-style-exploration-flow.md +55 -0
- package/project-templates/phaser/.claude/agents/refs/developer-dev-handoff.md +152 -0
- package/project-templates/phaser/.claude/agents/refs/developer-impl-cookbook.md +432 -0
- package/project-templates/phaser/.claude/agents/refs/developer-legacy-fallback.md +15 -0
- package/project-templates/phaser/.claude/agents/refs/developer-phase1-flow.md +404 -0
- package/project-templates/phaser/.claude/agents/refs/framework-5-component-filter.md +252 -0
- package/project-templates/phaser/.claude/agents/refs/framework-game-feel-juice.md +266 -0
- package/project-templates/phaser/.claude/agents/refs/framework-mda.md +147 -0
- package/project-templates/phaser/.claude/agents/refs/game-designer-gameplay-sufficiency.md +123 -0
- package/project-templates/phaser/.claude/agents/refs/game-designer-legacy-fallback.md +15 -0
- package/project-templates/phaser/.claude/agents/refs/game-designer-workflow-detail.md +69 -0
- package/project-templates/phaser/.claude/agents/refs/implementation-plan-guide.md +309 -0
- package/project-templates/phaser/.claude/agents/refs/reviewer-convergence-eval.md +350 -0
- package/project-templates/phaser/.claude/agents/refs/reviewer-legacy-fallback.md +14 -0
- package/project-templates/phaser/.claude/agents/refs/reviewer-six-dimension-eval.md +6 -0
- package/project-templates/phaser/.claude/agents/refs/skill-selector-guide.md +465 -0
- package/project-templates/phaser/.claude/agents/refs/skill-value-layers.md +145 -0
- package/project-templates/phaser/.claude/agents/refs/storyboard-generation-guide.md +110 -0
- package/project-templates/phaser/.claude/agents/refs/ta-3d-flip-recipe.md +88 -0
- package/project-templates/phaser/.claude/agents/refs/ta-atlas-deliverable-standard.md +67 -0
- package/project-templates/phaser/.claude/agents/refs/ta-batch-pipeline-recipes.md +120 -0
- package/project-templates/phaser/.claude/agents/refs/ta-image-generation-detail.md +300 -0
- package/project-templates/phaser/.claude/agents/refs/ta-image-ops-reference.md +495 -0
- package/project-templates/phaser/.claude/agents/refs/ta-pipeline-cookbook.md +1141 -0
- package/project-templates/phaser/.claude/agents/refs/ta-tools-reference.md +111 -0
- package/project-templates/phaser/.claude/agents/refs/ta-vfx-preset-catalog.md +365 -0
- package/project-templates/phaser/.claude/agents/refs/technical-artist-legacy-fallback.md +14 -0
- package/project-templates/phaser/.claude/agents/reviewer.md +116 -0
- package/project-templates/phaser/.claude/agents/technical-artist.md +100 -0
- package/project-templates/phaser/.claude/hooks/README.md +120 -0
- package/project-templates/phaser/.claude/hooks/asr-coverage-validate.mjs +381 -0
- package/project-templates/phaser/.claude/hooks/block-developer-preflight.mjs +185 -0
- package/project-templates/phaser/.claude/hooks/block-ring1-tasklist.mjs +95 -0
- package/project-templates/phaser/.claude/hooks/gate-dispatcher.mjs +78 -0
- package/project-templates/phaser/.claude/hooks/gate-runner.mjs +557 -0
- package/project-templates/phaser/.claude/hooks/gate.mjs +171 -0
- package/project-templates/phaser/.claude/hooks/snapshot-milestone.mjs +247 -0
- package/project-templates/phaser/.claude/hooks/user-prompt.mjs +194 -0
- package/project-templates/phaser/.claude/hooks/validate-atom-tree-selection.mjs +281 -0
- package/project-templates/phaser/.claude/hooks/validate-workflow-stop.mjs +1889 -0
- package/project-templates/phaser/.claude/settings.json +81 -0
- package/project-templates/phaser/.claude/skills/brainstorming/SKILL.md +161 -0
- package/project-templates/phaser/.claude/skills/brainstorming/scripts/frame-template.html +270 -0
- package/project-templates/phaser/.claude/skills/brainstorming/scripts/helper.js +177 -0
- package/project-templates/phaser/.claude/skills/brainstorming/scripts/server.cjs +354 -0
- package/project-templates/phaser/.claude/skills/brainstorming/scripts/start-server.sh +148 -0
- package/project-templates/phaser/.claude/skills/brainstorming/scripts/stop-server.sh +56 -0
- package/project-templates/phaser/.claude/skills/brainstorming/scripts/wait-for-selection.sh +62 -0
- package/project-templates/phaser/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/project-templates/phaser/.claude/skills/brainstorming/visual-companion.md +309 -0
- package/project-templates/phaser/.claude/skills/playcraft-ad-psychology/SKILL.md +182 -0
- package/project-templates/phaser/.claude/skills/playcraft-art-style-guide/SKILL.md +123 -0
- package/project-templates/phaser/.claude/skills/playcraft-asset-state-sheet/SKILL.md +205 -0
- package/project-templates/phaser/.claude/skills/playcraft-audio-generation/SKILL.md +280 -0
- package/project-templates/phaser/.claude/skills/playcraft-batch-pipeline/SKILL.md +184 -0
- package/project-templates/phaser/.claude/skills/playcraft-build-optimizer/SKILL.md +306 -0
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/SKILL.md +298 -0
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +123 -0
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +254 -0
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +324 -0
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +97 -0
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +118 -0
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +137 -0
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +397 -0
- package/project-templates/phaser/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +296 -0
- package/project-templates/phaser/.claude/skills/playcraft-image-ops/SKILL.md +122 -0
- package/project-templates/phaser/.claude/skills/playcraft-image-processing/SKILL.md +219 -0
- package/project-templates/phaser/.claude/skills/playcraft-masking/SKILL.md +373 -0
- package/project-templates/phaser/.claude/skills/playcraft-playable-optimization/SKILL.md +161 -0
- package/project-templates/phaser/.claude/skills/playcraft-research/SKILL.md +215 -0
- package/project-templates/phaser/.claude/skills/playcraft-skill-recommender/SKILL.md +331 -0
- package/project-templates/phaser/.claude/skills/playcraft-sprite-generation/SKILL.md +423 -0
- package/project-templates/phaser/.claude/skills/playcraft-sprite-remix/SKILL.md +158 -0
- package/project-templates/phaser/.claude/skills/playcraft-sprite-sheet/SKILL.md +100 -0
- package/project-templates/phaser/.claude/skills/playcraft-storyboard/SKILL.md +167 -0
- package/project-templates/phaser/.claude/skills/playcraft-style-qa/SKILL.md +270 -0
- package/project-templates/phaser/.claude/skills/playcraft-text-rendering/SKILL.md +236 -0
- package/project-templates/phaser/.claude/skills/playcraft-vfx-animation/SKILL.md +130 -0
- package/project-templates/phaser/.claude/skills/playwright-cli/SKILL.md +390 -0
- package/project-templates/phaser/.claude/skills/playwright-cli/references/element-attributes.md +23 -0
- package/project-templates/phaser/.claude/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/project-templates/phaser/.claude/skills/playwright-cli/references/request-mocking.md +87 -0
- package/project-templates/phaser/.claude/skills/playwright-cli/references/running-code.md +240 -0
- package/project-templates/phaser/.claude/skills/playwright-cli/references/session-management.md +226 -0
- package/project-templates/phaser/.claude/skills/playwright-cli/references/spec-driven-testing.md +308 -0
- package/project-templates/phaser/.claude/skills/playwright-cli/references/storage-state.md +275 -0
- package/project-templates/phaser/.claude/skills/playwright-cli/references/test-generation.md +134 -0
- package/project-templates/phaser/.claude/skills/playwright-cli/references/tracing.md +142 -0
- package/project-templates/phaser/.claude/skills/playwright-cli/references/video-recording.md +153 -0
- package/project-templates/phaser/.claude/skills/session-analyzer/SKILL.md +386 -0
- package/project-templates/phaser/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +182 -0
- package/project-templates/phaser/.claude/skills/session-analyzer/scripts/find-turns.mjs +72 -0
- package/project-templates/phaser/.claude/skills/session-analyzer/scripts/heavy-output.mjs +121 -0
- package/project-templates/phaser/.claude/skills/session-analyzer/scripts/resolve-session.mjs +103 -0
- package/project-templates/phaser/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +127 -0
- package/project-templates/phaser/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +106 -0
- package/project-templates/phaser/.claude/skills/session-analyzer/scripts/time-gaps.mjs +128 -0
- package/project-templates/phaser/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +67 -0
- package/project-templates/phaser/.claude/snapshot.mjs +257 -0
- package/project-templates/phaser/.cursor/hooks.json +17 -0
- package/project-templates/phaser/.cursor/rules/playcraft-orchestrator.mdc +142 -0
- package/project-templates/phaser/.cursor/rules/playcraft-subagent-boundary.mdc +29 -0
- package/project-templates/phaser/.playcraft-engine +1 -0
- package/project-templates/phaser/CHANGELOG.md +105 -0
- package/project-templates/phaser/CLAUDE.md +84 -0
- package/project-templates/phaser/assets/audio/bgm/.gitkeep +0 -0
- package/project-templates/phaser/assets/audio/sfx/.gitkeep +0 -0
- package/project-templates/phaser/assets/bundles/.gitkeep +0 -0
- package/project-templates/phaser/assets/images/bg/.gitkeep +0 -0
- package/project-templates/phaser/assets/images/reference/.gitkeep +0 -0
- package/project-templates/phaser/assets/images/storyboard/.gitkeep +0 -0
- package/project-templates/phaser/assets/images/tiles/.gitkeep +0 -0
- package/project-templates/phaser/assets/images/ui/.gitkeep +0 -0
- package/project-templates/phaser/assets/images/vfx/.gitkeep +0 -0
- package/project-templates/phaser/assets/models/.gitkeep +0 -0
- package/project-templates/phaser/docs/DESIGN.md +65 -0
- package/project-templates/phaser/docs/intent-clarifications.md +58 -0
- package/project-templates/phaser/docs/pipeline-overview.md +69 -0
- package/project-templates/phaser/docs/project-state.md +211 -0
- package/project-templates/phaser/docs/scaffold-manifest.md +10 -0
- package/project-templates/phaser/docs/team/agent-conduct.md +67 -0
- package/project-templates/phaser/docs/team/agent-runtime-matrix.md +80 -0
- package/project-templates/phaser/docs/team/atom-tree-format.md +114 -0
- package/project-templates/phaser/docs/team/collaboration.md +391 -0
- package/project-templates/phaser/docs/team/core-model.md +85 -0
- package/project-templates/phaser/docs/team/game-designer-migration-todo.md +8 -0
- package/project-templates/phaser/docs/team/harness-legacy-deprecation.md +23 -0
- package/project-templates/phaser/docs/team/platform-capabilities.md +18 -0
- package/project-templates/phaser/docs/team/workflow-changelog.md +137 -0
- package/project-templates/phaser/docs/team/workflow-consistency-checklist.md +240 -0
- package/project-templates/phaser/focus-lab/.gitkeep +0 -0
- package/project-templates/phaser/game/adapters/phaser.ts +52 -0
- package/project-templates/phaser/game/config/.gitkeep +0 -0
- package/project-templates/phaser/game/config/game-config.ts +46 -0
- package/project-templates/phaser/game/contract/publish.ts +141 -0
- package/project-templates/phaser/game/contract/types.ts +34 -0
- package/project-templates/phaser/game/gameplay/.gitkeep +0 -0
- package/project-templates/phaser/game/index.ts +81 -0
- package/project-templates/phaser/game/runtime-contract.ts +28 -0
- package/project-templates/phaser/game/scenes/.gitkeep +0 -0
- package/project-templates/phaser/globals.d.ts +51 -0
- package/project-templates/phaser/index.css +34 -0
- package/project-templates/phaser/index.html +18 -0
- package/project-templates/phaser/main.ts +9 -0
- package/project-templates/phaser/package.json +79 -0
- package/project-templates/phaser/skills/README.md +52 -0
- package/project-templates/phaser/skills/_shared/agent-prep.md +136 -0
- package/project-templates/phaser/skills/_shared/requirements.txt +1 -0
- package/project-templates/phaser/skills/_shared/scripts/build-evidence-gate.mjs +87 -0
- package/project-templates/phaser/skills/_shared/scripts/context-budget.mjs +215 -0
- package/project-templates/phaser/skills/_shared/scripts/create-playable.mjs +140 -0
- package/project-templates/phaser/skills/_shared/scripts/dev-start.mjs +151 -0
- package/project-templates/phaser/skills/_shared/scripts/disclosure-config.mjs +480 -0
- package/project-templates/phaser/skills/_shared/scripts/ensure-project.mjs +532 -0
- package/project-templates/phaser/skills/_shared/scripts/gen-placeholder-audio.py +345 -0
- package/project-templates/phaser/skills/_shared/scripts/gen-placeholder.py +244 -0
- package/project-templates/phaser/skills/_shared/scripts/harness-smoke.mjs +257 -0
- package/project-templates/phaser/skills/_shared/scripts/lib/validator-artifacts.mjs +129 -0
- package/project-templates/phaser/skills/_shared/scripts/placeholder-pack-sheet.mjs +67 -0
- package/project-templates/phaser/skills/_shared/scripts/placeholder_audio_lib.py +142 -0
- package/project-templates/phaser/skills/_shared/scripts/placeholder_lib.py +993 -0
- package/project-templates/phaser/skills/_shared/scripts/render-project-state.mjs +278 -0
- package/project-templates/phaser/skills/_shared/scripts/ring-config.mjs +252 -0
- package/project-templates/phaser/skills/_shared/scripts/skills-check.mjs +176 -0
- package/project-templates/phaser/skills/_shared/scripts/slice-set.mjs +77 -0
- package/project-templates/phaser/skills/_shared/scripts/state-advance-ring.mjs +63 -0
- package/project-templates/phaser/skills/_shared/scripts/state-get.mjs +215 -0
- package/project-templates/phaser/skills/_shared/scripts/state-handoff.mjs +120 -0
- package/project-templates/phaser/skills/_shared/scripts/state-set.mjs +113 -0
- package/project-templates/phaser/skills/_shared/scripts/state-store.mjs +893 -0
- package/project-templates/phaser/skills/_shared/scripts/sync-template-skills.mjs +112 -0
- package/project-templates/phaser/skills/_shared/scripts/verify-asset-code-sync.mjs +265 -0
- package/project-templates/phaser/skills/_shared/scripts/verify-atom-conformance.mjs +181 -0
- package/project-templates/phaser/skills/_shared/scripts/verify-env.mjs +285 -0
- package/project-templates/phaser/skills/_shared/scripts/verify-placeholders.mjs +161 -0
- package/project-templates/phaser/skills/_shared/scripts/workflow-doctor.mjs +348 -0
- package/project-templates/phaser/skills/playable-animate/SKILL.md +52 -0
- package/project-templates/phaser/skills/playable-animate/agents/openai.yaml +4 -0
- package/project-templates/phaser/skills/playable-animate/references/board-refresh.md +30 -0
- package/project-templates/phaser/skills/playable-animate/references/diagnosis-checklist.md +25 -0
- package/project-templates/phaser/skills/playable-animate/references/entrance-sequencing.md +33 -0
- package/project-templates/phaser/skills/playable-animate/references/interaction-timing.md +23 -0
- package/project-templates/phaser/skills/playable-art/SKILL.md +203 -0
- package/project-templates/phaser/skills/playable-art/agents/openai.yaml +4 -0
- package/project-templates/phaser/skills/playable-art/references/alpha-postprocess.md +46 -0
- package/project-templates/phaser/skills/playable-art/references/cta-art-spec.md +59 -0
- package/project-templates/phaser/skills/playable-art/references/layout-proportions.md +30 -0
- package/project-templates/phaser/skills/playable-art/references/prompt-patterns.md +83 -0
- package/project-templates/phaser/skills/playable-art/references/reference-comparison.md +30 -0
- package/project-templates/phaser/skills/playable-art/references/sheet-import-v2.md +91 -0
- package/project-templates/phaser/skills/playable-art/scripts/build-atlas.py +332 -0
- package/project-templates/phaser/skills/playable-art/scripts/chroma-key.py +50 -0
- package/project-templates/phaser/skills/playable-art/scripts/extract-alpha-split.py +65 -0
- package/project-templates/phaser/skills/playable-art/scripts/extract-dual-bg-alpha.py +118 -0
- package/project-templates/phaser/skills/playable-art/scripts/import-gpt-art-sheets-v2.py +403 -0
- package/project-templates/phaser/skills/playable-art/scripts/import-gpt-art-sheets.py +280 -0
- package/project-templates/phaser/skills/playable-art/scripts/import-gpt-tile-sheet.py +114 -0
- package/project-templates/phaser/skills/playable-art/scripts/import_gpt_art_pipeline.py +321 -0
- package/project-templates/phaser/skills/playable-art/scripts/import_gpt_art_sheets_v2_test.py +126 -0
- package/project-templates/phaser/skills/playable-art/scripts/placeholder_common.py +98 -0
- package/project-templates/phaser/skills/playable-art/scripts/remove-bg.py +91 -0
- package/project-templates/phaser/skills/playable-art/scripts/review-processed-assets.py +468 -0
- package/project-templates/phaser/skills/playable-art/scripts/review_processed_assets_test.py +116 -0
- package/project-templates/phaser/skills/playable-art/scripts/verify-alpha-clean.py +105 -0
- package/project-templates/phaser/skills/playable-debug/SKILL.md +116 -0
- package/project-templates/phaser/skills/playable-debug/agents/openai.yaml +4 -0
- package/project-templates/phaser/skills/playable-debug/references/debug-config.md +40 -0
- package/project-templates/phaser/skills/playable-focus/SKILL.md +148 -0
- package/project-templates/phaser/skills/playable-focus/scripts/bootstrap-playground.mjs +84 -0
- package/project-templates/phaser/skills/playable-focus/scripts/extract-focus-frames.mjs +87 -0
- package/project-templates/phaser/skills/playable-focus/scripts/generate-focus-reports.mjs +121 -0
- package/project-templates/phaser/skills/playable-focus/scripts/playground.mjs +390 -0
- package/project-templates/phaser/skills/playable-focus/templates/focus-lab/frame.html +39 -0
- package/project-templates/phaser/skills/playable-focus/templates/focus-lab/harness/clean-stage.js +114 -0
- package/project-templates/phaser/skills/playable-focus/templates/focus-lab/harness/frame-lab-scene.js +94 -0
- package/project-templates/phaser/skills/playable-focus/templates/focus-lab/harness/particle-lab-scene.js +224 -0
- package/project-templates/phaser/skills/playable-focus/templates/focus-lab/model.html +16 -0
- package/project-templates/phaser/skills/playable-focus/templates/focus-lab/particle.html +38 -0
- package/project-templates/phaser/skills/playable-focus/templates/focus-lab/shatter.html +47 -0
- package/project-templates/phaser/skills/playable-fx-decompose/SKILL.md +212 -0
- package/project-templates/phaser/skills/playable-fx-decompose/examples.md +89 -0
- package/project-templates/phaser/skills/playable-fx-decompose/reference.md +201 -0
- package/project-templates/phaser/skills/playable-fx-decompose/requirements.txt +2 -0
- package/project-templates/phaser/skills/playable-fx-decompose/scripts/build-blank-tile-crack.py +192 -0
- package/project-templates/phaser/skills/playable-fx-decompose/scripts/fix-shard-debris-frames.py +60 -0
- package/project-templates/phaser/skills/playable-fx-decompose/scripts/generate-shard-debris.py +276 -0
- package/project-templates/phaser/skills/playable-fx-peak/SKILL.md +237 -0
- package/project-templates/phaser/skills/playable-fx-peak/requirements.txt +2 -0
- package/project-templates/phaser/skills/playable-fx-peak/scripts/fix-frame-edges.py +70 -0
- package/project-templates/phaser/skills/playable-fx-peak/scripts/repack-frame-sequence.py +526 -0
- package/project-templates/phaser/skills/playable-fx-peak/scripts/validate-frame-anchors.py +130 -0
- package/project-templates/phaser/skills/playable-placeholder/SKILL.md +239 -0
- package/project-templates/phaser/skills/playable-record/SKILL.md +166 -0
- package/project-templates/phaser/skills/playable-record/scripts/lib/build-artifact.mjs +92 -0
- package/project-templates/phaser/skills/playable-record/scripts/lib/dev-server.mjs +104 -0
- package/project-templates/phaser/skills/playable-record/scripts/lib/record-audio-bridge.js +141 -0
- package/project-templates/phaser/skills/playable-record/scripts/lib/slice-structure-verify.mjs +199 -0
- package/project-templates/phaser/skills/playable-record/scripts/record-playable.mjs +583 -0
- package/project-templates/phaser/skills/playable-record/scripts/verify-build.mjs +185 -0
- package/project-templates/phaser/skills/playable-record/scripts/verify-contract.mjs +261 -0
- package/project-templates/phaser/skills/playable-record/scripts/verify-firstwin.mjs +398 -0
- package/project-templates/phaser/skills/playable-record/scripts/verify-fusion.mjs +320 -0
- package/project-templates/phaser/skills/playable-record/scripts/verify-lifecycle.mjs +78 -0
- package/project-templates/phaser/skills/playable-record/scripts/verify-slice-artist.mjs +53 -0
- package/project-templates/phaser/skills/playable-record/scripts/verify-slice-ta.mjs +53 -0
- package/project-templates/phaser/skills/playable-record/scripts/verify-slice.mjs +142 -0
- package/project-templates/phaser/skills/playable-record/scripts/verify-visual-ring.mjs +250 -0
- package/project-templates/phaser/skills/playable-record/scripts/verify-visual-ring1.mjs +190 -0
- package/project-templates/phaser/skills/playable-record/scripts/verify-vlm-video.mjs +227 -0
- package/project-templates/phaser/skills/playable-validate/SKILL.md +260 -0
- package/project-templates/phaser/skills/playable-validate/references/checklist-detail.md +36 -0
- package/project-templates/phaser/skills/playable-validate/references/single-html-bundle.md +76 -0
- package/project-templates/phaser/skills/playable-validate/validation-rules.md +200 -0
- package/project-templates/phaser/skills/playable-verify-ui/SKILL.md +295 -0
- package/project-templates/phaser/skills/playable-verify-ui/examples/asset-manifest.example.json +36 -0
- package/project-templates/phaser/skills/playable-verify-ui/examples/mahjong-match.profile.yaml +334 -0
- package/project-templates/phaser/skills/playable-verify-ui/profiles/template.profile.yaml +84 -0
- package/project-templates/phaser/skills/playable-verify-ui/reference.md +165 -0
- package/project-templates/phaser/skills/playable-verify-ui/references/phaser-patterns.md +67 -0
- package/project-templates/phaser/skills/playable-verify-ui/references/scaling-decision-tree.md +51 -0
- package/project-templates/phaser/skills/playable-verify-ui/requirements.txt +7 -0
- package/project-templates/phaser/skills/playable-verify-ui/schema/asset-manifest.schema.json +68 -0
- package/project-templates/phaser/skills/playable-verify-ui/schema/playable-ui-profile.schema.json +171 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/asset_manifest.py +231 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/lib/__init__.py +1 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/lib/config_expr.js +218 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/lib/node_utils.js +51 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/lib/profile_loader.py +59 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/lib/report.py +106 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/lib/runtime_contract.js +116 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/measure_frame_content.py +77 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/run_all.py +265 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_asset_sizes.py +77 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_assets_present.py +62 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_atlas_budget.py +84 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_atlas_frames.py +79 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_bg_clean.py +365 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_doc_asset_sync.py +90 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +59 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_game_config_load.js +54 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +64 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_layout_regions.js +110 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_profile_schema.py +37 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_ring1_visual.py +211 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_runtime_contract.js +58 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_script_load_order.js +73 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_slice_plan.py +238 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_slice_plan_test.py +125 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_text_overlap.py +333 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_tile_consistency.py +323 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_vision_evidence.py +894 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_vision_evidence_test.py +122 -0
- package/project-templates/phaser/skills/playable-verify-ui/scripts/verify_visual.py +422 -0
- package/project-templates/phaser/ta-workspace/scripts/.gitkeep +0 -0
- package/project-templates/phaser/templates/artist-log.template.md +182 -0
- package/project-templates/phaser/templates/atom-tree.template.json +133 -0
- package/project-templates/phaser/templates/design-brief.template.md +92 -0
- package/project-templates/phaser/templates/design-lens-checklist.reference.md +117 -0
- package/project-templates/phaser/templates/design-methodology.md +99 -0
- package/project-templates/phaser/templates/design.template.md +50 -0
- package/project-templates/phaser/templates/developer-log.template.md +258 -0
- package/project-templates/phaser/templates/five-axis-framework.md +194 -0
- package/project-templates/phaser/templates/gen-placeholder-audio.template.mjs +63 -0
- package/project-templates/phaser/templates/gen-placeholders.template.mjs +81 -0
- package/project-templates/phaser/templates/intent-clarifications.template.md +58 -0
- package/project-templates/phaser/templates/layout-spec.template.md +198 -0
- package/project-templates/phaser/templates/plan.template.md +80 -0
- package/project-templates/phaser/templates/project-state.template.md +136 -0
- package/project-templates/phaser/templates/review-report.template.md +275 -0
- package/project-templates/phaser/templates/style-exploration.template.md +93 -0
- package/project-templates/phaser/templates/ta-log.template.md +394 -0
- package/project-templates/phaser/tsconfig.json +20 -0
- package/project-templates/phaser/vite.config.ts +27 -0
- package/project-templates/threejs/.claude/agents/artist.md +101 -0
- package/project-templates/threejs/.claude/agents/developer.md +123 -0
- package/project-templates/threejs/.claude/agents/game-designer.md +192 -0
- package/project-templates/threejs/.claude/agents/refs/README.md +77 -0
- package/project-templates/threejs/.claude/agents/refs/artist-art-style-catalog.md +533 -0
- package/project-templates/threejs/.claude/agents/refs/artist-color-audio-recipes.md +153 -0
- package/project-templates/threejs/.claude/agents/refs/artist-deliverable-spec.md +230 -0
- package/project-templates/threejs/.claude/agents/refs/artist-dimension-axis.md +27 -0
- package/project-templates/threejs/.claude/agents/refs/artist-handoff-v2-checklist.md +76 -0
- package/project-templates/threejs/.claude/agents/refs/artist-legacy-fallback.md +14 -0
- package/project-templates/threejs/.claude/agents/refs/artist-master-composite-recipes.md +208 -0
- package/project-templates/threejs/.claude/agents/refs/artist-style-exploration-flow.md +55 -0
- package/project-templates/threejs/.claude/agents/refs/developer-dev-handoff.md +152 -0
- package/project-templates/threejs/.claude/agents/refs/developer-impl-cookbook.md +432 -0
- package/project-templates/threejs/.claude/agents/refs/developer-legacy-fallback.md +15 -0
- package/project-templates/threejs/.claude/agents/refs/developer-phase1-flow.md +404 -0
- package/project-templates/threejs/.claude/agents/refs/framework-5-component-filter.md +252 -0
- package/project-templates/threejs/.claude/agents/refs/framework-game-feel-juice.md +266 -0
- package/project-templates/threejs/.claude/agents/refs/framework-mda.md +147 -0
- package/project-templates/threejs/.claude/agents/refs/game-designer-gameplay-sufficiency.md +123 -0
- package/project-templates/threejs/.claude/agents/refs/game-designer-legacy-fallback.md +15 -0
- package/project-templates/threejs/.claude/agents/refs/game-designer-workflow-detail.md +69 -0
- package/project-templates/threejs/.claude/agents/refs/implementation-plan-guide.md +309 -0
- package/project-templates/threejs/.claude/agents/refs/reviewer-convergence-eval.md +350 -0
- package/project-templates/threejs/.claude/agents/refs/reviewer-legacy-fallback.md +14 -0
- package/project-templates/threejs/.claude/agents/refs/reviewer-six-dimension-eval.md +6 -0
- package/project-templates/threejs/.claude/agents/refs/skill-selector-guide.md +465 -0
- package/project-templates/threejs/.claude/agents/refs/skill-value-layers.md +145 -0
- package/project-templates/threejs/.claude/agents/refs/storyboard-generation-guide.md +110 -0
- package/project-templates/threejs/.claude/agents/refs/ta-3d-flip-recipe.md +88 -0
- package/project-templates/threejs/.claude/agents/refs/ta-atlas-deliverable-standard.md +67 -0
- package/project-templates/threejs/.claude/agents/refs/ta-batch-pipeline-recipes.md +120 -0
- package/project-templates/threejs/.claude/agents/refs/ta-image-generation-detail.md +300 -0
- package/project-templates/threejs/.claude/agents/refs/ta-image-ops-reference.md +495 -0
- package/project-templates/threejs/.claude/agents/refs/ta-pipeline-cookbook.md +1141 -0
- package/project-templates/threejs/.claude/agents/refs/ta-tools-reference.md +111 -0
- package/project-templates/threejs/.claude/agents/refs/ta-vfx-preset-catalog.md +365 -0
- package/project-templates/threejs/.claude/agents/refs/technical-artist-legacy-fallback.md +14 -0
- package/project-templates/threejs/.claude/agents/reviewer.md +116 -0
- package/project-templates/threejs/.claude/agents/technical-artist.md +100 -0
- package/project-templates/threejs/.claude/hooks/README.md +120 -0
- package/project-templates/threejs/.claude/hooks/asr-coverage-validate.mjs +381 -0
- package/project-templates/threejs/.claude/hooks/block-developer-preflight.mjs +185 -0
- package/project-templates/threejs/.claude/hooks/block-ring1-tasklist.mjs +95 -0
- package/project-templates/threejs/.claude/hooks/gate-dispatcher.mjs +78 -0
- package/project-templates/threejs/.claude/hooks/gate-runner.mjs +557 -0
- package/project-templates/threejs/.claude/hooks/gate.mjs +171 -0
- package/project-templates/threejs/.claude/hooks/snapshot-milestone.mjs +247 -0
- package/project-templates/threejs/.claude/hooks/user-prompt.mjs +194 -0
- package/project-templates/threejs/.claude/hooks/validate-atom-tree-selection.mjs +281 -0
- package/project-templates/threejs/.claude/hooks/validate-workflow-stop.mjs +1889 -0
- package/project-templates/threejs/.claude/settings.json +81 -0
- package/project-templates/threejs/.claude/skills/brainstorming/SKILL.md +161 -0
- package/project-templates/threejs/.claude/skills/brainstorming/scripts/frame-template.html +270 -0
- package/project-templates/threejs/.claude/skills/brainstorming/scripts/helper.js +177 -0
- package/project-templates/threejs/.claude/skills/brainstorming/scripts/server.cjs +354 -0
- package/project-templates/threejs/.claude/skills/brainstorming/scripts/start-server.sh +148 -0
- package/project-templates/threejs/.claude/skills/brainstorming/scripts/stop-server.sh +56 -0
- package/project-templates/threejs/.claude/skills/brainstorming/scripts/wait-for-selection.sh +62 -0
- package/project-templates/threejs/.claude/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/project-templates/threejs/.claude/skills/brainstorming/visual-companion.md +309 -0
- package/project-templates/threejs/.claude/skills/playcraft-ad-psychology/SKILL.md +182 -0
- package/project-templates/threejs/.claude/skills/playcraft-art-style-guide/SKILL.md +123 -0
- package/project-templates/threejs/.claude/skills/playcraft-asset-state-sheet/SKILL.md +205 -0
- package/project-templates/threejs/.claude/skills/playcraft-audio-generation/SKILL.md +280 -0
- package/project-templates/threejs/.claude/skills/playcraft-batch-pipeline/SKILL.md +184 -0
- package/project-templates/threejs/.claude/skills/playcraft-build-optimizer/SKILL.md +306 -0
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/SKILL.md +298 -0
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +123 -0
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +254 -0
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +324 -0
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +97 -0
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +118 -0
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +137 -0
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +397 -0
- package/project-templates/threejs/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +296 -0
- package/project-templates/threejs/.claude/skills/playcraft-image-ops/SKILL.md +122 -0
- package/project-templates/threejs/.claude/skills/playcraft-image-processing/SKILL.md +219 -0
- package/project-templates/threejs/.claude/skills/playcraft-masking/SKILL.md +373 -0
- package/project-templates/threejs/.claude/skills/playcraft-playable-optimization/SKILL.md +161 -0
- package/project-templates/threejs/.claude/skills/playcraft-research/SKILL.md +215 -0
- package/project-templates/threejs/.claude/skills/playcraft-skill-recommender/SKILL.md +331 -0
- package/project-templates/threejs/.claude/skills/playcraft-sprite-generation/SKILL.md +423 -0
- package/project-templates/threejs/.claude/skills/playcraft-sprite-remix/SKILL.md +158 -0
- package/project-templates/threejs/.claude/skills/playcraft-sprite-sheet/SKILL.md +100 -0
- package/project-templates/threejs/.claude/skills/playcraft-storyboard/SKILL.md +167 -0
- package/project-templates/threejs/.claude/skills/playcraft-style-qa/SKILL.md +270 -0
- package/project-templates/threejs/.claude/skills/playcraft-text-rendering/SKILL.md +236 -0
- package/project-templates/threejs/.claude/skills/playcraft-vfx-animation/SKILL.md +130 -0
- package/project-templates/threejs/.claude/skills/playwright-cli/SKILL.md +390 -0
- package/project-templates/threejs/.claude/skills/playwright-cli/references/element-attributes.md +23 -0
- package/project-templates/threejs/.claude/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/project-templates/threejs/.claude/skills/playwright-cli/references/request-mocking.md +87 -0
- package/project-templates/threejs/.claude/skills/playwright-cli/references/running-code.md +240 -0
- package/project-templates/threejs/.claude/skills/playwright-cli/references/session-management.md +226 -0
- package/project-templates/threejs/.claude/skills/playwright-cli/references/spec-driven-testing.md +308 -0
- package/project-templates/threejs/.claude/skills/playwright-cli/references/storage-state.md +275 -0
- package/project-templates/threejs/.claude/skills/playwright-cli/references/test-generation.md +134 -0
- package/project-templates/threejs/.claude/skills/playwright-cli/references/tracing.md +142 -0
- package/project-templates/threejs/.claude/skills/playwright-cli/references/video-recording.md +153 -0
- package/project-templates/threejs/.claude/skills/session-analyzer/SKILL.md +386 -0
- package/project-templates/threejs/.claude/skills/session-analyzer/scripts/execution-breakdown.mjs +182 -0
- package/project-templates/threejs/.claude/skills/session-analyzer/scripts/find-turns.mjs +72 -0
- package/project-templates/threejs/.claude/skills/session-analyzer/scripts/heavy-output.mjs +121 -0
- package/project-templates/threejs/.claude/skills/session-analyzer/scripts/resolve-session.mjs +103 -0
- package/project-templates/threejs/.claude/skills/session-analyzer/scripts/subagent-stats.mjs +127 -0
- package/project-templates/threejs/.claude/skills/session-analyzer/scripts/subagent-tool-timeline.mjs +106 -0
- package/project-templates/threejs/.claude/skills/session-analyzer/scripts/time-gaps.mjs +128 -0
- package/project-templates/threejs/.claude/skills/session-analyzer/scripts/turn-timeline.mjs +67 -0
- package/project-templates/threejs/.claude/snapshot.mjs +257 -0
- package/project-templates/threejs/.cursor/hooks.json +17 -0
- package/project-templates/threejs/.cursor/rules/playcraft-orchestrator.mdc +142 -0
- package/project-templates/threejs/.cursor/rules/playcraft-subagent-boundary.mdc +29 -0
- package/project-templates/threejs/.playcraft-engine +1 -0
- package/project-templates/threejs/CHANGELOG.md +105 -0
- package/project-templates/threejs/CLAUDE.md +84 -0
- package/project-templates/threejs/assets/audio/bgm/.gitkeep +0 -0
- package/project-templates/threejs/assets/audio/sfx/.gitkeep +0 -0
- package/project-templates/threejs/assets/bundles/.gitkeep +0 -0
- package/project-templates/threejs/assets/images/bg/.gitkeep +0 -0
- package/project-templates/threejs/assets/images/reference/.gitkeep +0 -0
- package/project-templates/threejs/assets/images/storyboard/.gitkeep +0 -0
- package/project-templates/threejs/assets/images/tiles/.gitkeep +0 -0
- package/project-templates/threejs/assets/images/ui/.gitkeep +0 -0
- package/project-templates/threejs/assets/images/vfx/.gitkeep +0 -0
- package/project-templates/threejs/assets/models/.gitkeep +0 -0
- package/project-templates/threejs/docs/DESIGN.md +65 -0
- package/project-templates/threejs/docs/intent-clarifications.md +58 -0
- package/project-templates/threejs/docs/pipeline-overview.md +69 -0
- package/project-templates/threejs/docs/project-state.md +211 -0
- package/project-templates/threejs/docs/scaffold-manifest.md +10 -0
- package/project-templates/threejs/docs/team/agent-conduct.md +67 -0
- package/project-templates/threejs/docs/team/agent-runtime-matrix.md +80 -0
- package/project-templates/threejs/docs/team/atom-tree-format.md +114 -0
- package/project-templates/threejs/docs/team/collaboration.md +391 -0
- package/project-templates/threejs/docs/team/core-model.md +85 -0
- package/project-templates/threejs/docs/team/game-designer-migration-todo.md +8 -0
- package/project-templates/threejs/docs/team/harness-legacy-deprecation.md +23 -0
- package/project-templates/threejs/docs/team/platform-capabilities.md +18 -0
- package/project-templates/threejs/docs/team/workflow-changelog.md +137 -0
- package/project-templates/threejs/docs/team/workflow-consistency-checklist.md +240 -0
- package/project-templates/threejs/focus-lab/.gitkeep +0 -0
- package/project-templates/threejs/game/config/.gitkeep +0 -0
- package/project-templates/threejs/game/config/game-config.ts +46 -0
- package/project-templates/threejs/game/contract/publish.ts +141 -0
- package/project-templates/threejs/game/contract/types.ts +34 -0
- package/project-templates/threejs/game/gameplay/.gitkeep +0 -0
- package/project-templates/threejs/game/index.ts +76 -0
- package/project-templates/threejs/game/runtime-contract.ts +15 -0
- package/project-templates/threejs/game/scenes/.gitkeep +0 -0
- package/project-templates/threejs/globals.d.ts +51 -0
- package/project-templates/threejs/index.css +34 -0
- package/project-templates/threejs/index.html +18 -0
- package/project-templates/threejs/main.ts +9 -0
- package/project-templates/threejs/package.json +79 -0
- package/project-templates/threejs/skills/README.md +52 -0
- package/project-templates/threejs/skills/_shared/agent-prep.md +136 -0
- package/project-templates/threejs/skills/_shared/requirements.txt +1 -0
- package/project-templates/threejs/skills/_shared/scripts/build-evidence-gate.mjs +87 -0
- package/project-templates/threejs/skills/_shared/scripts/context-budget.mjs +215 -0
- package/project-templates/threejs/skills/_shared/scripts/create-playable.mjs +140 -0
- package/project-templates/threejs/skills/_shared/scripts/dev-start.mjs +151 -0
- package/project-templates/threejs/skills/_shared/scripts/disclosure-config.mjs +480 -0
- package/project-templates/threejs/skills/_shared/scripts/ensure-project.mjs +532 -0
- package/project-templates/threejs/skills/_shared/scripts/gen-placeholder-audio.py +345 -0
- package/project-templates/threejs/skills/_shared/scripts/gen-placeholder.py +244 -0
- package/project-templates/threejs/skills/_shared/scripts/harness-smoke.mjs +257 -0
- package/project-templates/threejs/skills/_shared/scripts/lib/validator-artifacts.mjs +129 -0
- package/project-templates/threejs/skills/_shared/scripts/placeholder-pack-sheet.mjs +67 -0
- package/project-templates/threejs/skills/_shared/scripts/placeholder_audio_lib.py +142 -0
- package/project-templates/threejs/skills/_shared/scripts/placeholder_lib.py +993 -0
- package/project-templates/threejs/skills/_shared/scripts/render-project-state.mjs +278 -0
- package/project-templates/threejs/skills/_shared/scripts/ring-config.mjs +252 -0
- package/project-templates/threejs/skills/_shared/scripts/skills-check.mjs +176 -0
- package/project-templates/threejs/skills/_shared/scripts/slice-set.mjs +77 -0
- package/project-templates/threejs/skills/_shared/scripts/state-advance-ring.mjs +63 -0
- package/project-templates/threejs/skills/_shared/scripts/state-get.mjs +215 -0
- package/project-templates/threejs/skills/_shared/scripts/state-handoff.mjs +120 -0
- package/project-templates/threejs/skills/_shared/scripts/state-set.mjs +113 -0
- package/project-templates/threejs/skills/_shared/scripts/state-store.mjs +893 -0
- package/project-templates/threejs/skills/_shared/scripts/sync-template-skills.mjs +112 -0
- package/project-templates/threejs/skills/_shared/scripts/verify-asset-code-sync.mjs +265 -0
- package/project-templates/threejs/skills/_shared/scripts/verify-atom-conformance.mjs +181 -0
- package/project-templates/threejs/skills/_shared/scripts/verify-env.mjs +285 -0
- package/project-templates/threejs/skills/_shared/scripts/verify-placeholders.mjs +161 -0
- package/project-templates/threejs/skills/_shared/scripts/workflow-doctor.mjs +348 -0
- package/project-templates/threejs/skills/playable-animate/SKILL.md +52 -0
- package/project-templates/threejs/skills/playable-animate/agents/openai.yaml +4 -0
- package/project-templates/threejs/skills/playable-animate/references/board-refresh.md +30 -0
- package/project-templates/threejs/skills/playable-animate/references/diagnosis-checklist.md +25 -0
- package/project-templates/threejs/skills/playable-animate/references/entrance-sequencing.md +33 -0
- package/project-templates/threejs/skills/playable-animate/references/interaction-timing.md +23 -0
- package/project-templates/threejs/skills/playable-art/SKILL.md +203 -0
- package/project-templates/threejs/skills/playable-art/agents/openai.yaml +4 -0
- package/project-templates/threejs/skills/playable-art/references/alpha-postprocess.md +46 -0
- package/project-templates/threejs/skills/playable-art/references/cta-art-spec.md +59 -0
- package/project-templates/threejs/skills/playable-art/references/layout-proportions.md +30 -0
- package/project-templates/threejs/skills/playable-art/references/prompt-patterns.md +83 -0
- package/project-templates/threejs/skills/playable-art/references/reference-comparison.md +30 -0
- package/project-templates/threejs/skills/playable-art/references/sheet-import-v2.md +91 -0
- package/project-templates/threejs/skills/playable-art/scripts/build-atlas.py +332 -0
- package/project-templates/threejs/skills/playable-art/scripts/chroma-key.py +50 -0
- package/project-templates/threejs/skills/playable-art/scripts/extract-alpha-split.py +65 -0
- package/project-templates/threejs/skills/playable-art/scripts/extract-dual-bg-alpha.py +118 -0
- package/project-templates/threejs/skills/playable-art/scripts/import-gpt-art-sheets-v2.py +403 -0
- package/project-templates/threejs/skills/playable-art/scripts/import-gpt-art-sheets.py +280 -0
- package/project-templates/threejs/skills/playable-art/scripts/import-gpt-tile-sheet.py +114 -0
- package/project-templates/threejs/skills/playable-art/scripts/import_gpt_art_pipeline.py +321 -0
- package/project-templates/threejs/skills/playable-art/scripts/import_gpt_art_sheets_v2_test.py +126 -0
- package/project-templates/threejs/skills/playable-art/scripts/placeholder_common.py +98 -0
- package/project-templates/threejs/skills/playable-art/scripts/remove-bg.py +91 -0
- package/project-templates/threejs/skills/playable-art/scripts/review-processed-assets.py +468 -0
- package/project-templates/threejs/skills/playable-art/scripts/review_processed_assets_test.py +116 -0
- package/project-templates/threejs/skills/playable-art/scripts/verify-alpha-clean.py +105 -0
- package/project-templates/threejs/skills/playable-debug/SKILL.md +116 -0
- package/project-templates/threejs/skills/playable-debug/agents/openai.yaml +4 -0
- package/project-templates/threejs/skills/playable-debug/references/debug-config.md +40 -0
- package/project-templates/threejs/skills/playable-focus/SKILL.md +148 -0
- package/project-templates/threejs/skills/playable-focus/scripts/bootstrap-playground.mjs +84 -0
- package/project-templates/threejs/skills/playable-focus/scripts/extract-focus-frames.mjs +87 -0
- package/project-templates/threejs/skills/playable-focus/scripts/generate-focus-reports.mjs +121 -0
- package/project-templates/threejs/skills/playable-focus/scripts/playground.mjs +390 -0
- package/project-templates/threejs/skills/playable-focus/templates/focus-lab/frame.html +39 -0
- package/project-templates/threejs/skills/playable-focus/templates/focus-lab/harness/clean-stage.js +114 -0
- package/project-templates/threejs/skills/playable-focus/templates/focus-lab/harness/frame-lab-scene.js +94 -0
- package/project-templates/threejs/skills/playable-focus/templates/focus-lab/harness/particle-lab-scene.js +224 -0
- package/project-templates/threejs/skills/playable-focus/templates/focus-lab/model.html +16 -0
- package/project-templates/threejs/skills/playable-focus/templates/focus-lab/particle.html +38 -0
- package/project-templates/threejs/skills/playable-focus/templates/focus-lab/shatter.html +47 -0
- package/project-templates/threejs/skills/playable-fx-decompose/SKILL.md +212 -0
- package/project-templates/threejs/skills/playable-fx-decompose/examples.md +89 -0
- package/project-templates/threejs/skills/playable-fx-decompose/reference.md +201 -0
- package/project-templates/threejs/skills/playable-fx-decompose/requirements.txt +2 -0
- package/project-templates/threejs/skills/playable-fx-decompose/scripts/build-blank-tile-crack.py +192 -0
- package/project-templates/threejs/skills/playable-fx-decompose/scripts/fix-shard-debris-frames.py +60 -0
- package/project-templates/threejs/skills/playable-fx-decompose/scripts/generate-shard-debris.py +276 -0
- package/project-templates/threejs/skills/playable-fx-peak/SKILL.md +237 -0
- package/project-templates/threejs/skills/playable-fx-peak/requirements.txt +2 -0
- package/project-templates/threejs/skills/playable-fx-peak/scripts/fix-frame-edges.py +70 -0
- package/project-templates/threejs/skills/playable-fx-peak/scripts/repack-frame-sequence.py +526 -0
- package/project-templates/threejs/skills/playable-fx-peak/scripts/validate-frame-anchors.py +130 -0
- package/project-templates/threejs/skills/playable-placeholder/SKILL.md +239 -0
- package/project-templates/threejs/skills/playable-record/SKILL.md +166 -0
- package/project-templates/threejs/skills/playable-record/scripts/lib/build-artifact.mjs +92 -0
- package/project-templates/threejs/skills/playable-record/scripts/lib/dev-server.mjs +104 -0
- package/project-templates/threejs/skills/playable-record/scripts/lib/record-audio-bridge.js +141 -0
- package/project-templates/threejs/skills/playable-record/scripts/lib/slice-structure-verify.mjs +199 -0
- package/project-templates/threejs/skills/playable-record/scripts/record-playable.mjs +583 -0
- package/project-templates/threejs/skills/playable-record/scripts/verify-build.mjs +185 -0
- package/project-templates/threejs/skills/playable-record/scripts/verify-contract.mjs +261 -0
- package/project-templates/threejs/skills/playable-record/scripts/verify-firstwin.mjs +398 -0
- package/project-templates/threejs/skills/playable-record/scripts/verify-fusion.mjs +320 -0
- package/project-templates/threejs/skills/playable-record/scripts/verify-lifecycle.mjs +78 -0
- package/project-templates/threejs/skills/playable-record/scripts/verify-slice-artist.mjs +53 -0
- package/project-templates/threejs/skills/playable-record/scripts/verify-slice-ta.mjs +53 -0
- package/project-templates/threejs/skills/playable-record/scripts/verify-slice.mjs +142 -0
- package/project-templates/threejs/skills/playable-record/scripts/verify-visual-ring.mjs +250 -0
- package/project-templates/threejs/skills/playable-record/scripts/verify-visual-ring1.mjs +190 -0
- package/project-templates/threejs/skills/playable-record/scripts/verify-vlm-video.mjs +227 -0
- package/project-templates/threejs/skills/playable-validate/SKILL.md +260 -0
- package/project-templates/threejs/skills/playable-validate/references/checklist-detail.md +36 -0
- package/project-templates/threejs/skills/playable-validate/references/single-html-bundle.md +76 -0
- package/project-templates/threejs/skills/playable-validate/validation-rules.md +200 -0
- package/project-templates/threejs/skills/playable-verify-ui/SKILL.md +295 -0
- package/project-templates/threejs/skills/playable-verify-ui/examples/asset-manifest.example.json +36 -0
- package/project-templates/threejs/skills/playable-verify-ui/examples/mahjong-match.profile.yaml +334 -0
- package/project-templates/threejs/skills/playable-verify-ui/profiles/template.profile.yaml +84 -0
- package/project-templates/threejs/skills/playable-verify-ui/reference.md +165 -0
- package/project-templates/threejs/skills/playable-verify-ui/references/phaser-patterns.md +67 -0
- package/project-templates/threejs/skills/playable-verify-ui/references/scaling-decision-tree.md +51 -0
- package/project-templates/threejs/skills/playable-verify-ui/requirements.txt +7 -0
- package/project-templates/threejs/skills/playable-verify-ui/schema/asset-manifest.schema.json +68 -0
- package/project-templates/threejs/skills/playable-verify-ui/schema/playable-ui-profile.schema.json +171 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/asset_manifest.py +231 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/lib/__init__.py +1 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/lib/config_expr.js +218 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/lib/node_utils.js +51 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/lib/profile_loader.py +59 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/lib/report.py +106 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/lib/runtime_contract.js +116 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/measure_frame_content.py +77 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/run_all.py +265 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_asset_sizes.py +77 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_assets_present.py +62 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_atlas_budget.py +84 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_atlas_frames.py +79 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_bg_clean.py +365 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_doc_asset_sync.py +90 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_fetch_antipatterns.py +59 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_game_config_load.js +54 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_hardcoded_layout.js +64 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_layout_regions.js +110 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_profile_schema.py +37 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_ring1_visual.py +211 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_runtime_contract.js +58 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_script_load_order.js +73 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_slice_plan.py +238 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_slice_plan_test.py +125 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_text_overlap.py +333 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_tile_consistency.py +323 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_vision_evidence.py +894 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_vision_evidence_test.py +122 -0
- package/project-templates/threejs/skills/playable-verify-ui/scripts/verify_visual.py +422 -0
- package/project-templates/threejs/ta-workspace/scripts/.gitkeep +0 -0
- package/project-templates/threejs/templates/artist-log.template.md +182 -0
- package/project-templates/threejs/templates/atom-tree.template.json +133 -0
- package/project-templates/threejs/templates/design-brief.template.md +92 -0
- package/project-templates/threejs/templates/design-lens-checklist.reference.md +117 -0
- package/project-templates/threejs/templates/design-methodology.md +99 -0
- package/project-templates/threejs/templates/design.template.md +50 -0
- package/project-templates/threejs/templates/developer-log.template.md +258 -0
- package/project-templates/threejs/templates/five-axis-framework.md +194 -0
- package/project-templates/threejs/templates/gen-placeholder-audio.template.mjs +63 -0
- package/project-templates/threejs/templates/gen-placeholders.template.mjs +81 -0
- package/project-templates/threejs/templates/intent-clarifications.template.md +58 -0
- package/project-templates/threejs/templates/layout-spec.template.md +198 -0
- package/project-templates/threejs/templates/plan.template.md +80 -0
- package/project-templates/threejs/templates/project-state.template.md +136 -0
- package/project-templates/threejs/templates/review-report.template.md +275 -0
- package/project-templates/threejs/templates/style-exploration.template.md +93 -0
- package/project-templates/threejs/templates/ta-log.template.md +394 -0
- package/project-templates/threejs/tsconfig.json +20 -0
- package/project-templates/threejs/vite.config.ts +27 -0
- package/dist/atom-plan/validate-atom-plan.js +0 -298
- package/project-template/.claude/agents/designer.md +0 -124
- package/project-template/.claude/agents/pm.md +0 -166
- package/project-template/.claude/agents/refs/designer-style-exploration-flow.md +0 -67
- package/project-template/.claude/agents/refs/pm-workflow-detail.md +0 -567
- package/project-template/.claude/hooks/validate-atom-plan.mjs +0 -224
- package/project-template/.claude/skills/playcraft-workflow/SKILL.md +0 -485
- package/project-template/docs/team/atom-plan-format.md +0 -173
- package/project-template/templates/atom-plan.template.json +0 -44
- package/project-template/templates/atom-plan.template.md +0 -108
- /package/dist/{atom-plan → asr}/validate-asr-coverage.js +0 -0
- /package/{project-template → engine-packs/phaser}/game/config/.gitkeep +0 -0
- /package/{project-template → engine-packs/phaser}/game/gameplay/.gitkeep +0 -0
- /package/{project-template → engine-packs/phaser}/game/scenes/.gitkeep +0 -0
- /package/project-template/.claude/agents/refs/{designer-art-style-catalog.md → artist-art-style-catalog.md} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* PlayCraft workflow STOP validator — shared by Claude Code (SubagentStop) and Cursor (subagentStop).
|
|
4
|
-
* Ensures TA / Developer filled logs
|
|
4
|
+
* Ensures Artist / TA / Developer filled required workflow logs and valid project-state handoff before subagent may stop.
|
|
5
5
|
*
|
|
6
6
|
* Exit 0 = pass
|
|
7
7
|
* Exit 2 = block (Claude: decision block; Cursor: block stop)
|
|
@@ -10,56 +10,446 @@
|
|
|
10
10
|
|
|
11
11
|
import fs from 'node:fs';
|
|
12
12
|
import path from 'node:path';
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
13
14
|
import { fileURLToPath } from 'node:url';
|
|
14
|
-
import {
|
|
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';
|
|
15
22
|
|
|
16
23
|
const __filename = fileURLToPath(import.meta.url);
|
|
24
|
+
const require = createRequire(import.meta.url);
|
|
17
25
|
|
|
18
26
|
const INTAKE_HEADING = '## Upstream Intake';
|
|
19
27
|
const SKILL_PREFLIGHT_HEADING = '## Skill Preflight';
|
|
20
28
|
const PLAN_HEADING = {
|
|
21
29
|
'technical-artist': '## Production Plan',
|
|
22
|
-
developer:
|
|
30
|
+
developer: SKILL_PREFLIGHT_HEADING,
|
|
23
31
|
};
|
|
24
|
-
const
|
|
32
|
+
const PLAN_HEADING_BY_LOOP = {
|
|
25
33
|
developer: {
|
|
26
|
-
|
|
27
|
-
ui_rework: '## UI Pass Plan',
|
|
28
|
-
gameplay_pass: '## Gameplay Pass Plan',
|
|
34
|
+
build: SKILL_PREFLIGHT_HEADING,
|
|
29
35
|
},
|
|
30
36
|
};
|
|
31
37
|
const PLAN_REQUIRED_SUBS = {
|
|
32
38
|
developer: {
|
|
33
|
-
|
|
34
|
-
ui_rework: ['### Scene-Asset Binding Plan', '### Scene navigation', '### Risk Checklist'],
|
|
35
|
-
gameplay_pass: ['### PGS Strategy', '### Risk Checklist'],
|
|
39
|
+
build: [],
|
|
36
40
|
},
|
|
37
41
|
'technical-artist': ['### Coverage Plan', '### Atlas Assembly Plan', '### Risk Checklist'],
|
|
38
42
|
};
|
|
39
43
|
const PLACEHOLDER_RE = /\{\{[^}]+\}\}/;
|
|
40
44
|
const MIN_TAKEAWAY_LEN = 8;
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
'docs/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
+
};
|
|
51
77
|
|
|
52
|
-
const
|
|
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
|
+
};
|
|
53
88
|
|
|
54
|
-
//
|
|
55
|
-
const
|
|
89
|
+
// Artist: Skill Preflight enforced in ring 3+ build and ring 4 Wave 1.
|
|
90
|
+
const ARTIST_REQUIRED_DOCS = [];
|
|
56
91
|
|
|
57
92
|
const ROLE_LOG = {
|
|
58
|
-
'technical-artist': { log: 'logs/ta-log.md',
|
|
59
|
-
developer: { log: 'logs/developer-log.md',
|
|
60
|
-
|
|
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
|
+
],
|
|
61
125
|
};
|
|
62
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
|
+
|
|
63
453
|
/** @param {string} raw */
|
|
64
454
|
function parseHookInput(raw) {
|
|
65
455
|
if (!raw.trim()) return {};
|
|
@@ -80,7 +470,7 @@ function normalizeRole(s) {
|
|
|
80
470
|
|
|
81
471
|
/**
|
|
82
472
|
* @param {Record<string, unknown>} input
|
|
83
|
-
* @returns {'technical-artist' | 'developer' | 'designer' | null}
|
|
473
|
+
* @returns {'technical-artist' | 'developer' | 'artist' | 'reviewer' | 'game-designer' | 'skill-selector' | null}
|
|
84
474
|
*/
|
|
85
475
|
function detectRole(input) {
|
|
86
476
|
const candidates = [
|
|
@@ -95,8 +485,11 @@ function detectRole(input) {
|
|
|
95
485
|
for (const c of candidates) {
|
|
96
486
|
const n = normalizeRole(String(c));
|
|
97
487
|
if (n.includes('technical-artist') || n === 'ta') return 'technical-artist';
|
|
98
|
-
if (n === 'developer' || n === 'dev') return 'developer';
|
|
99
|
-
if (n === '
|
|
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';
|
|
100
493
|
}
|
|
101
494
|
|
|
102
495
|
const blobs = [
|
|
@@ -117,6 +510,422 @@ function detectRole(input) {
|
|
|
117
510
|
return null;
|
|
118
511
|
}
|
|
119
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
|
+
|
|
120
929
|
/** @param {string} cell */
|
|
121
930
|
function isReadChecked(cell) {
|
|
122
931
|
const t = cell.trim();
|
|
@@ -142,11 +951,12 @@ function parseUpstreamIntake(content) {
|
|
|
142
951
|
const section = nextHeading === -1 ? after : after.slice(0, nextHeading);
|
|
143
952
|
|
|
144
953
|
const rows = [];
|
|
954
|
+
const unmatchedRows = [];
|
|
145
955
|
for (const line of section.split('\n')) {
|
|
146
956
|
const trimmed = line.trim();
|
|
147
957
|
if (!trimmed.startsWith('|')) continue;
|
|
148
958
|
if (/^\|\s*---/.test(trimmed)) continue;
|
|
149
|
-
if (/^\|\s*Doc
|
|
959
|
+
if (/^\|\s*Doc(ument)?\s*\|/i.test(trimmed)) continue;
|
|
150
960
|
|
|
151
961
|
const parts = trimmed
|
|
152
962
|
.split('|')
|
|
@@ -156,13 +966,24 @@ function parseUpstreamIntake(content) {
|
|
|
156
966
|
if (parts.length < 3) continue;
|
|
157
967
|
|
|
158
968
|
const doc = parts[0].replace(/^`/, '').replace(/`$/, '').trim();
|
|
159
|
-
if (!doc.startsWith('docs/') && !doc.startsWith('logs/'))
|
|
969
|
+
if (!doc.startsWith('docs/') && !doc.startsWith('logs/')) {
|
|
970
|
+
unmatchedRows.push(doc || '(empty)');
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
160
973
|
|
|
161
974
|
rows.push({ doc, read: parts[1], takeaway: parts[2] });
|
|
162
975
|
}
|
|
163
976
|
|
|
164
977
|
if (rows.length === 0) {
|
|
165
|
-
|
|
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
|
+
}
|
|
166
987
|
}
|
|
167
988
|
|
|
168
989
|
return { rows, errors };
|
|
@@ -197,17 +1018,17 @@ function validateIntakeContent(content, requiredDocs) {
|
|
|
197
1018
|
/**
|
|
198
1019
|
* @param {string} content
|
|
199
1020
|
* @param {'technical-artist' | 'developer'} role
|
|
200
|
-
* @param {
|
|
1021
|
+
* @param {'clarify'|'build'|'verify'|null} [loop]
|
|
201
1022
|
*/
|
|
202
|
-
function validatePlan(content, role,
|
|
1023
|
+
function validatePlan(content, role, loop) {
|
|
203
1024
|
let heading = PLAN_HEADING[role];
|
|
204
1025
|
/** @type {string[]} */
|
|
205
1026
|
let requiredSubs = [];
|
|
206
1027
|
|
|
207
1028
|
if (role === 'developer') {
|
|
208
|
-
if (
|
|
209
|
-
heading =
|
|
210
|
-
requiredSubs = PLAN_REQUIRED_SUBS.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] ?? [];
|
|
211
1032
|
}
|
|
212
1033
|
} else if (role === 'technical-artist') {
|
|
213
1034
|
requiredSubs = PLAN_REQUIRED_SUBS['technical-artist'] ?? [];
|
|
@@ -216,7 +1037,7 @@ function validatePlan(content, role, stage) {
|
|
|
216
1037
|
const errors = [];
|
|
217
1038
|
const start = content.indexOf(heading);
|
|
218
1039
|
if (start === -1) {
|
|
219
|
-
return [`Missing "${heading}" section — write plan before STOP (
|
|
1040
|
+
return [`Missing "${heading}" section — write plan before STOP (loop: ${loop || 'unknown'})`];
|
|
220
1041
|
}
|
|
221
1042
|
|
|
222
1043
|
const after = content.slice(start + heading.length);
|
|
@@ -242,48 +1063,53 @@ function validatePlan(content, role, stage) {
|
|
|
242
1063
|
return errors;
|
|
243
1064
|
}
|
|
244
1065
|
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
const STAGE_MARKDOWN_BOLD_RE = new RegExp(`\\*\\*${STAGE_NAMES}\\*\\*`, 'i');
|
|
249
|
-
const STAGE_BACKTICK_RE = new RegExp(`stage[=:\\s]+\`${STAGE_NAMES}\``, 'i');
|
|
250
|
-
// YAML form inside ```yaml ... ``` block: `stage: ui_pass` (no backticks around value)
|
|
251
|
-
const STAGE_YAML_RE = new RegExp(`^\\s*stage:\\s*${STAGE_NAMES}\\s*$`, 'im');
|
|
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;
|
|
252
1069
|
|
|
253
1070
|
/**
|
|
254
|
-
* Extract current `stage` value from `docs/project-state.md`. Returns null if not found.
|
|
255
|
-
* Tries three forms (in order):
|
|
256
|
-
* 1. Markdown bold: `**ui_pass**` — used in Current Stage header
|
|
257
|
-
* 2. Backtick form: `` stage: `ui_pass` `` — used in prose
|
|
258
|
-
* 3. YAML form: `stage: ui_pass` — used in `## Agent handoff` YAML fence (most authoritative)
|
|
259
1071
|
* @param {string} projectDir
|
|
1072
|
+
* @returns {number|null}
|
|
260
1073
|
*/
|
|
261
|
-
function
|
|
1074
|
+
function readIteration(projectDir) {
|
|
262
1075
|
const statePath = path.join(projectDir, 'docs/project-state.md');
|
|
263
1076
|
if (!fs.existsSync(statePath)) return null;
|
|
264
1077
|
const content = fs.readFileSync(statePath, 'utf8');
|
|
265
|
-
const m =
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
return m ? m[1].toLowerCase() : null;
|
|
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;
|
|
270
1082
|
}
|
|
271
1083
|
|
|
272
|
-
|
|
1084
|
+
/**
|
|
1085
|
+
* @param {string} projectDir
|
|
1086
|
+
* @returns {'clarify'|'build'|'verify'|null}
|
|
1087
|
+
*/
|
|
1088
|
+
function readLoop(projectDir) {
|
|
273
1089
|
const statePath = path.join(projectDir, 'docs/project-state.md');
|
|
274
1090
|
if (!fs.existsSync(statePath)) return null;
|
|
275
1091
|
const content = fs.readFileSync(statePath, 'utf8');
|
|
276
|
-
const m =
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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;
|
|
280
1106
|
}
|
|
281
1107
|
|
|
282
1108
|
/**
|
|
283
|
-
* Validate
|
|
1109
|
+
* Validate Artist Skill Preflight section (Phase 2 / production only).
|
|
284
1110
|
* @param {string} content
|
|
285
1111
|
*/
|
|
286
|
-
function
|
|
1112
|
+
function validateArtistSkillPreflight(content) {
|
|
287
1113
|
const errors = [];
|
|
288
1114
|
const start = content.indexOf(SKILL_PREFLIGHT_HEADING);
|
|
289
1115
|
if (start === -1) {
|
|
@@ -327,12 +1153,183 @@ function validateDesignerSkillPreflight(content) {
|
|
|
327
1153
|
}
|
|
328
1154
|
|
|
329
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.
|
|
330
1159
|
* @param {string} projectDir
|
|
331
|
-
* @
|
|
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.
|
|
332
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
|
+
|
|
333
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
|
+
|
|
334
1328
|
const cfg = ROLE_LOG[role];
|
|
1329
|
+
const iteration = readIteration(projectDir) || 1;
|
|
1330
|
+
const loop = readLoop(projectDir);
|
|
335
1331
|
const logPath = path.join(projectDir, cfg.log);
|
|
1332
|
+
const requiredDocs = requiredDocsForRole(role, iteration);
|
|
336
1333
|
|
|
337
1334
|
if (!fs.existsSync(logPath)) {
|
|
338
1335
|
return [
|
|
@@ -341,32 +1338,406 @@ function validateRole(projectDir, role) {
|
|
|
341
1338
|
}
|
|
342
1339
|
|
|
343
1340
|
const content = fs.readFileSync(logPath, 'utf8');
|
|
1341
|
+
const regressionErrors = validateNonRegression(projectDir, iteration);
|
|
1342
|
+
const contextErrors = validateHandoffContext(projectDir, role);
|
|
344
1343
|
|
|
345
|
-
if (role === '
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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];
|
|
349
1350
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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`);
|
|
354
1375
|
}
|
|
355
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 {
|
|
356
1406
|
return [];
|
|
357
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
|
+
}
|
|
358
1468
|
|
|
359
|
-
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
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;
|
|
363
1735
|
}
|
|
364
1736
|
|
|
365
1737
|
/** @param {string} projectDir */
|
|
366
1738
|
function resolveProjectDir(projectDir) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
return { skip: true, reason: 'no docs/project-state.md' };
|
|
1739
|
+
if (!stateExists(projectDir)) {
|
|
1740
|
+
return { skip: true, reason: 'no docs/project-state.json or .md' };
|
|
370
1741
|
}
|
|
371
1742
|
return { skip: false };
|
|
372
1743
|
}
|
|
@@ -388,29 +1759,54 @@ async function readStdin() {
|
|
|
388
1759
|
|
|
389
1760
|
export {
|
|
390
1761
|
detectRole,
|
|
1762
|
+
roleFromActiveSlice,
|
|
1763
|
+
validateActiveSliceResolved,
|
|
1764
|
+
validateResolvedSliceStop,
|
|
1765
|
+
hasUnresolvedActiveSlice,
|
|
1766
|
+
isSliceResolved,
|
|
391
1767
|
parseUpstreamIntake,
|
|
392
1768
|
validateIntakeContent,
|
|
393
1769
|
validatePlan,
|
|
394
|
-
|
|
1770
|
+
validateArtistSkillPreflight,
|
|
1771
|
+
validateDeveloperRuntimeContract,
|
|
395
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,
|
|
396
1788
|
readStage,
|
|
397
1789
|
readGatePending,
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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,
|
|
401
1797
|
PLAN_HEADING,
|
|
402
1798
|
PLAN_REQUIRED_SUBS,
|
|
403
1799
|
ROLE_LOG,
|
|
1800
|
+
validateSkillAndPlaceholderEvidence,
|
|
1801
|
+
validateDevUrlDebugFlag,
|
|
1802
|
+
readSelectedAtomsForLink,
|
|
1803
|
+
extractMarkdownSection,
|
|
1804
|
+
countFilledTableRows,
|
|
404
1805
|
};
|
|
405
1806
|
|
|
406
1807
|
async function main() {
|
|
407
1808
|
const stdin = await readStdin();
|
|
408
1809
|
const input = parseHookInput(stdin);
|
|
409
|
-
const role = detectRole(input);
|
|
410
|
-
|
|
411
|
-
if (!role) {
|
|
412
|
-
process.exit(0);
|
|
413
|
-
}
|
|
414
1810
|
|
|
415
1811
|
const projectDir =
|
|
416
1812
|
process.env.CLAUDE_PROJECT_DIR ||
|
|
@@ -422,24 +1818,68 @@ async function main() {
|
|
|
422
1818
|
process.exit(0);
|
|
423
1819
|
}
|
|
424
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
|
+
|
|
425
1837
|
const errors = validateRole(projectDir, role);
|
|
426
1838
|
if (errors.length > 0) {
|
|
427
|
-
const
|
|
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;
|
|
428
1847
|
const section =
|
|
429
|
-
role === '
|
|
430
|
-
?
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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- ')}`;
|
|
435
1861
|
emitBlock(reason);
|
|
436
1862
|
}
|
|
437
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
|
+
|
|
438
1875
|
process.exit(0);
|
|
439
1876
|
}
|
|
440
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.
|
|
441
1881
|
const isMain =
|
|
442
|
-
process.argv[1] && path.
|
|
1882
|
+
process.argv[1] && path.basename(process.argv[1]) === path.basename(__filename);
|
|
443
1883
|
|
|
444
1884
|
if (isMain) {
|
|
445
1885
|
main().catch((err) => {
|