@playcraft/cli 0.0.55 → 0.0.57
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 +18 -0
- package/REMIX.md +115 -19
- package/dist/atom-tree/fusion-hints.js +8 -3
- package/dist/atom-tree/fusion-intent.js +62 -0
- package/dist/atom-tree/selection.js +3 -1
- package/dist/commands/create.js +299 -85
- package/dist/commands/image.js +79 -69
- package/dist/commands/levels.js +29 -0
- package/dist/commands/patterns.js +75 -0
- package/dist/commands/remix.js +93 -0
- package/dist/commands/skills.js +48 -17
- package/dist/index.js +9 -5
- package/dist/remix/clone-source.js +208 -0
- package/dist/remix/clone.js +256 -0
- package/dist/remix/local-git.js +56 -0
- package/dist/remix/pull.js +64 -0
- package/dist/remix/push.js +70 -0
- package/dist/scaffold/templates.js +1 -1
- package/dist/utils/agent-api-client.js +10 -3
- package/dist/utils/remix-image-attachments.js +4 -32
- package/dist/utils/sandbox-path.js +20 -0
- package/package.json +3 -3
- package/project-template/.claude/agents/creative-director.md +114 -0
- package/project-template/.claude/agents/developer.md +5 -2
- package/project-template/.claude/agents/game-designer.md +41 -20
- package/project-template/.claude/agents/refs/README.md +8 -0
- package/project-template/.claude/agents/refs/creative-director-guide.md +35 -0
- package/project-template/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
- package/project-template/.claude/agents/refs/creative-discovery-guide.md +76 -0
- package/project-template/.claude/agents/refs/creative-north-star-examples.md +82 -0
- package/project-template/.claude/agents/refs/developer-dev-handoff.md +1 -1
- package/project-template/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
- package/project-template/.claude/agents/refs/game-designer-gameplay-sufficiency.md +4 -4
- package/project-template/.claude/agents/reviewer.md +11 -0
- package/project-template/.claude/hooks/README.md +12 -12
- package/project-template/.claude/hooks/block-developer-preflight.mjs +49 -13
- package/project-template/.claude/hooks/block-ring1-tasklist.mjs +1 -1
- package/project-template/.claude/hooks/user-prompt.mjs +3 -13
- package/project-template/.claude/hooks/validate-workflow-stop.mjs +209 -12
- package/project-template/.claude/settings.json +2 -2
- package/project-template/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
- package/project-template/.claude/skills/session-analyzer/scripts/resolve-session.mjs +0 -1
- package/project-template/.claude/snapshot.mjs +0 -1
- package/project-template/.codebuddy/settings.json +1 -1
- package/project-template/.cursor/rules/playcraft-orchestrator.mdc +1 -1
- package/project-template/CLAUDE.md +9 -9
- package/project-template/docs/project-state.md +1 -1
- package/project-template/docs/team/agent-conduct.md +18 -18
- package/project-template/docs/team/collaboration.md +19 -7
- package/project-template/docs/team/workflow-changelog.md +15 -0
- package/project-template/fusion-patterns/catalog.json +77 -0
- package/project-template/package.json +2 -0
- package/project-template/skills/_shared/scripts/create-playable.mjs +31 -11
- package/project-template/skills/_shared/scripts/dev-start.mjs +3 -3
- package/project-template/skills/_shared/scripts/disclosure-config.mjs +65 -39
- package/project-template/skills/_shared/scripts/harness-smoke.mjs +3 -1
- package/project-template/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
- package/project-template/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
- package/project-template/skills/_shared/scripts/slice-set.mjs +29 -2
- package/project-template/skills/_shared/scripts/state-get.mjs +124 -7
- package/project-template/skills/_shared/scripts/state-store.mjs +25 -2
- package/project-template/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
- package/project-template/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
- package/project-template/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
- package/project-template/skills/playable-record/scripts/record-playable.mjs +2 -1
- package/project-template/skills/playable-record/scripts/verify-contract.mjs +4 -4
- package/project-template/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
- package/project-template/skills/playable-record/scripts/verify-fusion.mjs +37 -2
- package/project-template/skills/playable-record/scripts/verify-slice.mjs +2 -2
- package/project-template/templates/design-brief.template.md +34 -0
- package/project-template/templates/design-methodology.md +1 -2
- package/project-template/templates/design.template.md +65 -0
- package/project-template/templates/developer-log.template.md +12 -1
- package/project-template/templates/five-axis-framework.md +12 -0
- package/project-template/templates/game-designer-creative-process.template.md +99 -0
- package/project-template/templates/level-sketch.template.json +56 -0
- package/project-template/templates/north-star.template.md +43 -0
- package/project-template/templates/project-state.template.md +1 -1
- package/project-template/vite.config.ts +4 -5
- package/project-templates/phaser/.claude/agents/creative-director.md +114 -0
- package/project-templates/phaser/.claude/agents/game-designer.md +41 -20
- package/project-templates/phaser/.claude/agents/refs/creative-director-guide.md +35 -0
- package/project-templates/phaser/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
- package/project-templates/phaser/.claude/agents/refs/creative-discovery-guide.md +76 -0
- package/project-templates/phaser/.claude/agents/refs/creative-north-star-examples.md +82 -0
- package/project-templates/phaser/.claude/agents/refs/developer-dev-handoff.md +1 -1
- package/project-templates/phaser/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
- package/project-templates/phaser/.claude/agents/reviewer.md +11 -0
- package/project-templates/phaser/.claude/hooks/block-ring1-tasklist.mjs +2 -2
- package/project-templates/phaser/.claude/hooks/user-prompt.mjs +17 -18
- package/project-templates/phaser/.claude/hooks/validate-workflow-stop.mjs +209 -12
- package/project-templates/phaser/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
- package/project-templates/phaser/.cursor/rules/playcraft-orchestrator.mdc +1 -1
- package/project-templates/phaser/CLAUDE.md +88 -54
- package/project-templates/phaser/docs/DESIGN.md +25 -0
- package/project-templates/phaser/docs/project-state.md +1 -1
- package/project-templates/phaser/docs/team/collaboration.md +19 -7
- package/project-templates/phaser/docs/team/workflow-changelog.md +14 -0
- package/project-templates/phaser/fusion-patterns/catalog.json +77 -0
- package/project-templates/phaser/package.json +3 -1
- package/project-templates/phaser/skills/_shared/scripts/dev-start.mjs +3 -3
- package/project-templates/phaser/skills/_shared/scripts/disclosure-config.mjs +65 -39
- package/project-templates/phaser/skills/_shared/scripts/harness-smoke.mjs +3 -1
- package/project-templates/phaser/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
- package/project-templates/phaser/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
- package/project-templates/phaser/skills/_shared/scripts/state-get.mjs +124 -7
- package/project-templates/phaser/skills/_shared/scripts/state-store.mjs +25 -2
- package/project-templates/phaser/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
- package/project-templates/phaser/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
- package/project-templates/phaser/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
- package/project-templates/phaser/skills/playable-record/scripts/record-playable.mjs +2 -1
- package/project-templates/phaser/skills/playable-record/scripts/verify-contract.mjs +4 -4
- package/project-templates/phaser/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
- package/project-templates/phaser/skills/playable-record/scripts/verify-fusion.mjs +37 -2
- package/project-templates/phaser/skills/playable-record/scripts/verify-slice.mjs +2 -2
- package/project-templates/phaser/templates/design-brief.template.md +42 -11
- package/project-templates/phaser/templates/design-methodology.md +1 -2
- package/project-templates/phaser/templates/design.template.md +77 -0
- package/project-templates/phaser/templates/five-axis-framework.md +50 -1
- package/project-templates/phaser/templates/game-designer-creative-process.template.md +99 -0
- package/project-templates/phaser/templates/level-sketch.template.json +56 -0
- package/project-templates/phaser/templates/north-star.template.md +43 -0
- package/project-templates/phaser/templates/project-state.template.md +1 -1
- package/project-templates/phaser/vite.config.ts +4 -5
- package/project-templates/threejs/.claude/agents/creative-director.md +114 -0
- package/project-templates/threejs/.claude/agents/game-designer.md +41 -20
- package/project-templates/threejs/.claude/agents/refs/creative-director-guide.md +35 -0
- package/project-templates/threejs/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
- package/project-templates/threejs/.claude/agents/refs/creative-discovery-guide.md +76 -0
- package/project-templates/threejs/.claude/agents/refs/creative-north-star-examples.md +82 -0
- package/project-templates/threejs/.claude/agents/refs/developer-dev-handoff.md +1 -1
- package/project-templates/threejs/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
- package/project-templates/threejs/.claude/agents/reviewer.md +11 -0
- package/project-templates/threejs/.claude/hooks/block-ring1-tasklist.mjs +2 -2
- package/project-templates/threejs/.claude/hooks/user-prompt.mjs +17 -18
- package/project-templates/threejs/.claude/hooks/validate-workflow-stop.mjs +209 -12
- package/project-templates/threejs/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
- package/project-templates/threejs/.cursor/rules/playcraft-orchestrator.mdc +1 -1
- package/project-templates/threejs/CLAUDE.md +88 -54
- package/project-templates/threejs/docs/DESIGN.md +25 -0
- package/project-templates/threejs/docs/project-state.md +1 -1
- package/project-templates/threejs/docs/team/collaboration.md +19 -7
- package/project-templates/threejs/docs/team/workflow-changelog.md +14 -0
- package/project-templates/threejs/fusion-patterns/catalog.json +77 -0
- package/project-templates/threejs/package.json +3 -1
- package/project-templates/threejs/skills/_shared/scripts/dev-start.mjs +3 -3
- package/project-templates/threejs/skills/_shared/scripts/disclosure-config.mjs +65 -39
- package/project-templates/threejs/skills/_shared/scripts/harness-smoke.mjs +3 -1
- package/project-templates/threejs/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
- package/project-templates/threejs/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
- package/project-templates/threejs/skills/_shared/scripts/state-get.mjs +124 -7
- package/project-templates/threejs/skills/_shared/scripts/state-store.mjs +25 -2
- package/project-templates/threejs/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
- package/project-templates/threejs/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
- package/project-templates/threejs/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
- package/project-templates/threejs/skills/playable-record/scripts/record-playable.mjs +2 -1
- package/project-templates/threejs/skills/playable-record/scripts/verify-contract.mjs +4 -4
- package/project-templates/threejs/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
- package/project-templates/threejs/skills/playable-record/scripts/verify-fusion.mjs +37 -2
- package/project-templates/threejs/skills/playable-record/scripts/verify-slice.mjs +2 -2
- package/project-templates/threejs/templates/design-brief.template.md +42 -11
- package/project-templates/threejs/templates/design-methodology.md +1 -2
- package/project-templates/threejs/templates/design.template.md +77 -0
- package/project-templates/threejs/templates/five-axis-framework.md +50 -1
- package/project-templates/threejs/templates/game-designer-creative-process.template.md +99 -0
- package/project-templates/threejs/templates/level-sketch.template.json +56 -0
- package/project-templates/threejs/templates/north-star.template.md +43 -0
- package/project-templates/threejs/templates/project-state.template.md +1 -1
- package/project-templates/threejs/vite.config.ts +4 -5
- package/project-template/docs/DESIGN.md +0 -65
|
@@ -4,16 +4,16 @@ Hooks for **Claude Code** (`.claude/settings.json`), also shared with **Cursor**
|
|
|
4
4
|
|
|
5
5
|
## Hook inventory
|
|
6
6
|
|
|
7
|
-
| Hook | Event | When | What
|
|
8
|
-
| ---------------------------------- | ---------------------------- | ------------------------------------------------------------------- |
|
|
9
|
-
| `validate-atom-tree-selection.mjs` | `PostToolUse` | atom-tree.json written/edited | Validates selected atoms and skill references
|
|
10
|
-
| `gate-runner.mjs` | manual / `npm run ring:gate` | Before Ring Gate per iteration | Runs objective DoD verifiers, writes `ringDoD` + Ring Gate Runner verdict
|
|
11
|
-
| `gate-dispatcher.mjs` | `PostToolUse` | AskUserQuestion called | Identifies gate confirmations, dispatches to `gate.mjs`
|
|
12
|
-
| `gate.mjs` | (called by dispatcher) | Ring Gate confirmed by user | Runs snapshot, writes gate log, and advances state when accepted
|
|
13
|
-
| `user-prompt.mjs` | `UserPromptSubmit` | Every user prompt | Increments turn counter, snapshots project, writes prompt log; **iteration 1** injects Ring 1 Dispatch Path (orchestrator → subagents)
|
|
14
|
-
| `block-ring1-tasklist.mjs` | `PreToolUse` | TaskCreate/Update/List/Get/Output/Stop | Blocks internal Task lists during iteration 1; **Task / @game-designer / temporary ref-flow / @developer spawn still allowed**
|
|
15
|
-
| `validate-workflow-stop.mjs` | `SubagentStop` | TA/Developer/Artist/Reviewer/Game Designer/temporary ref-flow stops | Validates logs, handoff, and ring docs before subagent may stop
|
|
16
|
-
| `snapshot-milestone.mjs` | `SubagentStop` | Subagent stops | Detects milestone events (gate passed, dev ready) and snapshots
|
|
7
|
+
| Hook | Event | When | What |
|
|
8
|
+
| ---------------------------------- | ---------------------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| `validate-atom-tree-selection.mjs` | `PostToolUse` | atom-tree.json written/edited | Validates selected atoms and skill references |
|
|
10
|
+
| `gate-runner.mjs` | manual / `npm run ring:gate` | Before Ring Gate per iteration | Runs objective DoD verifiers, writes `ringDoD` + Ring Gate Runner verdict |
|
|
11
|
+
| `gate-dispatcher.mjs` | `PostToolUse` | AskUserQuestion called | Identifies gate confirmations, dispatches to `gate.mjs` |
|
|
12
|
+
| `gate.mjs` | (called by dispatcher) | Ring Gate confirmed by user | Runs snapshot, writes gate log, and advances state when accepted |
|
|
13
|
+
| `user-prompt.mjs` | `UserPromptSubmit` | Every user prompt | Increments turn counter, snapshots project, writes prompt log; **iteration 1** injects Ring 1 Dispatch Path (orchestrator → subagents) |
|
|
14
|
+
| `block-ring1-tasklist.mjs` | `PreToolUse` | TaskCreate/Update/List/Get/Output/Stop | Blocks internal Task lists during iteration 1; **Task / @creative-director / @game-designer / temporary ref-flow / @developer spawn still allowed** |
|
|
15
|
+
| `validate-workflow-stop.mjs` | `SubagentStop` | TA/Developer/Artist/Reviewer/Game Designer/temporary ref-flow stops | Validates logs, handoff, and ring docs before subagent may stop |
|
|
16
|
+
| `snapshot-milestone.mjs` | `SubagentStop` | Subagent stops | Detects milestone events (gate passed, dev ready) and snapshots |
|
|
17
17
|
|
|
18
18
|
## `validate-atom-tree-selection.mjs`
|
|
19
19
|
|
|
@@ -84,7 +84,7 @@ Triggered **on every `UserPromptSubmit`**. Actions:
|
|
|
84
84
|
1. **Increment turn counter** — stored in `.claude/logs/session.json`
|
|
85
85
|
2. **Snapshot project** — calls `../snapshot.mjs` with a sanitized prompt prefix as name
|
|
86
86
|
3. **Write prompt log** — appends to `logs/hook.log` with timestamp, turn number, and prompt preview
|
|
87
|
-
4. **Ring 1 dispatch injection** — when `iteration: 1` in state, emits `hookSpecificOutput.additionalContext` with orchestrator dispatch recipe (invoke `@game-designer` / temporary ref-flow / `@developer`; no inline role-play; no internal Task lists)
|
|
87
|
+
4. **Ring 1 dispatch injection** — when `iteration: 1` in state, emits `hookSpecificOutput.additionalContext` with orchestrator dispatch recipe (invoke `@creative-director` → `@game-designer` / temporary ref-flow / `@developer`; no inline role-play; no internal Task lists)
|
|
88
88
|
|
|
89
89
|
Non-blocking — snapshot failures are logged but never prevent the prompt from being processed.
|
|
90
90
|
|
|
@@ -92,7 +92,7 @@ Non-blocking — snapshot failures are logged but never prevent the prompt from
|
|
|
92
92
|
|
|
93
93
|
Triggered **before `TaskCreate` / `TaskUpdate` / `TaskList` / `TaskGet` / `TaskOutput` / `TaskStop`** when `iteration: 1`.
|
|
94
94
|
|
|
95
|
-
- Denies the tool call with `permissionDecision: deny` and points the agent to dispatch subagents via `Task` / `@game-designer` / temporary ref-flow / `@developer`
|
|
95
|
+
- Denies the tool call with `permissionDecision: deny` and points the agent to dispatch subagents via `Task` / `@creative-director` / `@game-designer` / temporary ref-flow / `@developer`
|
|
96
96
|
- Ring 2+ passes through unchanged
|
|
97
97
|
|
|
98
98
|
On deny: exit `2` + JSON `hookSpecificOutput.permissionDecision: deny`
|
|
@@ -44,20 +44,28 @@ function readIteration(projectDir) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** Selected atoms that should be linked into .claude/skills/ (excludes bundled playcraft-* skills). */
|
|
47
|
-
function
|
|
47
|
+
function readAtomTree(projectDir) {
|
|
48
48
|
const jsonPath = path.join(projectDir, 'docs', 'atom-tree.json');
|
|
49
|
-
if (!fs.existsSync(jsonPath)) return [];
|
|
49
|
+
if (!fs.existsSync(jsonPath)) return { exists: false, parseError: false, atoms: [] };
|
|
50
50
|
try {
|
|
51
51
|
const tree = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
|
52
52
|
const atoms = Array.isArray(tree?.meta?.selectedAtoms) ? tree.meta.selectedAtoms : [];
|
|
53
|
-
return
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
return {
|
|
54
|
+
exists: true,
|
|
55
|
+
parseError: false,
|
|
56
|
+
atoms: atoms
|
|
57
|
+
.map((a) => (typeof a === 'string' ? a.trim() : ''))
|
|
58
|
+
.filter((a) => a && !a.startsWith('{{') && !a.startsWith('playcraft-')),
|
|
59
|
+
};
|
|
56
60
|
} catch {
|
|
57
|
-
return [];
|
|
61
|
+
return { exists: true, parseError: true, atoms: [] };
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
64
|
|
|
65
|
+
function readSelectedAtoms(projectDir) {
|
|
66
|
+
return readAtomTree(projectDir).atoms;
|
|
67
|
+
}
|
|
68
|
+
|
|
61
69
|
/** Atoms with no linked dir under .claude/skills/. */
|
|
62
70
|
function missingLinks(projectDir, atoms) {
|
|
63
71
|
const skillsDir = path.join(projectDir, '.claude', 'skills');
|
|
@@ -87,13 +95,28 @@ function countFilledRows(section) {
|
|
|
87
95
|
return count;
|
|
88
96
|
}
|
|
89
97
|
|
|
90
|
-
function
|
|
98
|
+
function missingPreflightEvidence(projectDir, atoms) {
|
|
91
99
|
const logPath = path.join(projectDir, 'logs', 'developer-log.md');
|
|
92
|
-
if (!fs.existsSync(logPath)) return
|
|
100
|
+
if (!fs.existsSync(logPath)) return ['logs/developer-log.md missing'];
|
|
93
101
|
const content = fs.readFileSync(logPath, 'utf8');
|
|
102
|
+
const missing = [];
|
|
103
|
+
if (!content.includes('docs/atom-tree.json')) missing.push('docs/atom-tree.json');
|
|
94
104
|
const section = extractSection(content, '## Skill Preflight');
|
|
95
|
-
if (!section)
|
|
96
|
-
|
|
105
|
+
if (!section) {
|
|
106
|
+
missing.push('logs/developer-log.md § Skill Preflight');
|
|
107
|
+
return missing;
|
|
108
|
+
}
|
|
109
|
+
for (const atom of atoms) {
|
|
110
|
+
const skillPath = `.claude/skills/${atom}/SKILL.md`;
|
|
111
|
+
if (!section.includes(atom) || !section.includes(skillPath)) missing.push(skillPath);
|
|
112
|
+
}
|
|
113
|
+
if (atoms.length === 0 && countFilledRows(section) < 1) missing.push('one non-template Skill Preflight row');
|
|
114
|
+
return missing;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isSkillPreflightFilled(projectDir, atomCount) {
|
|
118
|
+
const atoms = readSelectedAtoms(projectDir);
|
|
119
|
+
return missingPreflightEvidence(projectDir, atoms).length === 0 && atoms.length >= atomCount;
|
|
97
120
|
}
|
|
98
121
|
|
|
99
122
|
function isGameSourcePath(filePath, projectDir) {
|
|
@@ -158,7 +181,18 @@ async function main() {
|
|
|
158
181
|
const iteration = readIteration(projectDir);
|
|
159
182
|
if (iteration < 1 || iteration > 3) process.exit(0);
|
|
160
183
|
|
|
161
|
-
const
|
|
184
|
+
const atomTree = readAtomTree(projectDir);
|
|
185
|
+
if (!atomTree.exists) {
|
|
186
|
+
emitDeny(
|
|
187
|
+
'Developer preflight gate: missing docs/atom-tree.json. '
|
|
188
|
+
+ 'Run the atom-tree selection/scaffold path before editing game/ so dispatchPacket.requiredContext can disclose selected atom Skills.',
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (atomTree.parseError) {
|
|
192
|
+
emitDeny('Developer preflight gate: docs/atom-tree.json is not valid JSON; fix it before editing game/.');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const atoms = atomTree.atoms;
|
|
162
196
|
|
|
163
197
|
const unlinked = missingLinks(projectDir, atoms);
|
|
164
198
|
if (unlinked.length > 0) {
|
|
@@ -168,10 +202,12 @@ async function main() {
|
|
|
168
202
|
);
|
|
169
203
|
}
|
|
170
204
|
|
|
171
|
-
|
|
205
|
+
const missingEvidence = missingPreflightEvidence(projectDir, atoms);
|
|
206
|
+
if (missingEvidence.length > 0) {
|
|
172
207
|
emitDeny(
|
|
173
208
|
'Developer preflight gate: logs/developer-log.md § Skill Preflight is not filled. '
|
|
174
|
-
+
|
|
209
|
+
+ `Missing required reading evidence: ${missingEvidence.join(', ')}. `
|
|
210
|
+
+ 'Before editing game/, read docs/atom-tree.json and .claude/skills/<atom>/SKILL.md for each selected atom, then fill one exact-path row per atom (what it provides + how you use it). '
|
|
175
211
|
+ 'Then generate placeholders (npm run placeholder:gen) and resume coding.',
|
|
176
212
|
);
|
|
177
213
|
}
|
|
@@ -85,7 +85,7 @@ async function main() {
|
|
|
85
85
|
|
|
86
86
|
emitDeny(
|
|
87
87
|
`Ring 1 Dispatch Path: internal Task lists are blocked during iteration 1 (${toolName}). `
|
|
88
|
-
+ `Use Task / @game-designer / temporary ref-flow atom-selection subagent / @developer spawn instead. Run npm run state:get and follow
|
|
88
|
+
+ `Use Task / @creative-director / @game-designer / temporary ref-flow atom-selection subagent / @developer spawn instead. Run npm run state:get and follow docs/team/collaboration.md § Ring 1 engine scaffold.`,
|
|
89
89
|
);
|
|
90
90
|
}
|
|
91
91
|
|
|
@@ -14,11 +14,6 @@ import fs from 'node:fs';
|
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
16
|
import { spawnSync } from 'node:child_process';
|
|
17
|
-
import {
|
|
18
|
-
parseHandoffBlock,
|
|
19
|
-
parseHandoffYaml,
|
|
20
|
-
resolveProjectStatePath,
|
|
21
|
-
} from '../../skills/_shared/scripts/state-handoff.mjs';
|
|
22
17
|
|
|
23
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
24
19
|
const __dirname = path.dirname(__filename);
|
|
@@ -123,7 +118,7 @@ function ring1DirectPathContext(projectDir) {
|
|
|
123
118
|
|
|
124
119
|
const loopGuides = {
|
|
125
120
|
clarify:
|
|
126
|
-
'- loop=clarify: Invoke @game-designer for
|
|
121
|
+
'- loop=clarify (split default): Read creative-director-kickoff-guide.md; translate user intent → Creative question (do not forward mechanism spec verbatim). Invoke @creative-director for Micro-Story Spine + north-star + creative-process, then @game-designer for design spec. After GD STOP, spawn temporary ref-flow atom-selection subagent. Do NOT write docs/DESIGN.md or docs/atom-tree.json as orchestrator.',
|
|
127
122
|
build:
|
|
128
123
|
'- loop=build: If scaffold not done, ensure the temporary atom-selection subagent has stopped, then run validate-atom-tree-selection → init-template → verify:contract → verify:lifecycle. Then Invoke @developer for ring 1 build.',
|
|
129
124
|
verify:
|
|
@@ -134,12 +129,12 @@ function ring1DirectPathContext(projectDir) {
|
|
|
134
129
|
return `[PlayCraft Ring 1 Dispatch Path — harness auto-injected]
|
|
135
130
|
iteration=1 loop=${loop}: you are the orchestrator — dispatch role subagents, do NOT inline Designer/Developer work.
|
|
136
131
|
- Do NOT create internal Task lists (TaskCreate / TaskUpdate / TaskList / TaskGet / TaskOutput / TaskStop).
|
|
137
|
-
- Task / @game-designer / temporary ref-flow atom-selection subagent / @developer spawn IS required when the table below says invoke.
|
|
132
|
+
- Task / @creative-director / @game-designer / temporary ref-flow atom-selection subagent / @developer spawn IS required when the table below says invoke.
|
|
138
133
|
- First command: npm run state:get — use next_orchestrator_action + summary; do NOT open docs/project-state.md in full.
|
|
139
134
|
${loopGuide}
|
|
140
135
|
- Game Designer owns docs/DESIGN.md. The temporary atom-selection subagent owns compact docs/atom-tree.json. Developer owns game/ + verifiers on STOP.
|
|
141
136
|
- No Ring 2+ docs, no Reviewer / Artist / TA, no record:mp4 in ring 1.
|
|
142
|
-
See
|
|
137
|
+
See docs/team/collaboration.md § Ring 1 engine scaffold and .cursor/rules/playcraft-orchestrator.mdc.`;
|
|
143
138
|
}
|
|
144
139
|
|
|
145
140
|
function emitAdditionalContext(text) {
|
|
@@ -181,11 +176,6 @@ async function main() {
|
|
|
181
176
|
];
|
|
182
177
|
writeHookLog(projectDir, lines.join('\n'));
|
|
183
178
|
|
|
184
|
-
const { iteration } = readHandoffState(projectDir);
|
|
185
|
-
if (iteration === 1) {
|
|
186
|
-
emitAdditionalContext(ring1DirectPathContext(projectDir));
|
|
187
|
-
}
|
|
188
|
-
|
|
189
179
|
console.error(`prompt #${state.turns} logged`);
|
|
190
180
|
saveState(projectDir, state);
|
|
191
181
|
}
|
|
@@ -19,6 +19,9 @@ import {
|
|
|
19
19
|
} from '../../skills/_shared/scripts/ring-config.mjs';
|
|
20
20
|
import { normalizeSliceState, readState, stateExists, isSliceResolved } from '../../skills/_shared/scripts/state-store.mjs';
|
|
21
21
|
import { validateBuildArtifactForSlice } from '../../skills/playable-record/scripts/lib/build-artifact.mjs';
|
|
22
|
+
import { verifyDesignConsistency, validateAnimationSpine, validateStorySpine } from '../../skills/_shared/scripts/verify-design-consistency.mjs';
|
|
23
|
+
import { validateSkillFidelity } from '../../skills/_shared/scripts/lib/skill-fidelity.mjs';
|
|
24
|
+
import { isMechanismLocked } from '../../skills/_shared/scripts/disclosure-config.mjs';
|
|
22
25
|
|
|
23
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
24
27
|
const require = createRequire(import.meta.url);
|
|
@@ -43,9 +46,26 @@ const PLAN_REQUIRED_SUBS = {
|
|
|
43
46
|
const PLACEHOLDER_RE = /\{\{[^}]+\}\}/;
|
|
44
47
|
const MIN_TAKEAWAY_LEN = 8;
|
|
45
48
|
|
|
49
|
+
function isCreativeDirectorSplitEnabledForStop() {
|
|
50
|
+
const raw = String(process.env.CREATIVE_DIRECTOR_SPLIT ?? '1').trim().toLowerCase();
|
|
51
|
+
return raw !== '0' && raw !== 'false' && raw !== 'off';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** GD mechanism-lock STOP (before north-star exists). */
|
|
55
|
+
function isGameDesignerMechanismPhase(projectDir) {
|
|
56
|
+
if (!isCreativeDirectorSplitEnabledForStop()) return false;
|
|
57
|
+
if (fs.existsSync(path.join(projectDir, 'docs/north-star.md'))) return false;
|
|
58
|
+
const state = readState(projectDir);
|
|
59
|
+
const ctx = state.handoffContext || {};
|
|
60
|
+
const primarySkill = String(ctx.primaryGameplaySkill ?? '').trim();
|
|
61
|
+
if (!primarySkill || primarySkill === 'unset') return false;
|
|
62
|
+
if (fs.existsSync(path.join(projectDir, 'docs/level-sketch.json'))) return false;
|
|
63
|
+
return isMechanismLocked(projectDir, ctx);
|
|
64
|
+
}
|
|
65
|
+
|
|
46
66
|
/** Required upstream docs scale with growth ring — ring 1 stays minimal (DESIGN only; design-brief is optional scratch). */
|
|
47
67
|
const DEV_REQUIRED_DOCS_BY_RING = {
|
|
48
|
-
1: ['docs/project-state.md', 'docs/DESIGN.md'],
|
|
68
|
+
1: ['docs/project-state.md', 'docs/DESIGN.md', 'docs/atom-tree.json'],
|
|
49
69
|
2: [
|
|
50
70
|
'docs/project-state.md',
|
|
51
71
|
'docs/design-brief.md',
|
|
@@ -95,6 +115,7 @@ const ROLE_LOG = {
|
|
|
95
115
|
artist: { log: 'logs/artist-log.md', label: 'Artist' },
|
|
96
116
|
reviewer: { log: 'logs/review-report.md', label: 'Reviewer' },
|
|
97
117
|
'game-designer': { log: null, label: 'Game Designer' },
|
|
118
|
+
'creative-director': { log: null, label: 'Creative Director' },
|
|
98
119
|
'skill-selector': { log: null, label: 'Temporary Atom Selector' },
|
|
99
120
|
};
|
|
100
121
|
|
|
@@ -488,6 +509,7 @@ function detectRole(input) {
|
|
|
488
509
|
if (n === 'developer-lead' || n === 'developer' || n === 'dev') return 'developer';
|
|
489
510
|
if (n === 'artist') return 'artist';
|
|
490
511
|
if (n === 'reviewer' || n === 'review') return 'reviewer';
|
|
512
|
+
if (n === 'creative-director' || n === 'creative director') return 'creative-director';
|
|
491
513
|
if (n === 'game-designer' || n === 'game designer' || n === 'designer') return 'game-designer';
|
|
492
514
|
if (n === 'skill-selector' || n === 'skill selector' || n === 'ref-flow') return 'skill-selector';
|
|
493
515
|
}
|
|
@@ -803,6 +825,88 @@ function validateReviewerStop(projectDir) {
|
|
|
803
825
|
return errors;
|
|
804
826
|
}
|
|
805
827
|
|
|
828
|
+
/**
|
|
829
|
+
* @param {string} projectDir
|
|
830
|
+
*/
|
|
831
|
+
function validateCreativeDirectorStop(projectDir) {
|
|
832
|
+
const errors = [];
|
|
833
|
+
const handoff = readHandoff(projectDir);
|
|
834
|
+
const state = readState(projectDir);
|
|
835
|
+
|
|
836
|
+
if (handoff.subagent_stop !== 'true') {
|
|
837
|
+
errors.push('Creative Director STOP requires subagent_stop: true');
|
|
838
|
+
}
|
|
839
|
+
if ((handoff.waiting_for || '').toLowerCase() !== 'orchestrator') {
|
|
840
|
+
errors.push('Creative Director STOP requires waiting_for: orchestrator');
|
|
841
|
+
}
|
|
842
|
+
if (!handoff.next_orchestrator_action || handoff.next_orchestrator_action === 'null') {
|
|
843
|
+
errors.push('Creative Director STOP requires next_orchestrator_action (Invoke @game-designer — ring 1 design spec)');
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
for (const doc of ['docs/north-star.md', 'docs/creative-process.md']) {
|
|
847
|
+
errors.push(...validateDocFile(projectDir, doc));
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const northPath = path.join(projectDir, 'docs/north-star.md');
|
|
851
|
+
if (fs.existsSync(northPath)) {
|
|
852
|
+
const north = fs.readFileSync(northPath, 'utf8');
|
|
853
|
+
if (!/One-liner|一句话/i.test(north) || north.replace(/\s/g, '').length < 80) {
|
|
854
|
+
errors.push('docs/north-star.md: missing substantive One-liner / north star fields');
|
|
855
|
+
}
|
|
856
|
+
if (!/Rejected vibe|拒绝/i.test(north)) {
|
|
857
|
+
errors.push('docs/north-star.md: missing Rejected vibe section');
|
|
858
|
+
}
|
|
859
|
+
if (!/Micro-Story\s*Spine/i.test(north)) {
|
|
860
|
+
errors.push('docs/north-star.md: missing Micro-Story Spine section');
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const cpPath = path.join(projectDir, 'docs/creative-process.md');
|
|
865
|
+
let creativeProcessText = '';
|
|
866
|
+
if (fs.existsSync(cpPath)) {
|
|
867
|
+
creativeProcessText = fs.readFileSync(cpPath, 'utf8');
|
|
868
|
+
const candidates = (creativeProcessText.match(/###\s*Candidate\s+[ABC]/gi) || []).length;
|
|
869
|
+
if (candidates < 3) {
|
|
870
|
+
errors.push('docs/creative-process.md: Phase 1 requires ≥3 candidates (A/B/C)');
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
if (fs.existsSync(northPath)) {
|
|
875
|
+
const north = fs.readFileSync(northPath, 'utf8');
|
|
876
|
+
const spineResult = validateStorySpine(north, '', creativeProcessText);
|
|
877
|
+
for (const p of spineResult.problems) {
|
|
878
|
+
errors.push(`docs/north-star.md: ${p}`);
|
|
879
|
+
}
|
|
880
|
+
if (spineResult.metrics.narrativeLevel === 'L3') {
|
|
881
|
+
errors.push('docs/north-star.md: Narrative level L3 forbidden — use L2 or below');
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (fs.existsSync(path.join(projectDir, 'docs/DESIGN.md'))) {
|
|
886
|
+
const designLen = fs.readFileSync(path.join(projectDir, 'docs/DESIGN.md'), 'utf8').length;
|
|
887
|
+
if (designLen > 200) {
|
|
888
|
+
errors.push('Creative Director must not write substantive docs/DESIGN.md — spec phase is @game-designer');
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
const primarySkill = String(state?.handoffContext?.primaryGameplaySkill ?? '').trim();
|
|
893
|
+
if (!primarySkill || primarySkill === 'unset') {
|
|
894
|
+
errors.push('Creative Director STOP requires handoffContext.primaryGameplaySkill (set during mechanism lock)');
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (fs.existsSync(northPath)) {
|
|
898
|
+
const north = fs.readFileSync(northPath, 'utf8');
|
|
899
|
+
const fidelity = validateSkillFidelity(projectDir, { northStarMd: north, payoffOnly: true });
|
|
900
|
+
if (!fidelity.skipped && !fidelity.pass) {
|
|
901
|
+
for (const reason of fidelity.driftReasons) {
|
|
902
|
+
errors.push(`CD PGS fidelity: ${reason}`);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
return errors;
|
|
908
|
+
}
|
|
909
|
+
|
|
806
910
|
/**
|
|
807
911
|
* @param {string} projectDir
|
|
808
912
|
*/
|
|
@@ -826,8 +930,41 @@ function validateGameDesignerStop(projectDir) {
|
|
|
826
930
|
return errors;
|
|
827
931
|
}
|
|
828
932
|
|
|
829
|
-
const ring = Math.min(Math.max(iteration, 1), 4);
|
|
830
933
|
const state = readState(projectDir);
|
|
934
|
+
const ring = Math.min(Math.max(iteration, 1), 4);
|
|
935
|
+
const mechanismPhase = ring === 1 && isGameDesignerMechanismPhase(projectDir);
|
|
936
|
+
|
|
937
|
+
if (mechanismPhase) {
|
|
938
|
+
const ctx = state.handoffContext || {};
|
|
939
|
+
const primarySkill = String(ctx.primaryGameplaySkill ?? '').trim();
|
|
940
|
+
if (!primarySkill || primarySkill === 'unset') {
|
|
941
|
+
errors.push('Mechanism lock STOP requires handoffContext.primaryGameplaySkill');
|
|
942
|
+
}
|
|
943
|
+
if (!isMechanismLocked(projectDir, ctx)) {
|
|
944
|
+
errors.push('Mechanism lock STOP requires docs/atom-tree.json meta.skillsMatch.items non-empty');
|
|
945
|
+
}
|
|
946
|
+
const atomTreePath = path.join(projectDir, 'docs/atom-tree.json');
|
|
947
|
+
if (!fs.existsSync(atomTreePath)) {
|
|
948
|
+
errors.push('Mechanism lock STOP requires docs/atom-tree.json');
|
|
949
|
+
} else {
|
|
950
|
+
errors.push(...validateHandoffContext(projectDir, 'game-designer'));
|
|
951
|
+
const refs = ctx.lastRole?.artifactRefs || [];
|
|
952
|
+
if (!refs.some((r) => String(r).includes('atom-tree.json'))) {
|
|
953
|
+
errors.push('handoffContext.lastRole.artifactRefs must include docs/atom-tree.json');
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
if (fs.existsSync(path.join(projectDir, 'docs/north-star.md'))) {
|
|
957
|
+
errors.push('Mechanism phase must not write docs/north-star.md — creative-director owns north-star');
|
|
958
|
+
}
|
|
959
|
+
if (fs.existsSync(path.join(projectDir, 'docs/DESIGN.md'))) {
|
|
960
|
+
const designLen = fs.readFileSync(path.join(projectDir, 'docs/DESIGN.md'), 'utf8').length;
|
|
961
|
+
if (designLen > 200) {
|
|
962
|
+
errors.push('Mechanism phase must not write substantive docs/DESIGN.md');
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
return errors;
|
|
966
|
+
}
|
|
967
|
+
|
|
831
968
|
const ss = normalizeSliceState(state.sliceState || {});
|
|
832
969
|
const sliceAxisClarify = ss.slices.length > 0 && (ring === 1 || ring === 2);
|
|
833
970
|
|
|
@@ -841,7 +978,25 @@ function validateGameDesignerStop(projectDir) {
|
|
|
841
978
|
if (ring === 1) {
|
|
842
979
|
const designPath = path.join(projectDir, 'docs/DESIGN.md');
|
|
843
980
|
if (fs.existsSync(designPath)) {
|
|
844
|
-
|
|
981
|
+
const designContent = fs.readFileSync(designPath, 'utf8');
|
|
982
|
+
errors.push(...validatePlayableSeedDesign(designContent));
|
|
983
|
+
errors.push(...validateDesignFusionSection(designContent));
|
|
984
|
+
}
|
|
985
|
+
const sketchPath = path.join(projectDir, 'docs/level-sketch.json');
|
|
986
|
+
if (!fs.existsSync(sketchPath) && sliceAxisClarify) {
|
|
987
|
+
errors.push('Ring 1 clarify requires docs/level-sketch.json (executable level sketch)');
|
|
988
|
+
}
|
|
989
|
+
if (ring === 1 && sliceAxisClarify && fs.existsSync(sketchPath)) {
|
|
990
|
+
if (fs.existsSync(designPath)) {
|
|
991
|
+
const designContent = fs.readFileSync(designPath, 'utf8');
|
|
992
|
+
errors.push(...validateAnimationSpine(designContent));
|
|
993
|
+
}
|
|
994
|
+
const designCheck = verifyDesignConsistency(projectDir);
|
|
995
|
+
if (!designCheck.ok) {
|
|
996
|
+
for (const p of designCheck.problems) {
|
|
997
|
+
errors.push(`verify:design: ${p}`);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
845
1000
|
}
|
|
846
1001
|
}
|
|
847
1002
|
|
|
@@ -1318,6 +1473,9 @@ function validateRole(projectDir, role) {
|
|
|
1318
1473
|
if (role === 'reviewer') {
|
|
1319
1474
|
return validateReviewerStop(projectDir);
|
|
1320
1475
|
}
|
|
1476
|
+
if (role === 'creative-director') {
|
|
1477
|
+
return validateCreativeDirectorStop(projectDir);
|
|
1478
|
+
}
|
|
1321
1479
|
if (role === 'game-designer') {
|
|
1322
1480
|
return validateGameDesignerStop(projectDir);
|
|
1323
1481
|
}
|
|
@@ -1393,20 +1551,28 @@ function validateRole(projectDir, role) {
|
|
|
1393
1551
|
}
|
|
1394
1552
|
|
|
1395
1553
|
/** Selected atoms that must be linked into .claude/skills/ (excludes bundled playcraft-* skills). */
|
|
1396
|
-
function
|
|
1554
|
+
function readAtomTreeForPreflight(projectDir) {
|
|
1397
1555
|
const jsonPath = path.join(projectDir, 'docs/atom-tree.json');
|
|
1398
|
-
if (!fs.existsSync(jsonPath)) return [];
|
|
1556
|
+
if (!fs.existsSync(jsonPath)) return { exists: false, parseError: false, atoms: [] };
|
|
1399
1557
|
try {
|
|
1400
1558
|
const tree = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
|
1401
1559
|
const atoms = Array.isArray(tree?.meta?.selectedAtoms) ? tree.meta.selectedAtoms : [];
|
|
1402
|
-
return
|
|
1403
|
-
|
|
1404
|
-
|
|
1560
|
+
return {
|
|
1561
|
+
exists: true,
|
|
1562
|
+
parseError: false,
|
|
1563
|
+
atoms: atoms
|
|
1564
|
+
.map((a) => (typeof a === 'string' ? a.trim() : ''))
|
|
1565
|
+
.filter((a) => a && !a.startsWith('{{') && !a.startsWith('playcraft-')),
|
|
1566
|
+
};
|
|
1405
1567
|
} catch {
|
|
1406
|
-
return [];
|
|
1568
|
+
return { exists: true, parseError: true, atoms: [] };
|
|
1407
1569
|
}
|
|
1408
1570
|
}
|
|
1409
1571
|
|
|
1572
|
+
function readSelectedAtomsForLink(projectDir) {
|
|
1573
|
+
return readAtomTreeForPreflight(projectDir).atoms;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1410
1576
|
/** Extract a markdown section body from `heading` up to the next same-or-higher-level heading. */
|
|
1411
1577
|
function extractMarkdownSection(content, heading) {
|
|
1412
1578
|
const start = content.indexOf(heading);
|
|
@@ -1437,6 +1603,21 @@ function countFilledTableRows(section) {
|
|
|
1437
1603
|
return count;
|
|
1438
1604
|
}
|
|
1439
1605
|
|
|
1606
|
+
function missingSelectedAtomPreflightRows(content, atoms) {
|
|
1607
|
+
const missing = [];
|
|
1608
|
+
if (!content.includes('docs/atom-tree.json')) missing.push('docs/atom-tree.json');
|
|
1609
|
+
const preflight = extractMarkdownSection(content, '## Skill Preflight');
|
|
1610
|
+
if (!preflight) {
|
|
1611
|
+
missing.push('logs/developer-log.md § Skill Preflight');
|
|
1612
|
+
return missing;
|
|
1613
|
+
}
|
|
1614
|
+
for (const atom of atoms) {
|
|
1615
|
+
const skillPath = `.claude/skills/${atom}/SKILL.md`;
|
|
1616
|
+
if (!preflight.includes(atom) || !preflight.includes(skillPath)) missing.push(skillPath);
|
|
1617
|
+
}
|
|
1618
|
+
return missing;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1440
1621
|
/**
|
|
1441
1622
|
* Shared skill-link + Skill Preflight + placeholder materialization evidence gate.
|
|
1442
1623
|
* Applied at every ring's Developer build STOP so "use placeholders / implement per skill"
|
|
@@ -1447,7 +1628,15 @@ function countFilledTableRows(section) {
|
|
|
1447
1628
|
*/
|
|
1448
1629
|
function validateSkillAndPlaceholderEvidence(projectDir, content) {
|
|
1449
1630
|
const errors = [];
|
|
1450
|
-
const
|
|
1631
|
+
const atomTree = readAtomTreeForPreflight(projectDir);
|
|
1632
|
+
const atoms = atomTree.atoms;
|
|
1633
|
+
|
|
1634
|
+
if (!atomTree.exists) {
|
|
1635
|
+
errors.push('Skill Preflight: missing docs/atom-tree.json — dispatchPacket.requiredContext cannot disclose selected atom Skills');
|
|
1636
|
+
}
|
|
1637
|
+
if (atomTree.parseError) {
|
|
1638
|
+
errors.push('Skill Preflight: docs/atom-tree.json must be valid JSON before Developer STOP');
|
|
1639
|
+
}
|
|
1451
1640
|
|
|
1452
1641
|
// 1. Skill link evidence: selected atoms materialized under .claude/skills/
|
|
1453
1642
|
const skillsDir = path.join(projectDir, '.claude/skills');
|
|
@@ -1458,13 +1647,19 @@ function validateSkillAndPlaceholderEvidence(projectDir, content) {
|
|
|
1458
1647
|
);
|
|
1459
1648
|
}
|
|
1460
1649
|
|
|
1461
|
-
// 2. Skill Preflight filled (one
|
|
1650
|
+
// 2. Skill Preflight filled (one exact-path row per atom)
|
|
1462
1651
|
const preflight = extractMarkdownSection(content, '## Skill Preflight');
|
|
1463
1652
|
if (!preflight || countFilledTableRows(preflight) < Math.max(1, atoms.length)) {
|
|
1464
1653
|
errors.push(
|
|
1465
1654
|
'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
1655
|
);
|
|
1467
1656
|
}
|
|
1657
|
+
const missingRows = missingSelectedAtomPreflightRows(content, atoms);
|
|
1658
|
+
if (missingRows.length > 0) {
|
|
1659
|
+
errors.push(
|
|
1660
|
+
`Skill Preflight: missing required reading evidence for ${missingRows.join(', ')} — read docs/atom-tree.json and add one exact .claude/skills/<atom>/SKILL.md row for every meta.selectedAtoms item`,
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1468
1663
|
|
|
1469
1664
|
// 3. Placeholder materialization evidence (placeholder-first, not code-draw-only)
|
|
1470
1665
|
const placeholder = extractMarkdownSection(content, '### Placeholder materialization');
|
|
@@ -1779,6 +1974,8 @@ export {
|
|
|
1779
1974
|
validateGameDesignerSeedContext,
|
|
1780
1975
|
validateGameDesignerSliceQueueSeed,
|
|
1781
1976
|
validateSkillsMatchSliceRefs,
|
|
1977
|
+
validateSkillAndPlaceholderEvidence,
|
|
1978
|
+
missingSelectedAtomPreflightRows,
|
|
1782
1979
|
validateDesignFusionSection,
|
|
1783
1980
|
validateReviewerHandoff,
|
|
1784
1981
|
validateReviewReportDoD,
|
|
@@ -1797,7 +1994,6 @@ export {
|
|
|
1797
1994
|
PLAN_HEADING,
|
|
1798
1995
|
PLAN_REQUIRED_SUBS,
|
|
1799
1996
|
ROLE_LOG,
|
|
1800
|
-
validateSkillAndPlaceholderEvidence,
|
|
1801
1997
|
validateDevUrlDebugFlag,
|
|
1802
1998
|
readSelectedAtomsForLink,
|
|
1803
1999
|
extractMarkdownSection,
|
|
@@ -1809,6 +2005,7 @@ async function main() {
|
|
|
1809
2005
|
const input = parseHookInput(stdin);
|
|
1810
2006
|
|
|
1811
2007
|
const projectDir =
|
|
2008
|
+
process.env.CODEBUDDY_PROJECT_DIR ||
|
|
1812
2009
|
process.env.CLAUDE_PROJECT_DIR ||
|
|
1813
2010
|
process.env.CURSOR_PROJECT_DIR ||
|
|
1814
2011
|
process.cwd();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"defaultMode": "
|
|
2
|
+
"defaultMode": "bypassPermissions",
|
|
3
3
|
"env": {
|
|
4
4
|
"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
|
|
5
5
|
},
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
],
|
|
55
55
|
"SubagentStop": [
|
|
56
56
|
{
|
|
57
|
-
"matcher": "artist|technical-artist|developer|reviewer|game-designer|skill-selector",
|
|
57
|
+
"matcher": "artist|technical-artist|developer|reviewer|game-designer|creative-director|skill-selector",
|
|
58
58
|
"hooks": [
|
|
59
59
|
{
|
|
60
60
|
"type": "command",
|
|
@@ -51,35 +51,35 @@ PlayCraft 是 **Vite** 项目,`ensure-project` 会检测到 `vite.config.ts` +
|
|
|
51
51
|
|
|
52
52
|
## 与 PlayCraft growth-rings 的接点
|
|
53
53
|
|
|
54
|
-
| iteration / loop
|
|
55
|
-
|
|
|
56
|
-
| ring 1 `loop: build`(收尾)
|
|
57
|
-
| ring 2+ `loop: build`(收尾)
|
|
58
|
-
| ring 4 `loop: verify`
|
|
59
|
-
| ring 4 verify ui_diff fail
|
|
60
|
-
| ring 4 optional optimization
|
|
54
|
+
| iteration / loop | 谁调用 | 调用什么 | 产出 |
|
|
55
|
+
| ----------------------------- | ----------------------- | --------------------------------------------------------------- | ------------------------------ |
|
|
56
|
+
| ring 1 `loop: build`(收尾) | Developer | `state:get` 披露的 contract/lifecycle/firstwin | 轻量 HOOK 生命周期 + 首关可胜 |
|
|
57
|
+
| ring 2+ `loop: build`(收尾) | Developer | 仅运行 `state:get.nextCommands` 中的 `verify:ui` / `record:mp4` | 当前 Ring 证据 |
|
|
58
|
+
| ring 4 `loop: verify` | Reviewer | `verify:ui` → `playwright-cli` 截图 | asset/layout 先行拦截 |
|
|
59
|
+
| ring 4 verify ui_diff fail | Developer / TA / Artist | 读 `playable-validate` 子 skill 做最小 fix | 修复后重跑 verify + record |
|
|
60
|
+
| ring 4 optional optimization | Reviewer / Orchestrator | `playable-validate` loop | `validate/PLAYABLE-*.md` + MP4 |
|
|
61
61
|
|
|
62
62
|
---
|
|
63
63
|
|
|
64
64
|
## 一句话指令表
|
|
65
65
|
|
|
66
|
-
| 用户/Agent 意图 | 命令/入口
|
|
67
|
-
| ------------------------ |
|
|
68
|
-
| 查看当前可运行命令 | `npm run state:get -- --json`
|
|
69
|
-
| 整片优化到可交付 | 读 `skills/playable-validate/SKILL.md` § Agent 入口
|
|
70
|
-
| 检查 UI / atlas / layout | 仅当 `state:get.nextCommands` 披露:`npm run verify:ui`
|
|
66
|
+
| 用户/Agent 意图 | 命令/入口 |
|
|
67
|
+
| ------------------------ | ----------------------------------------------------------------------------- |
|
|
68
|
+
| 查看当前可运行命令 | `npm run state:get -- --json` |
|
|
69
|
+
| 整片优化到可交付 | 读 `skills/playable-validate/SKILL.md` § Agent 入口 |
|
|
70
|
+
| 检查 UI / atlas / layout | 仅当 `state:get.nextCommands` 披露:`npm run verify:ui` |
|
|
71
71
|
| 录完整局 headed MP4 | 仅当 `state:get.nextCommands` 披露:`npm run record:mp4 -- --headed --fps=60` |
|
|
72
|
-
| 只录某个 focus zone | `npm run record:focus -- --scope=PHASE --fixture=endcard-win --headed`
|
|
73
|
-
| 优化 match 碎裂 FX | 读 `skills/playable-fx-decompose/SKILL.md`
|
|
74
|
-
| 优化帧光效 / burst | 读 `skills/playable-fx-peak/SKILL.md`
|
|
75
|
-
| 优化动效手感 | 读 `skills/playable-animate/SKILL.md`
|
|
76
|
-
| 局部 L→G 穷举 | `npm run sweep:L1` / `npm run sweep:G1`
|
|
72
|
+
| 只录某个 focus zone | `npm run record:focus -- --scope=PHASE --fixture=endcard-win --headed` |
|
|
73
|
+
| 优化 match 碎裂 FX | 读 `skills/playable-fx-decompose/SKILL.md` |
|
|
74
|
+
| 优化帧光效 / burst | 读 `skills/playable-fx-peak/SKILL.md` |
|
|
75
|
+
| 优化动效手感 | 读 `skills/playable-animate/SKILL.md` |
|
|
76
|
+
| 局部 L→G 穷举 | `npm run sweep:L1` / `npm run sweep:G1` |
|
|
77
77
|
|
|
78
78
|
---
|
|
79
79
|
|
|
80
80
|
## Vite 项目注意事项
|
|
81
81
|
|
|
82
|
-
PlayCraft 项目用 Vite,`record:mp4` 默认脚本已经带了 `--use-dev --url=http://localhost:
|
|
82
|
+
PlayCraft 项目用 Vite,`record:mp4` 默认脚本已经带了 `--use-dev --url=http://localhost:5173/`。只在当前 Ring 已披露 `record:mp4` 时运行:
|
|
83
83
|
|
|
84
84
|
```bash
|
|
85
85
|
npm run record:mp4 -- --headed --fps=60
|
|
@@ -88,10 +88,10 @@ npm run record:mp4 -- --headed --fps=60
|
|
|
88
88
|
这会:
|
|
89
89
|
|
|
90
90
|
1. 在当前目录启动 `npm run dev`。
|
|
91
|
-
2. 等待 `http://localhost:
|
|
91
|
+
2. 等待 `http://localhost:5173/` 可访问。
|
|
92
92
|
3. 用 Playwright 打开该 URL 并录制。
|
|
93
93
|
|
|
94
|
-
如果 Vite 端口不是
|
|
94
|
+
如果 Vite 端口不是 5173,改 `package.json` 里的 `record:mp4` 脚本,或手动:
|
|
95
95
|
|
|
96
96
|
```bash
|
|
97
97
|
# 终端 1
|
|
@@ -27,7 +27,6 @@ if (!keyword) {
|
|
|
27
27
|
const home = homedir();
|
|
28
28
|
const stores = [
|
|
29
29
|
{ name: 'Claude Code', path: join(home, '.claude', 'projects') },
|
|
30
|
-
{ name: 'Claude Internal', path: join(home, '.claude-internal', 'projects') },
|
|
31
30
|
{ name: 'CodeBuddy', path: join(home, '.codebuddy', 'projects') },
|
|
32
31
|
];
|
|
33
32
|
|