@playcraft/cli 0.0.40 → 0.0.42
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/README.md +66 -3
- package/dist/atom-plan/validate-atom-plan.js +298 -0
- package/dist/cli-root-help.js +1 -1
- package/dist/commands/3d.js +363 -0
- package/dist/commands/create.js +337 -0
- package/dist/commands/image.js +1337 -43
- package/dist/commands/recommend.js +1 -1
- package/dist/commands/remix.js +213 -0
- package/dist/commands/skills.js +1379 -0
- package/dist/commands/tools-3d.js +473 -0
- package/dist/commands/tools-generation.js +452 -0
- package/dist/commands/tools-project.js +400 -0
- package/dist/commands/tools-research.js +37 -0
- package/dist/commands/tools-research.test.js +216 -0
- package/dist/commands/tools-utils.js +183 -0
- package/dist/commands/tools.js +7 -616
- package/dist/config.js +2 -0
- package/dist/index.js +19 -1
- package/dist/utils/version-checker.js +8 -11
- package/package.json +9 -3
- package/project-template/.claude/agents/designer.md +120 -0
- package/project-template/.claude/agents/developer.md +124 -0
- package/project-template/.claude/agents/pm.md +164 -0
- package/project-template/.claude/agents/refs/README.md +73 -0
- package/project-template/.claude/agents/refs/designer-art-style-catalog.md +533 -0
- package/project-template/.claude/agents/refs/designer-color-audio-recipes.md +153 -0
- package/project-template/.claude/agents/refs/designer-deliverable-spec.md +191 -0
- package/project-template/.claude/agents/refs/designer-dimension-axis.md +27 -0
- package/project-template/.claude/agents/refs/designer-handoff-v2-checklist.md +68 -0
- package/project-template/.claude/agents/refs/designer-master-composite-recipes.md +208 -0
- package/project-template/.claude/agents/refs/designer-style-exploration-flow.md +37 -0
- package/project-template/.claude/agents/refs/developer-dev-handoff.md +109 -0
- package/project-template/.claude/agents/refs/developer-impl-cookbook.md +134 -0
- package/project-template/.claude/agents/refs/developer-phase1-flow.md +136 -0
- package/project-template/.claude/agents/refs/pm-workflow-detail.md +551 -0
- package/project-template/.claude/agents/refs/reviewer-convergence-eval.md +130 -0
- package/project-template/.claude/agents/refs/reviewer-six-dimension-eval.md +6 -0
- package/project-template/.claude/agents/refs/ta-3d-flip-recipe.md +85 -0
- package/project-template/.claude/agents/refs/ta-atlas-deliverable-standard.md +67 -0
- package/project-template/.claude/agents/refs/ta-batch-pipeline-recipes.md +120 -0
- package/project-template/.claude/agents/refs/ta-image-generation-detail.md +356 -0
- package/project-template/.claude/agents/refs/ta-image-ops-reference.md +495 -0
- package/project-template/.claude/agents/refs/ta-pipeline-cookbook.md +1108 -0
- package/project-template/.claude/agents/refs/ta-tools-reference.md +111 -0
- package/project-template/.claude/agents/refs/ta-vfx-preset-catalog.md +365 -0
- package/project-template/.claude/agents/reviewer.md +127 -0
- package/project-template/.claude/agents/technical-artist.md +122 -0
- package/project-template/.claude/hooks/README.md +44 -0
- package/project-template/.claude/hooks/validate-atom-plan.mjs +224 -0
- package/project-template/.claude/hooks/validate-workflow-stop.mjs +343 -0
- package/project-template/.claude/settings.json +36 -0
- package/project-template/.claude/settings.local.json +4 -0
- package/project-template/.claude/skills/playcraft-ad-psychology/SKILL.md +182 -0
- package/project-template/.claude/skills/playcraft-art-style-guide/SKILL.md +123 -0
- package/project-template/.claude/skills/playcraft-asset-state-sheet/SKILL.md +141 -0
- package/project-template/.claude/skills/playcraft-audio-generation/SKILL.md +280 -0
- package/project-template/.claude/skills/playcraft-batch-pipeline/SKILL.md +184 -0
- package/project-template/.claude/skills/playcraft-build-optimizer/SKILL.md +306 -0
- package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +279 -0
- package/project-template/.claude/skills/playcraft-image-generation/reference/build-sprite-sheet.template.mjs +123 -0
- package/project-template/.claude/skills/playcraft-image-generation/reference/compare-style.template.mjs +254 -0
- package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch-sprite.template.mjs +235 -0
- package/project-template/.claude/skills/playcraft-image-generation/reference/gen-batch.template.mjs +97 -0
- package/project-template/.claude/skills/playcraft-image-generation/reference/gen-edit-variants.template.mjs +118 -0
- package/project-template/.claude/skills/playcraft-image-generation/reference/process-batch.template.mjs +137 -0
- package/project-template/.claude/skills/playcraft-image-generation/reference/prompt-cookbook.md +397 -0
- package/project-template/.claude/skills/playcraft-image-generation/reference/validate-sprite-sheet.template.mjs +296 -0
- package/project-template/.claude/skills/playcraft-image-ops/SKILL.md +122 -0
- package/project-template/.claude/skills/playcraft-masking/SKILL.md +373 -0
- package/project-template/.claude/skills/playcraft-research/SKILL.md +212 -0
- package/project-template/.claude/skills/playcraft-sprite-generation/SKILL.md +423 -0
- package/project-template/.claude/skills/playcraft-storyboard/SKILL.md +167 -0
- package/project-template/.claude/skills/playcraft-style-qa/SKILL.md +270 -0
- package/project-template/.claude/skills/playcraft-text-rendering/SKILL.md +236 -0
- package/project-template/.claude/skills/playcraft-vfx-animation/SKILL.md +130 -0
- package/project-template/.claude/skills/playcraft-workflow/SKILL.md +485 -0
- package/project-template/.claude/skills/playwright-cli/SKILL.md +390 -0
- package/project-template/.claude/skills/playwright-cli/references/element-attributes.md +23 -0
- package/project-template/.claude/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/project-template/.claude/skills/playwright-cli/references/request-mocking.md +87 -0
- package/project-template/.claude/skills/playwright-cli/references/running-code.md +240 -0
- package/project-template/.claude/skills/playwright-cli/references/session-management.md +226 -0
- package/project-template/.claude/skills/playwright-cli/references/spec-driven-testing.md +312 -0
- package/project-template/.claude/skills/playwright-cli/references/storage-state.md +275 -0
- package/project-template/.claude/skills/playwright-cli/references/test-generation.md +138 -0
- package/project-template/.claude/skills/playwright-cli/references/tracing.md +142 -0
- package/project-template/.claude/skills/playwright-cli/references/video-recording.md +157 -0
- package/project-template/.cursor/hooks.json +17 -0
- package/project-template/.cursor/rules/playcraft-orchestrator.mdc +137 -0
- package/project-template/.cursor/rules/playcraft-subagent-boundary.mdc +18 -0
- package/project-template/CLAUDE.md +280 -0
- package/project-template/assets/audio/bgm/.gitkeep +0 -0
- package/project-template/assets/audio/sfx/.gitkeep +0 -0
- package/project-template/assets/bundles/.gitkeep +0 -0
- package/project-template/assets/images/bg/.gitkeep +0 -0
- package/project-template/assets/images/reference/.gitkeep +0 -0
- package/project-template/assets/images/storyboard/.gitkeep +0 -0
- package/project-template/assets/images/tiles/.gitkeep +0 -0
- package/project-template/assets/images/ui/.gitkeep +0 -0
- package/project-template/assets/images/vfx/.gitkeep +0 -0
- package/project-template/assets/models/.gitkeep +0 -0
- package/project-template/docs/team/agent-conduct.md +121 -0
- package/project-template/docs/team/agent-runtime-matrix.md +62 -0
- package/project-template/docs/team/atom-plan-format.md +105 -0
- package/project-template/docs/team/collaboration.md +297 -0
- package/project-template/docs/team/core-model.md +50 -0
- package/project-template/docs/team/platform-capabilities.md +15 -0
- package/project-template/docs/team/workflow-changelog.md +65 -0
- package/project-template/docs/team/workflow-consistency-checklist.md +140 -0
- package/project-template/game/config/.gitkeep +0 -0
- package/project-template/game/gameplay/.gitkeep +0 -0
- package/project-template/game/scenes/.gitkeep +0 -0
- package/project-template/logs/.gitkeep +0 -0
- package/project-template/ta-workspace/logs/.gitkeep +0 -0
- package/project-template/ta-workspace/scripts/.gitkeep +0 -0
- package/project-template/ta-workspace/tmp/.gitkeep +0 -0
- package/project-template/templates/atom-plan.template.json +26 -0
- package/project-template/templates/atom-plan.template.md +108 -0
- package/project-template/templates/design-brief.template.md +195 -0
- package/project-template/templates/design-lens-checklist.reference.md +117 -0
- package/project-template/templates/design-methodology.md +99 -0
- package/project-template/templates/designer-log.template.md +114 -0
- package/project-template/templates/developer-log.template.md +134 -0
- package/project-template/templates/five-axis-framework.md +186 -0
- package/project-template/templates/intent-clarifications.template.md +58 -0
- package/project-template/templates/layout-spec.template.md +146 -0
- package/project-template/templates/project-state.template.md +237 -0
- package/project-template/templates/review-report.template.md +91 -0
- package/project-template/templates/style-exploration.template.md +93 -0
- package/project-template/templates/ta-log.template.md +343 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "TA: (1) style-faithful mass production from MC/ASR, (2) 100% assetMapping, (3) production-grade assets for Developer — not gameplay or build."
|
|
3
|
+
allowedTools:
|
|
4
|
+
- "Read"
|
|
5
|
+
- "Grep"
|
|
6
|
+
- "Glob"
|
|
7
|
+
- "Write"
|
|
8
|
+
- "Edit"
|
|
9
|
+
- "Skill"
|
|
10
|
+
- "Bash(ls:*)"
|
|
11
|
+
- "Bash(cat:*)"
|
|
12
|
+
- "Bash(find:*)"
|
|
13
|
+
- "Bash(mkdir:*)"
|
|
14
|
+
- "Bash(cp:*)"
|
|
15
|
+
- "Bash(mv:*)"
|
|
16
|
+
- "Bash(rm:*)"
|
|
17
|
+
- "Bash(playcraft image:*)"
|
|
18
|
+
- "Bash(playcraft 3d:*)"
|
|
19
|
+
- "Bash(playcraft tools:*)"
|
|
20
|
+
- "Bash(playcraft audio:*)"
|
|
21
|
+
- "Bash(playcraft skills:*)"
|
|
22
|
+
- "Bash(node:*)"
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
> **First Step**: Read `docs/project-state.md` → **`## Agent handoff`** → **## Runtime** → branch; read `refs/` only when Runtime says so. STOP: [STOP sync checklist](../../docs/team/collaboration.md#stop-sync-checklist).
|
|
26
|
+
|
|
27
|
+
# Technical Artist — Playable Ads Production Agent
|
|
28
|
+
|
|
29
|
+
## Agent Conduct
|
|
30
|
+
|
|
31
|
+
> Full: [docs/team/agent-conduct.md](../../docs/team/agent-conduct.md). **On invoke, follow ## Runtime**; Spec Quick-Check at production entry; orchestrator owns `stage`.
|
|
32
|
+
|
|
33
|
+
## Runtime (on invoke)
|
|
34
|
+
|
|
35
|
+
1. Read `docs/project-state.md` → parse `## Agent handoff` YAML.
|
|
36
|
+
2. If `subagent_stop: true` and `subagent: technical-artist` and `waiting_for: orchestrator` → **Already STOPPED — waiting for orchestrator**.
|
|
37
|
+
3. Branch:
|
|
38
|
+
|
|
39
|
+
| Condition | This round only | On STOP |
|
|
40
|
+
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
41
|
+
| `stage: production`, Wave 1 done | Confirm Wave 1 = done → Spec Quick-Check → Style Interpretation → **Production Plan** → bulk → Compliance; ≤5 files → CLI, >5 → `ta-workspace/scripts/` | When all TA atoms `done`: `next_orchestrator_action: "Set stage=ui_pass, invoke @developer"` |
|
|
42
|
+
| `devStatus: blocked_upstream`, routeTo TA | Fix listed paths only → ta-log | `next_orchestrator_action: "Re-invoke @developer"` |
|
|
43
|
+
| `stage: ui_rework`, Action Items routeTo TA | Fix report items at contract paths | `next_orchestrator_action: "Re-invoke @developer after ui_pass/ui_rework"` |
|
|
44
|
+
|
|
45
|
+
4. **Do not** change `stage`. Update Production Pipeline Wave 2 + handoff + `--- PLAYCRAFT_STOP ---` (`role: technical-artist`).
|
|
46
|
+
|
|
47
|
+
**Track done:** **Production Plan** complete (Coverage + Atlas Assembly + Risk Checklist all `[x]`); all `assignTo: TA` atoms `done` + Compliance Gate green + atlas/WebP + sidecars per **`refs/ta-atlas-deliverable-standard.md`** + `ta-log.md` manifest complete.
|
|
48
|
+
|
|
49
|
+
## Mission
|
|
50
|
+
|
|
51
|
+
> Team mission: see [CLAUDE.md](../../CLAUDE.md#mission). Stage 链路与上下游: [CLAUDE.md § Stage Model](../../CLAUDE.md#stage-model).
|
|
52
|
+
|
|
53
|
+
## Goals
|
|
54
|
+
|
|
55
|
+
**Top 3 (in order):** (1) **Style-faithful mass production** from MC / ASR — **do not redefine art direction**. (2) **100% `assetMapping`** — every path, dims/format per `layout-spec`. (3) **Production-grade handoff** — no placeholders; `ta-log.md` + sprite/atlas params complete.
|
|
56
|
+
|
|
57
|
+
**Success:** Step 0 pass or `spec-gap` filed; Style Interpretation + **Production Plan** (100% assetMapping coverage) before bulk; micro-batch ≥3 before bulk; Compliance green; Developer can verify every path with `playcraft image|audio info`.
|
|
58
|
+
|
|
59
|
+
**Non-goals:** gameplay design, first-level tuning, **file-size optimization** (build stage), MC/storyboard (Designer), `npm run dev` (Developer).
|
|
60
|
+
|
|
61
|
+
## Step 0 (one-liner)
|
|
62
|
+
|
|
63
|
+
Before generation: complete **`ta-log.md` § Upstream Intake** (read 四件套 + `style-exploration` + `designer-log` per [`agent-conduct.md`](../../docs/team/agent-conduct.md)); confirm **every `assetMapping` row** has explicit size/format + **`logs/designer-log.md`** has composite + Style Intent Notes — else `spec-gap` / `blocked`. Then execute **[`refs/ta-pipeline-cookbook.md`](refs/ta-pipeline-cookbook.md)** (**Step 0 Pre → Step 0a → 0 → 0e → 1 → 2**). **Step 0 Pre (mediaGroups Reuse)** is mandatory — check `skillsMatch.mediaGroups` for pre-matched reusable assets before generating anything; link available assets directly. **Step 0e (Transparency Classification)** is mandatory before any batch generation — classify every asset's transparency need, select chroma key per color conflict matrix, generate with correct chroma, then dark-bg verify post remove-background.
|
|
64
|
+
|
|
65
|
+
## Execute (L2)
|
|
66
|
+
|
|
67
|
+
| Ref | When |
|
|
68
|
+
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
|
|
69
|
+
| **[`refs/ta-pipeline-cookbook.md`](refs/ta-pipeline-cookbook.md)** | Step 0b–D, pipeline model, ASR extraction rules, stages A–D, **full Compliance Gate**, reference-image matrix, DAG edits |
|
|
70
|
+
| **[`refs/ta-atlas-deliverable-standard.md`](refs/ta-atlas-deliverable-standard.md)** | **WebP** runtime images, atlas `.webp` + `.json`, digit strips, production/conversion commands |
|
|
71
|
+
|
|
72
|
+
**CLI depth:** [`refs/ta-tools-reference.md`](refs/ta-tools-reference.md). **Skills** (as cookbook triggers): `playcraft-masking`, `playcraft-sprite-generation`, `playcraft-vfx-animation`, `playcraft-style-qa`, `playcraft-text-rendering`, `playcraft-image-generation`, `playcraft-image-ops`.
|
|
73
|
+
|
|
74
|
+
## File Access
|
|
75
|
+
|
|
76
|
+
**Read:** `docs/project-state.md`, `design-brief.md`, `layout-spec.md` (`assetMapping`), `atom-plan.json` (`assignTo: TA`), `atom-plan.md` (TA Skill Context), `style-exploration.md`, `logs/designer-log.md`.
|
|
77
|
+
|
|
78
|
+
**Write:** `logs/ta-log.md`; `atom-plan.json` (`atoms[].status`); `atom-plan.md` (TA Skill Context); `docs/project-state.md` (tracking); `assets/images/`, `assets/audio/`; `ta-workspace/scripts/`.
|
|
79
|
+
|
|
80
|
+
## Compliance Gate (condensed)
|
|
81
|
+
|
|
82
|
+
| Check | Pass |
|
|
83
|
+
| ------------------ | -------------------------------------------------------------------------------------- |
|
|
84
|
+
| Atlas group table | `layout-spec.md` atlas grouping table exists → else `spec-gap` routeTo PM |
|
|
85
|
+
| Coverage | Every `assetMapping` path exists; dimensions match spec |
|
|
86
|
+
| Format | **WebP** for runtime images / atlases (see atlas ref); **MP3** after TA audio pipeline |
|
|
87
|
+
| Atlases | Per `layout-spec`: grouped assets → one `.webp` + `.json` where required |
|
|
88
|
+
| Params | `ta-log.md` records cols, frameW, frameH, frameCount for every sheet/atlas |
|
|
89
|
+
| **Transparency** | ta-log.md has Transparency Classification table (all rows verified, no `❓`) |
|
|
90
|
+
| **Alpha channel** | All transparent assets have `channels=4`; no `channels=3` in transparent deliverables |
|
|
91
|
+
| **Dark-bg verify** | All transparent assets pass dark-background overlay check (no white/chroma residue) |
|
|
92
|
+
| **BG full-cover** | All full-screen backgrounds: edge-to-edge, no fade/vignette/white corners |
|
|
93
|
+
| Process | Style interpretation table in `ta-log.md`; no open TA questions in ICP |
|
|
94
|
+
| Isolation | No undeclared external deps on deliverables |
|
|
95
|
+
|
|
96
|
+
> TA ignores playable **file-size budget**; optimize at Developer `playcraft build`.
|
|
97
|
+
|
|
98
|
+
## Developer upstream rework
|
|
99
|
+
|
|
100
|
+
When Developer blocks with `routeTo: TA` (or `devStatus: blocked_upstream`): read `intent-clarifications.md` + blocker table; fix assets at **contract paths**; re-run Compliance for affected items; update `ta-log.md`. Done when Dev self-check can pass — not merely files on disk.
|
|
101
|
+
|
|
102
|
+
## Identity
|
|
103
|
+
|
|
104
|
+
**Designer = 生 (creation)**; **TA = 产 (production)** — extract → batch → comply. **Working style:** ≤5 files → CLI; >5 → `ta-workspace/scripts/` (Node.js).
|
|
105
|
+
|
|
106
|
+
## Important Rules
|
|
107
|
+
|
|
108
|
+
1. **Production Plan before generation** — complete `logs/ta-log.md` § Production Plan (Risk Checklist all `[x]`) before bulk; hook enforces on STOP.
|
|
109
|
+
2. **Transparency Classification before generation** — auto-derive `needsAlpha` from assetMapping path convention (`bg/`=opaque, `ui/`/`vfx/`/`txt/`=alpha) and `bgStrategy` from element color palette (see Step 0e color matrix). Complete Step 0e table before any batch generation. **Never** default to green screen — gold/yellow/orange/VFX/text assets MUST use blue screen.
|
|
110
|
+
3. **Use CLI transparency tool chain** — `remove-background` (floodfill first, `--method ai` as fallback), `segment` (SAM3, for complex shapes or MC/ASR extraction), `decompose-layers` (for multi-layer MC decomposition). Never skip these tools — "generate and done" without remove-background is the root cause of all white-bg/black-block quality issues.
|
|
111
|
+
4. **mediaGroups first, generate second** — read `atom-plan.json` → `skillsMatch.mediaGroups`; for each matched media asset, `playcraft skills link` it to the contract path before generating from scratch. Only generate when mediaGroups has no match or quality is insufficient (document skip reason in `dagRevisions`).
|
|
112
|
+
5. **Skill Preflight before production** — read all relevant playcraft pipeline Skills (`playcraft-image-generation`, `playcraft-masking`, `playcraft-sprite-generation`, `playcraft-text-rendering`, etc.) BEFORE producing any asset. Fill `ta-log.md` § Skill Preflight table. Do not "freelance" — follow the platform's established best practices.
|
|
113
|
+
6. **Update atom status on completion** — after each atom is done: update `atom-plan.json` → `atoms[].status = "done"` + `atoms[].actualOutput = "<path>"`. After writing TA Skill Context: update `atom-plan.md` § TA Skill Context. **Never** leave atoms at `pending` / `actualOutput: null` after delivery.
|
|
114
|
+
7. **Extract first** — Step 0b references before batch generation.
|
|
115
|
+
8. **Batch multi-element sets** — `playcraft-sprite-generation` (not one-off loops).
|
|
116
|
+
9. **`assetMapping` is law** — zero missing or off-path deliveries.
|
|
117
|
+
10. **Compliance before done** — `playcraft image info` / `audio info` on every deliverable path.
|
|
118
|
+
11. **Dark-background verify all transparent assets** — after remove-background, overlay on #1A1A2E dark background to catch white edges, chroma residue, and black blocks. VFX atlas: verify **each frame** individually.
|
|
119
|
+
12. **WebP + atlas sidecars** per `ta-atlas-deliverable-standard` unless `layout-spec` explicitly excepts.
|
|
120
|
+
13. **Never modify `game/`** — Developer's domain.
|
|
121
|
+
14. **Pipeline order** — E→A→B incremental; C₁ earliest; D parallel; update `project-state` / atom-plan after batches.
|
|
122
|
+
15. **Prompt quality = output quality** — five-section prompt + 9-item review before major generates (see sprite skill).
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# PlayCraft workflow hooks
|
|
2
|
+
|
|
3
|
+
Shared validator for **Claude Code** (`.claude/settings.json` → `SubagentStop`) and **Cursor** (`.cursor/hooks.json` → `subagentStop`).
|
|
4
|
+
|
|
5
|
+
## `validate-atom-plan.mjs`
|
|
6
|
+
|
|
7
|
+
When **PM** subagent stops, validates **`docs/atom-plan.json`**:
|
|
8
|
+
|
|
9
|
+
- Every non-empty `skillRef` exists in the skills package (`@playcraft/skills`)
|
|
10
|
+
- `skillRef` must be `*.aigameplay` or `*.aiconfig` (no `playcraft-*`)
|
|
11
|
+
- `skillRef` must appear in `skillsMatch.items` when snapshot is present
|
|
12
|
+
|
|
13
|
+
Runs `playcraft skills validate-atom-plan` when CLI is on `PATH`; otherwise embedded check.
|
|
14
|
+
|
|
15
|
+
On failure: exit `2` — PM must fix atom-plan before STOP.
|
|
16
|
+
|
|
17
|
+
## `validate-workflow-stop.mjs`
|
|
18
|
+
|
|
19
|
+
When a **Technical Artist** or **Developer** subagent stops, checks `logs/ta-log.md` or `logs/developer-log.md`:
|
|
20
|
+
|
|
21
|
+
**§ Upstream Intake**
|
|
22
|
+
|
|
23
|
+
- Every required doc row has Read ✓
|
|
24
|
+
- Every takeaway is filled (no `{{placeholders}}`, min 8 chars)
|
|
25
|
+
|
|
26
|
+
**§ Production Plan** (TA) or **§ UI Pass Plan** / **§ Gameplay Pass Plan** (Developer — validated by `stage` in project-state)
|
|
27
|
+
|
|
28
|
+
- Required subsections present (TA: Coverage / Atlas / Risk; Developer ui_pass: Scene-Asset / Scene navigation / Risk; gameplay_pass: PGS / Risk)
|
|
29
|
+
- Risk Checklist fully checked `[x]`
|
|
30
|
+
- No `{{placeholders}}` in plan section
|
|
31
|
+
|
|
32
|
+
On failure:
|
|
33
|
+
|
|
34
|
+
- **Claude Code**: exit `2` + JSON `{"decision":"block","reason":"..."}` — subagent must fix intake before stopping
|
|
35
|
+
- **Cursor**: exit `2` — blocks subagent stop; stderr shows the same reason
|
|
36
|
+
|
|
37
|
+
Role detection: `agent_type` / `PLAYCRAFT_STOP` footer `role:` in the last message. Other agents (PM, Designer, Reviewer) are skipped.
|
|
38
|
+
|
|
39
|
+
## Enable
|
|
40
|
+
|
|
41
|
+
- **Claude Code**: project hooks load from `.claude/settings.json` automatically (see `/hooks` menu)
|
|
42
|
+
- **Cursor**: reload after editing `.cursor/hooks.json`; verify in **Hooks** output channel
|
|
43
|
+
|
|
44
|
+
Requires **Node.js** on `PATH` for the hook process.
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PM SubagentStop — 校验 docs/atom-plan.json 中 skillRef 均存在于 skills 库。
|
|
4
|
+
* 优先调用 `playcraft skills validate-atom-plan`;若 CLI 不可用则内嵌最小校验。
|
|
5
|
+
*
|
|
6
|
+
* Exit 0 = pass
|
|
7
|
+
* Exit 2 = block (Claude / Cursor subagent stop)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
|
|
17
|
+
const SKILL_REF_EMPTY = new Set(['', '—', '-', '–']);
|
|
18
|
+
const SKILL_REF_SUFFIX_RE = /\.(aigameplay|aiconfig)$/;
|
|
19
|
+
|
|
20
|
+
/** @param {string} raw */
|
|
21
|
+
function parseHookInput(raw) {
|
|
22
|
+
if (!raw.trim()) return {};
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(raw);
|
|
25
|
+
} catch {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** @param {string} s */
|
|
31
|
+
function normalizeRole(s) {
|
|
32
|
+
return String(s || '')
|
|
33
|
+
.toLowerCase()
|
|
34
|
+
.replace(/\s+/g, '-')
|
|
35
|
+
.replace(/_/g, '-');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {Record<string, unknown>} input
|
|
40
|
+
* @returns {'pm' | null}
|
|
41
|
+
*/
|
|
42
|
+
function detectRole(input) {
|
|
43
|
+
const candidates = [
|
|
44
|
+
input.agent_type,
|
|
45
|
+
input.agent_name,
|
|
46
|
+
input.subagent_name,
|
|
47
|
+
input.subagent_type,
|
|
48
|
+
input.subagent,
|
|
49
|
+
input.role,
|
|
50
|
+
].filter(Boolean);
|
|
51
|
+
|
|
52
|
+
for (const c of candidates) {
|
|
53
|
+
const n = normalizeRole(String(c));
|
|
54
|
+
if (n === 'pm' || n.includes('project-manager')) return 'pm';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const blobs = [input.last_assistant_message, input.last_message, input.message, input.output]
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.join('\n');
|
|
60
|
+
if (/---\s*PLAYCRAFT_STOP\s*---/i.test(blobs) && /role:\s*pm\b/i.test(blobs)) return 'pm';
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** @param {string} projectDir */
|
|
65
|
+
function loadSkillAtomIds(projectDir) {
|
|
66
|
+
const dirs = [];
|
|
67
|
+
const envPaths = process.env.AGENT_SKILLS_PATHS?.split(',').map((p) => p.trim()).filter(Boolean) ?? [];
|
|
68
|
+
dirs.push(...envPaths);
|
|
69
|
+
|
|
70
|
+
const nm = path.join(projectDir, 'node_modules', '@playcraft', 'skills', 'skills');
|
|
71
|
+
if (fs.existsSync(nm)) dirs.push(nm);
|
|
72
|
+
|
|
73
|
+
const ids = new Set();
|
|
74
|
+
for (const dir of dirs) {
|
|
75
|
+
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) continue;
|
|
76
|
+
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
77
|
+
if (!ent.isDirectory()) continue;
|
|
78
|
+
const mp = path.join(dir, ent.name, 'manifest.json');
|
|
79
|
+
if (!fs.existsSync(mp)) continue;
|
|
80
|
+
try {
|
|
81
|
+
const m = JSON.parse(fs.readFileSync(mp, 'utf8'));
|
|
82
|
+
if (m.atomId) ids.add(m.atomId);
|
|
83
|
+
} catch {
|
|
84
|
+
// skip
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return ids;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {string} projectDir
|
|
93
|
+
* @returns {{ errors: string[], warnings: string[] }}
|
|
94
|
+
*/
|
|
95
|
+
function validateInline(projectDir) {
|
|
96
|
+
const jsonPath = path.join(projectDir, 'docs', 'atom-plan.json');
|
|
97
|
+
const errors = [];
|
|
98
|
+
const warnings = [];
|
|
99
|
+
|
|
100
|
+
if (!fs.existsSync(jsonPath)) {
|
|
101
|
+
errors.push('缺少 docs/atom-plan.json(从 templates/atom-plan.template.json 创建)');
|
|
102
|
+
return { errors, warnings };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let plan;
|
|
106
|
+
try {
|
|
107
|
+
plan = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
|
108
|
+
} catch (e) {
|
|
109
|
+
errors.push(`atom-plan.json 解析失败:${e.message}`);
|
|
110
|
+
return { errors, warnings };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (plan.schemaVersion !== 1) {
|
|
114
|
+
errors.push(`schemaVersion 须为 1,当前:${plan.schemaVersion}`);
|
|
115
|
+
}
|
|
116
|
+
if (!Array.isArray(plan.atoms)) {
|
|
117
|
+
errors.push('atoms 须为数组');
|
|
118
|
+
return { errors, warnings };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const skillIndex = loadSkillAtomIds(projectDir);
|
|
122
|
+
if (skillIndex.size === 0) {
|
|
123
|
+
errors.push('未找到 skills 库(请 npm install @playcraft/skills 或设置 AGENT_SKILLS_PATHS)');
|
|
124
|
+
return { errors, warnings };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const snapshotIds = new Set((plan.skillsMatch?.items ?? []).map((i) => i.atomId).filter(Boolean));
|
|
128
|
+
|
|
129
|
+
for (const atom of plan.atoms) {
|
|
130
|
+
const ref = atom.skillRef == null ? '' : String(atom.skillRef).trim();
|
|
131
|
+
if (!ref || SKILL_REF_EMPTY.has(ref) || ref.startsWith('{{')) continue;
|
|
132
|
+
|
|
133
|
+
if (!SKILL_REF_SUFFIX_RE.test(ref)) {
|
|
134
|
+
errors.push(`${atom.atomId}: skillRef 须为 *.aigameplay 或 *.aiconfig:${ref}`);
|
|
135
|
+
} else if (ref.startsWith('playcraft-')) {
|
|
136
|
+
errors.push(`${atom.atomId}: 禁止 playcraft-* 平台 skill:${ref}`);
|
|
137
|
+
} else if (!skillIndex.has(ref)) {
|
|
138
|
+
errors.push(`${atom.atomId}: skillRef 不在 skills 库:${ref}`);
|
|
139
|
+
} else if (snapshotIds.size > 0 && !snapshotIds.has(ref)) {
|
|
140
|
+
errors.push(`${atom.atomId}: skillRef ${ref} 不在 skillsMatch.items 中`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (atom.type !== 'GameplayAtom' && atom.type !== 'ConfigAtom') {
|
|
144
|
+
errors.push(`${atom.atomId}: 仅 GameplayAtom/ConfigAtom 可有 skillRef`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!(plan.skillsMatch?.items?.length > 0)) {
|
|
149
|
+
warnings.push('skillsMatch.items 为空 — 请先 playcraft skills match --json 并写入 skillsMatch');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { errors, warnings };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** @param {string} projectDir */
|
|
156
|
+
function runCliValidator(projectDir) {
|
|
157
|
+
const r = spawnSync('playcraft', ['skills', 'validate-atom-plan', '--project-dir', projectDir], {
|
|
158
|
+
encoding: 'utf8',
|
|
159
|
+
cwd: projectDir,
|
|
160
|
+
});
|
|
161
|
+
return { status: r.status ?? 1, stdout: r.stdout ?? '', stderr: r.stderr ?? '' };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function emitBlock(reason) {
|
|
165
|
+
const payload = { decision: 'block', reason };
|
|
166
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
167
|
+
process.stderr.write(`[playcraft-atom-plan] ${reason}\n`);
|
|
168
|
+
process.exit(2);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function readStdin() {
|
|
172
|
+
const chunks = [];
|
|
173
|
+
for await (const chunk of process.stdin) {
|
|
174
|
+
chunks.push(chunk);
|
|
175
|
+
}
|
|
176
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export { detectRole, validateInline };
|
|
180
|
+
|
|
181
|
+
async function main() {
|
|
182
|
+
const stdin = await readStdin();
|
|
183
|
+
const input = parseHookInput(stdin);
|
|
184
|
+
const role = detectRole(input);
|
|
185
|
+
|
|
186
|
+
if (role !== 'pm') {
|
|
187
|
+
process.exit(0);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const projectDir =
|
|
191
|
+
process.env.CLAUDE_PROJECT_DIR ||
|
|
192
|
+
process.env.CURSOR_PROJECT_DIR ||
|
|
193
|
+
process.cwd();
|
|
194
|
+
|
|
195
|
+
const statePath = path.join(projectDir, 'docs', 'project-state.md');
|
|
196
|
+
if (!fs.existsSync(statePath)) {
|
|
197
|
+
process.exit(0);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let cli = runCliValidator(projectDir);
|
|
201
|
+
if (cli.status !== 0 && /ENOENT|not found/i.test(cli.stderr)) {
|
|
202
|
+
const { errors, warnings } = validateInline(projectDir);
|
|
203
|
+
for (const w of warnings) process.stderr.write(`[playcraft-atom-plan] warn: ${w}\n`);
|
|
204
|
+
if (errors.length > 0) {
|
|
205
|
+
emitBlock(`PM STOP blocked — atom-plan skillRef 校验失败:\n- ${errors.join('\n- ')}`);
|
|
206
|
+
}
|
|
207
|
+
process.exit(0);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (cli.status !== 0) {
|
|
211
|
+
const detail = (cli.stderr || cli.stdout).trim() || 'playcraft skills validate-atom-plan failed';
|
|
212
|
+
emitBlock(`PM STOP blocked — ${detail}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
process.exit(0);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const isMain = process.argv[1] && path.resolve(process.argv[1]) === __filename;
|
|
219
|
+
if (isMain) {
|
|
220
|
+
main().catch((err) => {
|
|
221
|
+
process.stderr.write(`[playcraft-atom-plan] ${err.message}\n`);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
});
|
|
224
|
+
}
|