@playcraft/cli 0.0.55 → 0.0.56
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 +21 -0
- package/REMIX.md +103 -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 +47 -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 +255 -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
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 2,
|
|
3
|
+
"patterns": [
|
|
4
|
+
{
|
|
5
|
+
"id": "arrow_pick_match_fusion",
|
|
6
|
+
"intentTags": ["arrow", "elimination", "fusion", "match-3", "消除", "箭头", "融合"],
|
|
7
|
+
"title": "Arrow slide-out + tray match-3",
|
|
8
|
+
"mechanics": "Paths slide into a tray; match-3 clears tray slots. Combines path puzzle dependency with elimination payoff.",
|
|
9
|
+
"playerFantasy": "Tap an arrow path — it slides out. Fill the tray strategically; match-3 clears space before overflow.",
|
|
10
|
+
"decisionMoment": "Choosing which blocked path to unlock first vs filling the tray for a combo clear.",
|
|
11
|
+
"skillRef": "arrow_pick_match_rules.aigameplay",
|
|
12
|
+
"recommendedProfile": "standard",
|
|
13
|
+
"depthHint": "不要写「箭头+三消」就停;写清:棋盘哪一块先解锁、托盘峰值如何迫使颜色顺序、blocked 误点教什么。",
|
|
14
|
+
"antiPatternRef": "8×8 but ≤4 paths or all-free opening — no puzzle identity"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "path_elimination_puzzle",
|
|
18
|
+
"intentTags": ["arrow", "path", "puzzle", "elimination", "箭头", "路径"],
|
|
19
|
+
"title": "Pure path elimination puzzle",
|
|
20
|
+
"mechanics": "Click arrow heads; paths slide out when unobstructed. Order matters — blocked paths teach dependency.",
|
|
21
|
+
"playerFantasy": "Read arrow directions, find the unlock sequence, clear the board.",
|
|
22
|
+
"decisionMoment": "First tap on a blocked path — shake/highlight feedback — then finding the correct unlock order.",
|
|
23
|
+
"skillRef": "path_elimination_rules.aigameplay",
|
|
24
|
+
"recommendedProfile": "standard",
|
|
25
|
+
"depthHint": "用折线密度与依赖层数定难度,固定 8×8;至少 2 层解锁波次,禁止开局全部 FREE。",
|
|
26
|
+
"antiPatternRef": "All paths FREE at start — zero decision"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "match3_board",
|
|
30
|
+
"intentTags": ["match-3", "match3", "swap", "三消", "消除"],
|
|
31
|
+
"title": "Classic match-3 board",
|
|
32
|
+
"mechanics": "Swap adjacent tiles; match-3 removes; gravity refills.",
|
|
33
|
+
"playerFantasy": "Swap to create matches; cascades and combos build excitement.",
|
|
34
|
+
"decisionMoment": "Choosing between an obvious match vs setting up a cascade.",
|
|
35
|
+
"skillRef": "match3_core.aigameplay",
|
|
36
|
+
"recommendedProfile": "standard",
|
|
37
|
+
"depthHint": "若与 path 融合,说明主手势是 swap 还是 tap-path,避免异基底双主手势。",
|
|
38
|
+
"antiPatternRef": "Auto-clear board with no player swap"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "board_phase_unlock",
|
|
42
|
+
"intentTags": ["phase", "unlock", "mask", "分区", "解锁", "board", "fusion"],
|
|
43
|
+
"title": "Board phase unlock (same-grid mask)",
|
|
44
|
+
"mechanics": "Same 8×8 board; masked regions unlock after match clears or path milestones. New paths appear in unlocked zones — board changes via mask, not resize.",
|
|
45
|
+
"playerFantasy": "Clear the first zone to reveal more puzzle — the board grows in complexity without shrinking cells.",
|
|
46
|
+
"decisionMoment": "Spend a tray clear to unlock the next zone vs hoarding colors for a safer combo.",
|
|
47
|
+
"skillRef": "arrow_pick_match_rules.aigameplay",
|
|
48
|
+
"recommendedProfile": "standard",
|
|
49
|
+
"depthHint": "写清每 phase 解锁条件与新增 path 条数;难度靠追加折线而非缩小棋盘。",
|
|
50
|
+
"antiPatternRef": "Resize board to 4×4 for difficulty — use path count instead"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "match3_tray_puzzle_hybrid",
|
|
54
|
+
"intentTags": ["tray", "match-3", "puzzle", "hybrid", "托盘", "三消", "拼图"],
|
|
55
|
+
"title": "Tray-only match-3 + board path puzzle",
|
|
56
|
+
"mechanics": "Primary gesture: path tap on board. Match-3 happens only inside the tray. Board side is pure dependency puzzle — no swap on grid.",
|
|
57
|
+
"playerFantasy": "Solve the arrow maze; the tray is your pressure valve — match-3 is the relief, not the main puzzle.",
|
|
58
|
+
"decisionMoment": "Push a risky color into a nearly-full tray hoping the next two paths complete the triple.",
|
|
59
|
+
"skillRef": "arrow_pick_match_rules.aigameplay",
|
|
60
|
+
"recommendedProfile": "ring1_min",
|
|
61
|
+
"depthHint": "声明三消仅在托盘;棋盘侧禁止 swap。写托盘峰值曲线与颜色推出顺序。",
|
|
62
|
+
"antiPatternRef": "Board swap match-3 + path tap without interaction contract"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "match3_board_phase",
|
|
66
|
+
"intentTags": ["match3", "phase", "path", "hybrid", "sequential", "双区"],
|
|
67
|
+
"title": "Match-3 main + path lock zone (high risk)",
|
|
68
|
+
"mechanics": "Primary zone: swap match-3. Secondary zone: path puzzle locked until match milestone. C-Sequential — document pseudo-fusion reducer.",
|
|
69
|
+
"playerFantasy": "Clear tiles to unlock the arrow zone — two systems, one win condition.",
|
|
70
|
+
"decisionMoment": "Finish the match-3 objective vs peeking at the locked path zone.",
|
|
71
|
+
"skillRef": "match3_core.aigameplay",
|
|
72
|
+
"recommendedProfile": "hard",
|
|
73
|
+
"depthHint": "Ring 2+ only.必须写 pseudo-fusion 与单 reducer,避免双主手势抢输入。",
|
|
74
|
+
"antiPatternRef": "Two equal primary gestures with no documented interaction contract"
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
@@ -54,6 +54,8 @@
|
|
|
54
54
|
"placeholder:gen-audio": "python3 skills/_shared/scripts/gen-placeholder-audio.py",
|
|
55
55
|
"verify:placeholders": "node skills/_shared/scripts/verify-placeholders.mjs --root=.",
|
|
56
56
|
"verify:fusion": "node skills/playable-record/scripts/verify-fusion.mjs --root=. --use-dev",
|
|
57
|
+
"verify:design": "node skills/_shared/scripts/verify-design-consistency.mjs --root=.",
|
|
58
|
+
"levels:validate": "node skills/_shared/scripts/validate-level-sketch.mjs --root=.",
|
|
57
59
|
"verify:visual-ring1": "node skills/playable-record/scripts/verify-visual-ring1.mjs --root=. --use-dev",
|
|
58
60
|
"verify:visual-ring2": "node skills/playable-record/scripts/verify-visual-ring.mjs --root=. --ring=2 --use-dev",
|
|
59
61
|
"verify:visual-ring3": "node skills/playable-record/scripts/verify-visual-ring.mjs --root=. --ring=3 --use-dev",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { access, cp, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
11
|
-
import { basename, dirname, join, resolve } from 'node:path';
|
|
11
|
+
import { basename, dirname, join, resolve, sep } from 'node:path';
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
13
|
import { spawnSync } from 'node:child_process';
|
|
14
14
|
|
|
@@ -24,17 +24,27 @@ const COPY_EXCLUDES = new Set([
|
|
|
24
24
|
'dist',
|
|
25
25
|
'package-lock.json',
|
|
26
26
|
'playable.ui.profile.yaml',
|
|
27
|
-
'playground',
|
|
28
|
-
'recordings',
|
|
29
|
-
'validate',
|
|
30
27
|
]);
|
|
31
28
|
|
|
29
|
+
/**
|
|
30
|
+
* 仅在「模板根直接子目录」时才排除的运行时产物目录。
|
|
31
|
+
* 这些名字也会出现在 skills 内部(如 skills/playable-focus/templates/playground 与
|
|
32
|
+
* skills/playable-focus/templates/focus-lab),故只在顶层排除,避免误伤 skills 的模板素材
|
|
33
|
+
* (否则 bootstrap-playground.mjs 会因找不到 registry.template.json 而 ENOENT 退出)。
|
|
34
|
+
* 对齐 create.ts 的 shouldCopyTemplateEntry 行为。
|
|
35
|
+
*/
|
|
36
|
+
const TEMPLATE_ROOTLEVEL_EXCLUDES = new Set(['playground', 'recordings', 'validate']);
|
|
37
|
+
|
|
32
38
|
function shouldCopy(src) {
|
|
33
39
|
const name = basename(src);
|
|
34
40
|
if (COPY_EXCLUDES.has(name)) return false;
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
|
|
41
|
+
// 运行时产物目录只排除模板根这一层;skills 内的同名目录是模板素材,必须复制
|
|
42
|
+
if (TEMPLATE_ROOTLEVEL_EXCLUDES.has(name) && dirname(src) === TEMPLATE_ROOT) return false;
|
|
43
|
+
// 顶层 focus-lab/ 是运行时 lab 目录,只保留 .gitkeep 占位;
|
|
44
|
+
// 但 skills 内部(如 skills/playable-focus/templates/focus-lab)是模板素材,需正常复制
|
|
45
|
+
if (src.startsWith(join(TEMPLATE_ROOT, 'focus-lab') + sep)) return name === '.gitkeep';
|
|
46
|
+
if (src.includes(`${sep}ta-workspace${sep}logs${sep}`)) return name === '.gitkeep';
|
|
47
|
+
if (src.includes(`${sep}ta-workspace${sep}tmp${sep}`)) return name === '.gitkeep';
|
|
38
48
|
return true;
|
|
39
49
|
}
|
|
40
50
|
|
|
@@ -107,9 +117,10 @@ async function main() {
|
|
|
107
117
|
});
|
|
108
118
|
ensureResult = { status: r.status, stdout: r.stdout, stderr: r.stderr };
|
|
109
119
|
}
|
|
120
|
+
const ensureFailed = opts.ensure && ensureResult?.status !== 0;
|
|
110
121
|
|
|
111
122
|
const result = {
|
|
112
|
-
ok:
|
|
123
|
+
ok: !ensureFailed,
|
|
113
124
|
projectRoot: destRoot,
|
|
114
125
|
templateRoot: TEMPLATE_ROOT,
|
|
115
126
|
skillsRoot: SKILLS_ROOT,
|
|
@@ -125,13 +136,22 @@ async function main() {
|
|
|
125
136
|
console.log(JSON.stringify(result, null, 2));
|
|
126
137
|
} else {
|
|
127
138
|
console.log(`[create-playable] created ${destRoot}`);
|
|
128
|
-
if (
|
|
139
|
+
if (!opts.ensure) {
|
|
140
|
+
// 跳过 ensure,无事可报
|
|
141
|
+
} else if (!ensureFailed) {
|
|
129
142
|
console.log('[create-playable] ensure-project OK');
|
|
130
|
-
} else
|
|
131
|
-
|
|
143
|
+
} else {
|
|
144
|
+
// ensure-project 失败时透出子进程真实输出(如 ENOENT、缺失模板),
|
|
145
|
+
// 否则只会看到无意义的 "node exit 1",无法定位漏文件根因。
|
|
146
|
+
console.error('[create-playable] ensure-project failed:');
|
|
147
|
+
if (ensureResult?.stderr) console.error(ensureResult.stderr.trimEnd());
|
|
148
|
+
if (ensureResult?.stdout) console.error(ensureResult.stdout.trimEnd());
|
|
149
|
+
console.error(' → 项目模板可能不完整,运行 npm run ensure:project 排查');
|
|
132
150
|
}
|
|
133
151
|
for (const step of result.next) console.log(` → ${step}`);
|
|
134
152
|
}
|
|
153
|
+
|
|
154
|
+
if (ensureFailed) process.exit(1);
|
|
135
155
|
}
|
|
136
156
|
|
|
137
157
|
main().catch((err) => {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Start (or stop) the persistent preview dev server and record the real devUrl.
|
|
4
4
|
*
|
|
5
5
|
* This removes the LLM from port handling entirely (docs/harness/experiment-lessons.md §9.2):
|
|
6
|
-
* Vite runs with strictPort:false, so when
|
|
6
|
+
* Vite runs with strictPort:false, so when 5173 is taken it auto-increments; this
|
|
7
7
|
* script parses Vite's actual `Local:` URL from the log, appends `?debug=1`, and
|
|
8
8
|
* writes it back to project-state via the same layer `state:set` uses. The agent
|
|
9
9
|
* never reads or records a port by hand.
|
|
@@ -20,11 +20,11 @@ import { openSync, closeSync, existsSync, readFileSync, writeFileSync, rmSync }
|
|
|
20
20
|
import { spawn } from 'node:child_process';
|
|
21
21
|
import { join, resolve } from 'node:path';
|
|
22
22
|
import { applyHandoffUpdates, resolveProjectStatePath } from './state-handoff.mjs';
|
|
23
|
-
import { parseLocalUrl, waitForHttp } from '../../playable-record/scripts/lib/dev-server.mjs';
|
|
23
|
+
import { parseLocalUrl, waitForHttp, DEFAULT_DEV_URL } from '../../playable-record/scripts/lib/dev-server.mjs';
|
|
24
24
|
|
|
25
25
|
const PID_FILE = '.playcraft-dev.pid';
|
|
26
26
|
const LOG_FILE = '.playcraft-dev.log';
|
|
27
|
-
const PREFERRED_URL =
|
|
27
|
+
const PREFERRED_URL = DEFAULT_DEV_URL;
|
|
28
28
|
|
|
29
29
|
function parseArgs(argv) {
|
|
30
30
|
const opts = { root: process.cwd(), stop: false, timeout: 30000 };
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
* for that surface.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { evaluateSliceQueueForRingGate } from './state-store.mjs';
|
|
13
|
+
import { resolvePrimarySkillId } from './lib/skill-fidelity.mjs';
|
|
14
|
+
|
|
10
15
|
const COMMON_REQUIRED = ['CLAUDE.md'];
|
|
11
16
|
|
|
12
17
|
export const FUTURE_RING_FILES = [
|
|
@@ -22,51 +27,43 @@ export const FUTURE_RING_FILES = [
|
|
|
22
27
|
const DISCLOSURE_BY_RING_LOOP = {
|
|
23
28
|
'1:clarify': {
|
|
24
29
|
currentAction:
|
|
25
|
-
'Orchestrator: invoke @game-designer
|
|
26
|
-
requiredFiles: [...COMMON_REQUIRED
|
|
27
|
-
deferredFiles: FUTURE_RING_FILES
|
|
30
|
+
'Orchestrator: invoke @game-designer with only the user\'s original intent. Do not add design decisions, engine hints, platform constraints, or workflow steps — designer reads its own role card.',
|
|
31
|
+
requiredFiles: [...COMMON_REQUIRED],
|
|
32
|
+
deferredFiles: FUTURE_RING_FILES,
|
|
28
33
|
nextRole: 'game-designer',
|
|
29
|
-
artifactRefs: [
|
|
30
|
-
requiredContextFields: [
|
|
34
|
+
artifactRefs: [],
|
|
35
|
+
requiredContextFields: [],
|
|
31
36
|
allowedTasks: [
|
|
32
|
-
'Invoke @game-designer
|
|
33
|
-
'Game Designer: design-brief discovery + DESIGN seed + playcraft skills match + sliceState queue',
|
|
34
|
-
'Game Designer: run playcraft skills match and write meta.skillsMatch + compact selectedAtoms',
|
|
35
|
-
'Record fromScratchReason when core mechanic is novel',
|
|
36
|
-
'Define first-level win strategy and semantic placeholders (Game Designer subagent)',
|
|
37
|
+
'Invoke @game-designer with user intent only',
|
|
37
38
|
],
|
|
38
39
|
forbiddenTasks: [
|
|
39
40
|
'Do not write docs/DESIGN.md as orchestrator',
|
|
40
41
|
'Do not implement game/ as orchestrator during clarify',
|
|
41
|
-
'Do not
|
|
42
|
-
'Do not
|
|
43
|
-
'Do not
|
|
44
|
-
'Do not claim skillRefs implement core mechanic without skills read',
|
|
45
|
-
'Do not run standalone record:mp4 as a Ring 1 blocker',
|
|
46
|
-
'Do not run verify:ui as Ring 1 blockers',
|
|
42
|
+
'Do not add design decisions, engine hints, or workflow steps to the designer dispatch',
|
|
43
|
+
'Do not describe what designer should produce — its role card defines outputs',
|
|
44
|
+
'Do not list designer CLI commands — its role card defines the construction chain',
|
|
47
45
|
],
|
|
48
46
|
nextCommands: [
|
|
49
|
-
'Invoke @game-designer —
|
|
47
|
+
'Invoke @game-designer — pass only the user\'s original intent, nothing more',
|
|
50
48
|
],
|
|
51
49
|
},
|
|
52
50
|
'1:select': {
|
|
53
|
-
currentAction: 'Orchestrator: after Game Designer STOP, spawn a temporary ref-flow atom-selection subagent
|
|
51
|
+
currentAction: 'Orchestrator: after Game Designer STOP, spawn a temporary ref-flow atom-selection subagent.',
|
|
54
52
|
requiredFiles: [...COMMON_REQUIRED, 'docs/DESIGN.md'],
|
|
55
53
|
deferredFiles: FUTURE_RING_FILES,
|
|
56
54
|
nextRole: 'temporary-ref-flow',
|
|
57
55
|
artifactRefs: ['docs/DESIGN.md', 'docs/atom-tree.json'],
|
|
58
|
-
requiredContextFields: ['lastRole.summary', '
|
|
56
|
+
requiredContextFields: ['lastRole.summary', 'sharedDecisions.coreAction'],
|
|
59
57
|
allowedTasks: [
|
|
60
|
-
'Spawn temporary ref-flow atom-selection subagent
|
|
61
|
-
'Pass DESIGN.md gameplay keywords and constraints to the temporary selector',
|
|
58
|
+
'Spawn temporary ref-flow atom-selection subagent',
|
|
62
59
|
],
|
|
63
60
|
forbiddenTasks: [
|
|
64
61
|
'Do not select atoms inline as orchestrator',
|
|
65
62
|
'Do not run init-template before atom-selection STOP',
|
|
66
|
-
'Do not
|
|
63
|
+
'Do not add engine or gameplay-trunk hints to the selector dispatch',
|
|
67
64
|
],
|
|
68
65
|
nextCommands: [
|
|
69
|
-
'Spawn temporary ref-flow atom-selection subagent —
|
|
66
|
+
'Spawn temporary ref-flow atom-selection subagent — read docs/DESIGN.md and follow skill-selector-guide.md',
|
|
70
67
|
],
|
|
71
68
|
},
|
|
72
69
|
'1:scaffold': {
|
|
@@ -95,29 +92,21 @@ const DISCLOSURE_BY_RING_LOOP = {
|
|
|
95
92
|
],
|
|
96
93
|
},
|
|
97
94
|
'1:build': {
|
|
98
|
-
currentAction: 'Orchestrator: invoke @developer for Ring 1 build.
|
|
95
|
+
currentAction: 'Orchestrator: invoke @developer for Ring 1 build.',
|
|
99
96
|
requiredFiles: [...COMMON_REQUIRED, 'docs/DESIGN.md', 'docs/atom-tree.json', 'docs/scaffold-manifest.md', 'game/'],
|
|
100
97
|
deferredFiles: FUTURE_RING_FILES.filter((file) => file !== 'logs/review-report.md'),
|
|
101
98
|
nextRole: 'developer',
|
|
102
99
|
artifactRefs: ['docs/DESIGN.md', 'docs/atom-tree.json', 'logs/developer-log.md', 'game/'],
|
|
103
|
-
requiredContextFields: ['lastRole.summary', '
|
|
100
|
+
requiredContextFields: ['lastRole.summary', 'sharedDecisions.engine', 'sharedDecisions.coreAction', 'sharedDecisions.semanticPlaceholders'],
|
|
104
101
|
allowedTasks: [
|
|
105
102
|
'Invoke @developer for ring 1 build',
|
|
106
|
-
'Apply engine scaffold if needed (orchestrator only, before developer)',
|
|
107
|
-
'Implement core gameplay loop (Developer subagent)',
|
|
108
|
-
'Wire window.__playable runtime contract (Developer subagent)',
|
|
109
|
-
'Make autoPlay follow player-reachable actions (Developer subagent)',
|
|
110
|
-
'Record devUrl when ready (Developer subagent)',
|
|
111
103
|
],
|
|
112
104
|
forbiddenTasks: [
|
|
113
105
|
'Do not implement game/ as orchestrator when @developer should own build',
|
|
114
|
-
'Do not
|
|
115
|
-
'Do not implement CTA fallback as a Ring 1 blocker',
|
|
116
|
-
'Do not run full MP4 as a Ring 1 blocker',
|
|
117
|
-
'Do not produce production assets',
|
|
106
|
+
'Do not add implementation hints to the developer dispatch — developer reads its dispatchPacket + role card',
|
|
118
107
|
],
|
|
119
108
|
nextCommands: [
|
|
120
|
-
'Invoke @developer —
|
|
109
|
+
'Invoke @developer — pass the active slice from sliceState, nothing more',
|
|
121
110
|
'npm run placeholder:gen',
|
|
122
111
|
'npm run verify:contract',
|
|
123
112
|
'npm run verify:lifecycle',
|
|
@@ -349,6 +338,21 @@ function normalizeIteration(iteration) {
|
|
|
349
338
|
return Number.isFinite(n) && n >= 1 && n <= 4 ? n : 1;
|
|
350
339
|
}
|
|
351
340
|
|
|
341
|
+
/** Compatibility export for STOP hook mechanism-phase detection. */
|
|
342
|
+
export function isMechanismLocked(projectRoot, handoffContext = {}) {
|
|
343
|
+
const skill = resolvePrimarySkillId(projectRoot, handoffContext);
|
|
344
|
+
if (!skill) return false;
|
|
345
|
+
const treePath = join(projectRoot, 'docs/atom-tree.json');
|
|
346
|
+
if (!existsSync(treePath)) return false;
|
|
347
|
+
try {
|
|
348
|
+
const tree = JSON.parse(readFileSync(treePath, 'utf8'));
|
|
349
|
+
const items = tree?.meta?.skillsMatch?.items ?? [];
|
|
350
|
+
return items.length > 0;
|
|
351
|
+
} catch {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
352
356
|
function resolveDisclosureKey({ iteration = 1, loop = 'clarify', handoff = {} } = {}) {
|
|
353
357
|
const ring = normalizeIteration(iteration);
|
|
354
358
|
const normalizedLoop = normalizeLoop(loop);
|
|
@@ -407,6 +411,11 @@ export function disclosureForSlice(dispatchPacket, { iteration = 1 } = {}) {
|
|
|
407
411
|
const status = String(dispatchPacket.status || 'pending').trim();
|
|
408
412
|
const allowedFiles = Array.isArray(dispatchPacket.allowedFiles) ? dispatchPacket.allowedFiles : [];
|
|
409
413
|
const packetForbidden = Array.isArray(dispatchPacket.forbiddenTasks) ? dispatchPacket.forbiddenTasks : [];
|
|
414
|
+
const packetRequired = Array.isArray(dispatchPacket.requiredContext?.mustRead)
|
|
415
|
+
? dispatchPacket.requiredContext.mustRead
|
|
416
|
+
: Array.isArray(dispatchPacket.requiredContext)
|
|
417
|
+
? dispatchPacket.requiredContext
|
|
418
|
+
: [];
|
|
410
419
|
|
|
411
420
|
const currentAction =
|
|
412
421
|
`Orchestrator: invoke @${role} for slice "${sliceId}" — ${doWhat} (flow: ${flow}). ` +
|
|
@@ -429,7 +438,7 @@ export function disclosureForSlice(dispatchPacket, { iteration = 1 } = {}) {
|
|
|
429
438
|
ring,
|
|
430
439
|
loop: flow,
|
|
431
440
|
currentAction,
|
|
432
|
-
currentRingRequiredFiles: [...COMMON_REQUIRED, ...allowedFiles],
|
|
441
|
+
currentRingRequiredFiles: [...new Set([...COMMON_REQUIRED, ...packetRequired, ...allowedFiles])],
|
|
433
442
|
deferredFiles: FUTURE_RING_FILES.filter((f) => !allowedFiles.includes(f)),
|
|
434
443
|
allowedTasks: [
|
|
435
444
|
`Invoke @${role} with dispatchPacket only (sliceId=${sliceId}, flow=${flow})`,
|
|
@@ -443,7 +452,7 @@ export function disclosureForSlice(dispatchPacket, { iteration = 1 } = {}) {
|
|
|
443
452
|
],
|
|
444
453
|
nextCommands,
|
|
445
454
|
nextRole: role,
|
|
446
|
-
artifactRefs:
|
|
455
|
+
artifactRefs: [...new Set(['dispatchPacket', ...packetRequired, ...allowedFiles])],
|
|
447
456
|
requiredContextFields: [
|
|
448
457
|
'dispatchPacket.sliceId',
|
|
449
458
|
'dispatchPacket.role',
|
|
@@ -455,11 +464,28 @@ export function disclosureForSlice(dispatchPacket, { iteration = 1 } = {}) {
|
|
|
455
464
|
};
|
|
456
465
|
}
|
|
457
466
|
|
|
458
|
-
/** Unified entry: active dispatchPacket wins;
|
|
459
|
-
export function disclosureForProjectState({
|
|
467
|
+
/** Unified entry: active dispatchPacket wins; drained queue promotes to verify + ring:gate. */
|
|
468
|
+
export function disclosureForProjectState({
|
|
469
|
+
iteration = 1,
|
|
470
|
+
loop = 'clarify',
|
|
471
|
+
handoff = {},
|
|
472
|
+
dispatchPacket = null,
|
|
473
|
+
sliceState = null,
|
|
474
|
+
} = {}) {
|
|
460
475
|
if (dispatchPacket) {
|
|
461
476
|
return disclosureForSlice(dispatchPacket, { iteration });
|
|
462
477
|
}
|
|
478
|
+
const ss = sliceState || {};
|
|
479
|
+
const gate = evaluateSliceQueueForRingGate(ss);
|
|
480
|
+
const ringGatePending = String(handoff.ringGate || 'pending').toLowerCase() !== 'passed';
|
|
481
|
+
if (
|
|
482
|
+
ss.activeSliceId === 'none'
|
|
483
|
+
&& gate.applicable
|
|
484
|
+
&& gate.ok
|
|
485
|
+
&& ringGatePending
|
|
486
|
+
) {
|
|
487
|
+
return disclosureForState({ iteration, loop: 'verify', handoff });
|
|
488
|
+
}
|
|
463
489
|
return disclosureForState({ iteration, loop, handoff });
|
|
464
490
|
}
|
|
465
491
|
|
|
@@ -25,6 +25,8 @@ import { createRequire } from 'node:module';
|
|
|
25
25
|
import { dirname, join, resolve } from 'node:path';
|
|
26
26
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
27
27
|
|
|
28
|
+
import { DEFAULT_DEV_URL } from '../../playable-record/scripts/lib/dev-server.mjs';
|
|
29
|
+
|
|
28
30
|
const require = createRequire(import.meta.url);
|
|
29
31
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
32
|
const SKILLS_ROOT = resolve(__dirname, '../..');
|
|
@@ -171,7 +173,7 @@ async function checkContractLive(projectRoot) {
|
|
|
171
173
|
const { runContractProbe } = await import(pathToFileURL(probePath).href);
|
|
172
174
|
const res = await runContractProbe({
|
|
173
175
|
root: projectRoot,
|
|
174
|
-
url: isVite ?
|
|
176
|
+
url: isVite ? DEFAULT_DEV_URL : null,
|
|
175
177
|
useDev: isVite,
|
|
176
178
|
port: 8080,
|
|
177
179
|
scene: 'MainScene',
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fusion intent detection — shared by verify:fusion and verify:design-consistency.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
const FUSION_KEYWORDS = [
|
|
8
|
+
'融合',
|
|
9
|
+
'fusion',
|
|
10
|
+
'hybrid',
|
|
11
|
+
'combine',
|
|
12
|
+
'组合',
|
|
13
|
+
'mix',
|
|
14
|
+
'mixed',
|
|
15
|
+
'plus',
|
|
16
|
+
'+',
|
|
17
|
+
'和',
|
|
18
|
+
'与',
|
|
19
|
+
'加',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const ELIMINATION_KEYWORDS = ['消除', 'elimination', 'eliminate', 'match-3', 'match3', '三消', 'match'];
|
|
23
|
+
const ARROW_PATH_KEYWORDS = ['箭头', 'arrow', 'path', '路径', 'slide-out', 'slide'];
|
|
24
|
+
|
|
25
|
+
/** @param {string[]} intentTags @param {string} [rawIntent] */
|
|
26
|
+
export function detectFusionIntent(intentTags, rawIntent = '') {
|
|
27
|
+
const blob = `${rawIntent} ${(intentTags ?? []).join(' ')}`.toLowerCase();
|
|
28
|
+
if (FUSION_KEYWORDS.some((k) => blob.includes(k.toLowerCase()))) return true;
|
|
29
|
+
const hasElim = ELIMINATION_KEYWORDS.some((k) => blob.includes(k.toLowerCase()));
|
|
30
|
+
const hasArrow = ARROW_PATH_KEYWORDS.some((k) => blob.includes(k.toLowerCase()));
|
|
31
|
+
return hasElim && hasArrow;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Collect fusion-intent signals from project artifacts.
|
|
36
|
+
* @param {string} root
|
|
37
|
+
* @returns {{ intentTags: string[], rawIntent: string }}
|
|
38
|
+
*/
|
|
39
|
+
export function readFusionIntentFromProject(root) {
|
|
40
|
+
const tags = [];
|
|
41
|
+
let raw = '';
|
|
42
|
+
|
|
43
|
+
const configPath = join(root, 'config.json');
|
|
44
|
+
if (existsSync(configPath)) {
|
|
45
|
+
try {
|
|
46
|
+
const cfg = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
47
|
+
if (cfg.prompt) raw += ` ${cfg.prompt}`;
|
|
48
|
+
} catch {
|
|
49
|
+
// ignore
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const atomTreePath = join(root, 'docs/atom-tree.json');
|
|
54
|
+
if (existsSync(atomTreePath)) {
|
|
55
|
+
try {
|
|
56
|
+
const tree = JSON.parse(readFileSync(atomTreePath, 'utf8'));
|
|
57
|
+
const intent = tree?.meta?.skillsMatch?.intent;
|
|
58
|
+
if (Array.isArray(intent)) tags.push(...intent.map(String));
|
|
59
|
+
else if (typeof intent === 'string') tags.push(intent);
|
|
60
|
+
} catch {
|
|
61
|
+
// ignore
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const statePath = join(root, 'docs/project-state.json');
|
|
66
|
+
if (existsSync(statePath)) {
|
|
67
|
+
try {
|
|
68
|
+
const state = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
69
|
+
if (Array.isArray(state.intentTags)) tags.push(...state.intentTags.map(String));
|
|
70
|
+
if (typeof state.rawIntent === 'string') raw += ` ${state.rawIntent}`;
|
|
71
|
+
} catch {
|
|
72
|
+
// ignore
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const briefPath = join(root, 'docs/design-brief.md');
|
|
77
|
+
if (existsSync(briefPath)) {
|
|
78
|
+
try {
|
|
79
|
+
const brief = readFileSync(briefPath, 'utf8');
|
|
80
|
+
const userMatch = brief.match(/\[USER\][^\n]*/i);
|
|
81
|
+
if (userMatch) raw += ` ${userMatch[0]}`;
|
|
82
|
+
} catch {
|
|
83
|
+
// ignore
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { intentTags: tags, rawIntent: raw.trim() };
|
|
88
|
+
}
|