@playcraft/cli 0.0.53 → 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 +439 -159
- 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 +13 -6
- package/dist/remix/clone-source.js +208 -0
- package/dist/remix/clone.js +255 -0
- package/dist/scaffold/templates.js +35 -0
- 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/playable-template-phaser/.playcraftignore +10 -0
- package/project-templates/playable-template-phaser/CLAUDE.md +178 -0
- package/project-templates/playable-template-phaser/PLAYCRAFT-MIGRATION.md +328 -0
- package/project-templates/playable-template-phaser/README.md +381 -0
- package/project-templates/playable-template-phaser/assets/brain.png +0 -0
- package/project-templates/playable-template-phaser/assets/button.png +0 -0
- package/project-templates/playable-template-phaser/assets/interact.png +0 -0
- package/project-templates/playable-template-phaser/assets/logo.webp +0 -0
- package/project-templates/playable-template-phaser/assets/warn_brain.png +0 -0
- package/project-templates/playable-template-phaser/builds.config.js +12 -0
- package/project-templates/playable-template-phaser/globals.d.ts +2 -0
- package/project-templates/playable-template-phaser/index.html +12 -0
- package/project-templates/playable-template-phaser/package.json +45 -0
- package/project-templates/playable-template-phaser/scripts/base64-to-mp3.js +340 -0
- package/project-templates/playable-template-phaser/scripts/convert-to-webp.js +370 -0
- package/project-templates/playable-template-phaser/src/Game.ts +154 -0
- package/project-templates/playable-template-phaser/src/config/LoadingConfig.ts +40 -0
- package/project-templates/playable-template-phaser/src/config/ThemeConfig.ts +81 -0
- package/project-templates/playable-template-phaser/src/index.css +18 -0
- package/project-templates/playable-template-phaser/src/index.html +9 -0
- package/project-templates/playable-template-phaser/src/index.ts +30 -0
- package/project-templates/playable-template-phaser/src/json5.d.ts +4 -0
- package/project-templates/playable-template-phaser/src/managers/AudioManager.ts +559 -0
- package/project-templates/playable-template-phaser/src/scenes/MainScene.ts +301 -0
- package/project-templates/playable-template-phaser/src/scenes/PreloadScene.ts +256 -0
- package/project-templates/playable-template-phaser/src/theme/README.md +194 -0
- package/project-templates/playable-template-phaser/src/theme/blue-theme/assets/.gitkeep +24 -0
- package/project-templates/playable-template-phaser/src/theme/blue-theme/index.ts +27 -0
- package/project-templates/playable-template-phaser/src/theme/blue-theme/theme.data.json5 +19 -0
- package/project-templates/playable-template-phaser/src/theme/green-theme/assets/.gitkeep +3 -0
- package/project-templates/playable-template-phaser/src/theme/green-theme/index.ts +20 -0
- package/project-templates/playable-template-phaser/src/theme/green-theme/theme.data.json5 +19 -0
- package/project-templates/playable-template-phaser/src/theme/index.ts +1 -0
- package/project-templates/playable-template-phaser/src/theme/red-theme/assets/.gitkeep +3 -0
- package/project-templates/playable-template-phaser/src/theme/red-theme/index.ts +20 -0
- package/project-templates/playable-template-phaser/src/theme/red-theme/theme.data.json5 +19 -0
- package/project-templates/playable-template-phaser/src/theme/theme.metadata.md +60 -0
- package/project-templates/playable-template-phaser/src/theme/theme.schema.json5 +149 -0
- package/project-templates/playable-template-phaser/src/ui/BrainAgeUI.ts +681 -0
- package/project-templates/playable-template-phaser/src/ui/CountdownUI.ts +565 -0
- package/project-templates/playable-template-phaser/src/ui/PlayButton.ts +51 -0
- package/project-templates/playable-template-phaser/src/ui/ScoreUI.ts +207 -0
- package/project-templates/playable-template-phaser/src/ui/SuccessUI.ts +43 -0
- package/project-templates/playable-template-phaser/src/ui/index.ts +15 -0
- package/project-templates/playable-template-phaser/tsconfig.json +26 -0
- package/project-templates/playable-template-phaser/vite.config.ts +64 -0
- package/project-templates/playable-template-phaser//346/226/260/346/211/213/345/274/200/345/217/221/346/214/207/345/215/227.md +209 -0
- package/project-templates/playable-template-three/README.md +795 -0
- package/project-templates/playable-template-three/assets/Stork.glb +0 -0
- package/project-templates/playable-template-three/assets/button.png +0 -0
- package/project-templates/playable-template-three/assets/logo.webp +0 -0
- package/project-templates/playable-template-three/build.json +7 -0
- package/project-templates/playable-template-three/builds.config.example.js +151 -0
- package/project-templates/playable-template-three/builds.config.js +158 -0
- package/project-templates/playable-template-three/package.json +36 -0
- package/project-templates/playable-template-three/scripts/convert-to-webp.js +370 -0
- package/project-templates/playable-template-three/scripts/theme-builds.js +514 -0
- package/project-templates/playable-template-three/src/Game.ts +215 -0
- package/project-templates/playable-template-three/src/config/LoadingConfig.ts +27 -0
- package/project-templates/playable-template-three/src/config/ThemeConfig.ts +36 -0
- package/project-templates/playable-template-three/src/global.d.ts +108 -0
- package/project-templates/playable-template-three/src/index.css +18 -0
- package/project-templates/playable-template-three/src/index.html +9 -0
- package/project-templates/playable-template-three/src/index.ts +20 -0
- package/project-templates/playable-template-three/src/managers/AudioManager.ts +538 -0
- package/project-templates/playable-template-three/src/scenes/PreloadScene.ts +305 -0
- package/project-templates/playable-template-three/src/theme/README.md +225 -0
- package/project-templates/playable-template-three/src/theme/blue-theme/assets/.gitkeep +28 -0
- package/project-templates/playable-template-three/src/theme/blue-theme/index.ts +45 -0
- package/project-templates/playable-template-three/src/theme/current.ts +12 -0
- package/project-templates/playable-template-three/src/theme/green-theme/assets/.gitkeep +3 -0
- package/project-templates/playable-template-three/src/theme/green-theme/index.ts +34 -0
- package/project-templates/playable-template-three/src/theme/red-theme/assets/.gitkeep +3 -0
- package/project-templates/playable-template-three/src/theme/red-theme/index.ts +34 -0
- package/project-templates/playable-template-three/tsconfig.json +24 -0
- 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
package/dist/commands/create.js
CHANGED
|
@@ -5,6 +5,7 @@ import path from 'path';
|
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import pc from 'picocolors';
|
|
7
7
|
import ora from 'ora';
|
|
8
|
+
import { resolveCreateTemplate, resolveCreateTemplateDir, } from '../scaffold/templates.js';
|
|
8
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
10
|
const __dirname = path.dirname(__filename);
|
|
10
11
|
/** 生成一个本地占位 Project ID(99xxxxxx),后续可通过 playcraft config set 更新为真实 ID */
|
|
@@ -20,6 +21,43 @@ function toPackageName(name) {
|
|
|
20
21
|
.replace(/[^a-z0-9\-]/g, '')
|
|
21
22
|
.replace(/^-+|-+$/g, '');
|
|
22
23
|
}
|
|
24
|
+
/** 检测目标目录的父目录是否在 Git 仓库内 */
|
|
25
|
+
function isInsideGitWorktree(dir) {
|
|
26
|
+
try {
|
|
27
|
+
execSync('git rev-parse --is-inside-work-tree', {
|
|
28
|
+
cwd: dir,
|
|
29
|
+
stdio: 'pipe',
|
|
30
|
+
});
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function gitBranchExists(cwd, branchName) {
|
|
38
|
+
try {
|
|
39
|
+
execSync(`git show-ref --verify --quiet "refs/heads/${branchName}"`, {
|
|
40
|
+
cwd,
|
|
41
|
+
stdio: 'pipe',
|
|
42
|
+
});
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function getUniqueProjectBranchName(cwd, projectName) {
|
|
50
|
+
const baseName = `project/${toPackageName(path.basename(projectName))}`;
|
|
51
|
+
if (!gitBranchExists(cwd, baseName))
|
|
52
|
+
return baseName;
|
|
53
|
+
const suffixBase = Date.now().toString(36);
|
|
54
|
+
for (let i = 0; i < 100; i += 1) {
|
|
55
|
+
const candidate = `${baseName}-${suffixBase}${i === 0 ? '' : `-${i}`}`;
|
|
56
|
+
if (!gitBranchExists(cwd, candidate))
|
|
57
|
+
return candidate;
|
|
58
|
+
}
|
|
59
|
+
throw new Error(`无法生成唯一 Git 分支名: ${baseName}`);
|
|
60
|
+
}
|
|
23
61
|
/** 初始化 git 仓库并创建初始提交(供 Claude Code 子 agent worktree 隔离使用) */
|
|
24
62
|
async function initGitRepository(targetDir) {
|
|
25
63
|
const gitDir = path.join(targetDir, '.git');
|
|
@@ -45,6 +83,48 @@ async function initGitRepository(targetDir) {
|
|
|
45
83
|
env: gitEnv,
|
|
46
84
|
});
|
|
47
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* 在父 Git 仓库中为新项目创建独立 worktree 并初始化 orphan 分支。
|
|
88
|
+
*
|
|
89
|
+
* - 使用 --no-checkout 避免在 Windows / 大工程场景下复制父仓库文件
|
|
90
|
+
* - checkout --orphan 保证分支历史干净,不含父仓库的任何文件
|
|
91
|
+
* - 后续 add -A + commit 在 worktree 内天然安全
|
|
92
|
+
*/
|
|
93
|
+
async function initParentGitWorktree(targetDir, _projectName) {
|
|
94
|
+
const gitEnv = {
|
|
95
|
+
...process.env,
|
|
96
|
+
GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME ?? 'PlayCraft',
|
|
97
|
+
GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL ?? 'playcraft@local',
|
|
98
|
+
GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME ?? 'PlayCraft',
|
|
99
|
+
GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL ?? 'playcraft@local',
|
|
100
|
+
};
|
|
101
|
+
const parentDir = path.dirname(targetDir);
|
|
102
|
+
// 零膨胀创建 worktree:不 checkout 任何文件
|
|
103
|
+
execSync(`git worktree add --no-checkout --detach "${targetDir}" HEAD`, {
|
|
104
|
+
cwd: parentDir,
|
|
105
|
+
stdio: 'pipe',
|
|
106
|
+
env: gitEnv,
|
|
107
|
+
});
|
|
108
|
+
// 创建无历史 orphan 分支
|
|
109
|
+
execSync('git checkout --orphan worktree-init', {
|
|
110
|
+
cwd: targetDir,
|
|
111
|
+
stdio: 'pipe',
|
|
112
|
+
env: gitEnv,
|
|
113
|
+
});
|
|
114
|
+
// checkout --orphan 不清空 index(保留 HEAD tree),需要手动清理
|
|
115
|
+
execSync('git rm --cached -r --ignore-unmatch .', {
|
|
116
|
+
cwd: targetDir,
|
|
117
|
+
stdio: 'pipe',
|
|
118
|
+
env: gitEnv,
|
|
119
|
+
});
|
|
120
|
+
// 清理 orphan checkout 遗留文件;-x 确保父仓库 ignored 产物不会混入新模板。
|
|
121
|
+
execSync('git clean -fdx', {
|
|
122
|
+
cwd: targetDir,
|
|
123
|
+
stdio: 'pipe',
|
|
124
|
+
env: gitEnv,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/** 任何层级都要排除的通用产物(文件名 / 目录名) */
|
|
48
128
|
const TEMPLATE_COPY_EXCLUDES = new Set([
|
|
49
129
|
'.DS_Store',
|
|
50
130
|
'.git',
|
|
@@ -52,35 +132,42 @@ const TEMPLATE_COPY_EXCLUDES = new Set([
|
|
|
52
132
|
'dist',
|
|
53
133
|
'package-lock.json',
|
|
54
134
|
'playable.ui.profile.yaml',
|
|
55
|
-
'playground',
|
|
56
|
-
'recordings',
|
|
57
|
-
'validate',
|
|
58
135
|
]);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
136
|
+
/**
|
|
137
|
+
* 仅作为「模板根直接子目录」时才排除的运行时产物目录。
|
|
138
|
+
* 这些名字也会出现在 skills 内部(如 skills/playable-focus/templates/playground),
|
|
139
|
+
* 故只在顶层排除,避免误伤 skills 的模板素材(否则 bootstrap-playground.mjs 会因找不到
|
|
140
|
+
* registry.template.json 而 ENOENT 退出)。
|
|
141
|
+
*/
|
|
142
|
+
const TEMPLATE_ROOTLEVEL_EXCLUDES = new Set(['playground', 'recordings', 'validate']);
|
|
143
|
+
function shouldCopyTemplateEntry(srcPath, entryName, templateRoot) {
|
|
63
144
|
if (TEMPLATE_COPY_EXCLUDES.has(entryName))
|
|
64
145
|
return false;
|
|
146
|
+
if (TEMPLATE_ROOTLEVEL_EXCLUDES.has(entryName) && path.dirname(srcPath) === templateRoot) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
65
149
|
if (srcPath.includes(`${path.sep}ta-workspace${path.sep}logs${path.sep}`))
|
|
66
150
|
return entryName === '.gitkeep';
|
|
67
151
|
if (srcPath.includes(`${path.sep}ta-workspace${path.sep}tmp${path.sep}`))
|
|
68
152
|
return entryName === '.gitkeep';
|
|
69
|
-
|
|
153
|
+
// 顶层 focus-lab/ 是运行时 lab 目录,只保留 .gitkeep 占位;但 skills 内部的同名目录
|
|
154
|
+
// (如 skills/playable-focus/templates/focus-lab)是模板素材,需正常复制,故限定在模板根之下。
|
|
155
|
+
const toplevelFocusLabPrefix = path.join(templateRoot, 'focus-lab') + path.sep;
|
|
156
|
+
if (srcPath.startsWith(toplevelFocusLabPrefix))
|
|
70
157
|
return entryName === '.gitkeep';
|
|
71
158
|
return true;
|
|
72
159
|
}
|
|
73
160
|
/** 递归复制目录,跳过模板运行时产物 */
|
|
74
|
-
async function copyDir(src, dest) {
|
|
161
|
+
async function copyDir(src, dest, templateRoot) {
|
|
75
162
|
await fs.mkdir(dest, { recursive: true });
|
|
76
163
|
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
77
164
|
for (const entry of entries) {
|
|
78
165
|
const srcPath = path.join(src, entry.name);
|
|
79
|
-
if (!shouldCopyTemplateEntry(srcPath, entry.name))
|
|
166
|
+
if (!shouldCopyTemplateEntry(srcPath, entry.name, templateRoot))
|
|
80
167
|
continue;
|
|
81
168
|
const destPath = path.join(dest, entry.name);
|
|
82
169
|
if (entry.isDirectory()) {
|
|
83
|
-
await copyDir(srcPath, destPath);
|
|
170
|
+
await copyDir(srcPath, destPath, templateRoot);
|
|
84
171
|
}
|
|
85
172
|
else {
|
|
86
173
|
await fs.copyFile(srcPath, destPath);
|
|
@@ -860,7 +947,12 @@ async function setupPlatformSkillLinks(targetDir, cliCommandsDir) {
|
|
|
860
947
|
}
|
|
861
948
|
async function runEnsureProject(targetDir) {
|
|
862
949
|
const ensureScript = path.join(targetDir, 'skills', '_shared', 'scripts', 'ensure-project.mjs');
|
|
863
|
-
|
|
950
|
+
try {
|
|
951
|
+
await fs.access(ensureScript);
|
|
952
|
+
}
|
|
953
|
+
catch {
|
|
954
|
+
return { warnings: ['ensure-project script not found; skipped workflow bootstrap'] };
|
|
955
|
+
}
|
|
864
956
|
const output = execFileSync(process.execPath, [ensureScript, `--root=${targetDir}`, '--no-install', '--json'], {
|
|
865
957
|
cwd: targetDir,
|
|
866
958
|
encoding: 'utf8',
|
|
@@ -873,97 +965,294 @@ async function runEnsureProject(targetDir) {
|
|
|
873
965
|
return { warnings: ['ensure-project completed but returned non-JSON (JavaScript Object Notation, JavaScript 对象表示法) output'] };
|
|
874
966
|
}
|
|
875
967
|
}
|
|
968
|
+
async function runNpmInstall(targetDir) {
|
|
969
|
+
execSync('npm install', {
|
|
970
|
+
cwd: targetDir,
|
|
971
|
+
stdio: 'pipe',
|
|
972
|
+
encoding: 'utf8',
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
async function runHarnessSmoke(targetDir) {
|
|
976
|
+
const smokeScript = path.join(targetDir, 'skills', '_shared', 'scripts', 'harness-smoke.mjs');
|
|
977
|
+
try {
|
|
978
|
+
await fs.access(smokeScript);
|
|
979
|
+
}
|
|
980
|
+
catch {
|
|
981
|
+
return {
|
|
982
|
+
ok: true,
|
|
983
|
+
checks: [{ name: 'harness:smoke', status: 'skip', message: 'harness-smoke script not found' }],
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
const output = execFileSync(process.execPath, [smokeScript, `--root=${targetDir}`, '--json'], {
|
|
987
|
+
cwd: targetDir,
|
|
988
|
+
encoding: 'utf8',
|
|
989
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
990
|
+
});
|
|
991
|
+
return JSON.parse(output);
|
|
992
|
+
}
|
|
993
|
+
async function patchPlayablePackageJson(targetDir, name) {
|
|
994
|
+
const pkgPath = path.join(targetDir, 'package.json');
|
|
995
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
|
996
|
+
pkg.name = toPackageName(name);
|
|
997
|
+
pkg.description = `${name} — playable ad project`;
|
|
998
|
+
await fs.writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
999
|
+
}
|
|
1000
|
+
async function initializeAgentProject(targetDir) {
|
|
1001
|
+
await setupPlatformSkillLinks(targetDir, __dirname);
|
|
1002
|
+
const codebuddySpinner = ora('生成 CodeBuddy Code 配置...').start();
|
|
1003
|
+
try {
|
|
1004
|
+
await setupCodeBuddyEnv(targetDir, __dirname);
|
|
1005
|
+
codebuddySpinner.succeed('CodeBuddy Code 配置生成完成');
|
|
1006
|
+
}
|
|
1007
|
+
catch (error) {
|
|
1008
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1009
|
+
codebuddySpinner.warn(`CodeBuddy Code 配置生成跳过: ${message}`);
|
|
1010
|
+
}
|
|
1011
|
+
const cursorSpinner = ora('生成 Cursor 配置...').start();
|
|
1012
|
+
try {
|
|
1013
|
+
await setupCursorEnv(targetDir);
|
|
1014
|
+
cursorSpinner.succeed('Cursor 配置生成完成');
|
|
1015
|
+
}
|
|
1016
|
+
catch (error) {
|
|
1017
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1018
|
+
cursorSpinner.warn(`Cursor 配置生成跳过: ${message}`);
|
|
1019
|
+
}
|
|
1020
|
+
try {
|
|
1021
|
+
const localPath = path.join(targetDir, '.claude', 'settings.local.json');
|
|
1022
|
+
const localRaw = await fs.readFile(localPath, 'utf-8');
|
|
1023
|
+
const localSettings = JSON.parse(localRaw);
|
|
1024
|
+
fixSettingsLocalPermissions(localSettings, targetDir);
|
|
1025
|
+
await fs.writeFile(localPath, JSON.stringify(localSettings, null, 2) + '\n');
|
|
1026
|
+
}
|
|
1027
|
+
catch {
|
|
1028
|
+
// settings.local.json 不存在或解析失败,跳过
|
|
1029
|
+
}
|
|
1030
|
+
const installSpinner = ora('安装项目依赖...').start();
|
|
1031
|
+
try {
|
|
1032
|
+
await runNpmInstall(targetDir);
|
|
1033
|
+
installSpinner.succeed('项目依赖安装完成');
|
|
1034
|
+
}
|
|
1035
|
+
catch (error) {
|
|
1036
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1037
|
+
installSpinner.fail(`项目依赖安装失败: ${message}`);
|
|
1038
|
+
console.error(pc.dim(' 提示: 请检查网络连接和 Node.js 版本(需 >= 20)'));
|
|
1039
|
+
process.exit(1);
|
|
1040
|
+
}
|
|
1041
|
+
const ensureSpinner = ora('初始化项目工作流...').start();
|
|
1042
|
+
try {
|
|
1043
|
+
const ensureResult = await runEnsureProject(targetDir);
|
|
1044
|
+
ensureSpinner.succeed('项目工作流初始化完成');
|
|
1045
|
+
for (const warning of ensureResult.warnings ?? []) {
|
|
1046
|
+
console.log(pc.dim(` ensure-project: ${warning}`));
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
catch (error) {
|
|
1050
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1051
|
+
const stderr = error.stderr?.trimEnd();
|
|
1052
|
+
const stdout = error.stdout?.trimEnd();
|
|
1053
|
+
ensureSpinner.fail(`项目工作流初始化失败: ${message}`);
|
|
1054
|
+
if (stderr)
|
|
1055
|
+
console.error(pc.red(` ${stderr}`));
|
|
1056
|
+
if (stdout)
|
|
1057
|
+
console.error(pc.dim(` ${stdout}`));
|
|
1058
|
+
process.exit(1);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
function printCreateNextSteps(template, _targetDir, name, here, worktree) {
|
|
1062
|
+
console.log();
|
|
1063
|
+
if (worktree) {
|
|
1064
|
+
console.log(pc.dim('提示:本项目为 Git worktree,分支已独立提交。'));
|
|
1065
|
+
console.log(pc.dim(` 完成后合并到主仓库(合并后文件在 experiments/${name}/ 下,保留完整提交历史):`));
|
|
1066
|
+
console.log(` ${pc.cyan(`git rm --cached -r experiments/${name}/ 2>/dev/null; git read-tree --prefix=experiments/${name}/ ${worktree.projectBranchName}; T=$(git write-tree); C=$(echo "merge: ${name}" | git commit-tree $T -p HEAD -p ${worktree.projectBranchName}); git merge --ff-only $C; git checkout HEAD -- experiments/${name}/`)}`);
|
|
1067
|
+
console.log();
|
|
1068
|
+
}
|
|
1069
|
+
console.log(pc.dim('下一步:'));
|
|
1070
|
+
if (!here) {
|
|
1071
|
+
console.log(` ${pc.cyan(`cd ${name}`)}`);
|
|
1072
|
+
}
|
|
1073
|
+
if (template.kind === 'agent') {
|
|
1074
|
+
console.log(` ${pc.cyan('playcraft skills init-atom-tree --intent "<your game keywords>"')}`);
|
|
1075
|
+
console.log(` ${pc.cyan('playcraft remix init-template --from-atom-tree')}`);
|
|
1076
|
+
console.log(` ${pc.cyan('npm run verify:contract && npm run dev')}`);
|
|
1077
|
+
console.log();
|
|
1078
|
+
console.log(pc.dim('如需使用 PlayCraft CLI 打渠道包:'));
|
|
1079
|
+
console.log(` ${pc.cyan('playcraft build . -p facebook')}`);
|
|
1080
|
+
console.log();
|
|
1081
|
+
console.log(pc.dim('正式接入 PlayCraft 后,替换项目 ID:'));
|
|
1082
|
+
console.log(` ${pc.cyan('playcraft config set --key projectId --value <真实项目ID>')}`);
|
|
1083
|
+
}
|
|
1084
|
+
else {
|
|
1085
|
+
console.log(` ${pc.cyan('npm install')}`);
|
|
1086
|
+
console.log(` ${pc.cyan('npm run dev')}`);
|
|
1087
|
+
console.log();
|
|
1088
|
+
console.log(pc.dim('构建渠道包:'));
|
|
1089
|
+
console.log(` ${pc.cyan('npm run build -- google')}`);
|
|
1090
|
+
console.log(` ${pc.cyan('npm run builds')}`);
|
|
1091
|
+
console.log();
|
|
1092
|
+
console.log(pc.dim('或使用 PlayCraft CLI:'));
|
|
1093
|
+
console.log(` ${pc.cyan(`playcraft build ${here ? '.' : name} -p facebook`)}`);
|
|
1094
|
+
}
|
|
1095
|
+
console.log();
|
|
1096
|
+
}
|
|
1097
|
+
/** Resolve target directory and display name for `playcraft create`. */
|
|
1098
|
+
export function resolveCreatePaths(baseDir, projectName, here) {
|
|
1099
|
+
const resolvedBase = path.resolve(baseDir);
|
|
1100
|
+
if (here) {
|
|
1101
|
+
const name = projectName?.trim() || path.basename(resolvedBase) || 'project';
|
|
1102
|
+
return { targetDir: resolvedBase, name };
|
|
1103
|
+
}
|
|
1104
|
+
const name = projectName?.trim();
|
|
1105
|
+
if (!name) {
|
|
1106
|
+
throw new Error('project-name is required unless --here is set');
|
|
1107
|
+
}
|
|
1108
|
+
return { targetDir: path.join(resolvedBase, name), name };
|
|
1109
|
+
}
|
|
1110
|
+
/** Returns whether the target directory should be removed before worktree creation. */
|
|
1111
|
+
export async function confirmTargetDirOverwrite(targetDir, displayLabel, force) {
|
|
1112
|
+
try {
|
|
1113
|
+
const stat = await fs.stat(targetDir);
|
|
1114
|
+
if (!stat.isDirectory())
|
|
1115
|
+
return false;
|
|
1116
|
+
const entries = await fs.readdir(targetDir);
|
|
1117
|
+
if (entries.length === 0)
|
|
1118
|
+
return false;
|
|
1119
|
+
if (force)
|
|
1120
|
+
return true;
|
|
1121
|
+
const { overwrite } = await inquirer.prompt([
|
|
1122
|
+
{
|
|
1123
|
+
type: 'confirm',
|
|
1124
|
+
name: 'overwrite',
|
|
1125
|
+
message: pc.yellow(`目录 ${displayLabel} 已存在且非空,继续会覆盖同名文件,是否继续?`),
|
|
1126
|
+
default: false,
|
|
1127
|
+
},
|
|
1128
|
+
]);
|
|
1129
|
+
if (!overwrite) {
|
|
1130
|
+
console.log(pc.yellow('已取消'));
|
|
1131
|
+
process.exit(0);
|
|
1132
|
+
}
|
|
1133
|
+
return true;
|
|
1134
|
+
}
|
|
1135
|
+
catch {
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
876
1139
|
export async function createCommand(projectName, options) {
|
|
877
1140
|
console.log(pc.cyan('\n🎮 PlayCraft 可玩广告项目创建\n'));
|
|
1141
|
+
let template;
|
|
1142
|
+
try {
|
|
1143
|
+
template = resolveCreateTemplate(options.template ?? 'agent');
|
|
1144
|
+
}
|
|
1145
|
+
catch (error) {
|
|
1146
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1147
|
+
console.error(pc.red(message));
|
|
1148
|
+
process.exit(1);
|
|
1149
|
+
}
|
|
878
1150
|
const baseDir = options.dir ? path.resolve(options.dir) : process.cwd();
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
type: 'input',
|
|
909
|
-
name: 'skillsDir',
|
|
910
|
-
message: 'Skills 目录路径 (可选,供 playcraft skills 命令使用,直接回车跳过):',
|
|
911
|
-
default: '',
|
|
912
|
-
validate: async (input) => {
|
|
913
|
-
if (!input.trim())
|
|
1151
|
+
const here = options.here ?? false;
|
|
1152
|
+
const force = options.force ?? false;
|
|
1153
|
+
const nonInteractive = options.nonInteractive === true;
|
|
1154
|
+
const isAgentTemplate = template.kind === 'agent';
|
|
1155
|
+
if (nonInteractive && !projectName && !here) {
|
|
1156
|
+
console.error(pc.red('非交互模式需要指定项目名称: playcraft create <project-name> --non-interactive'));
|
|
1157
|
+
process.exit(1);
|
|
1158
|
+
}
|
|
1159
|
+
let name;
|
|
1160
|
+
let port;
|
|
1161
|
+
let skillsDir;
|
|
1162
|
+
if (nonInteractive) {
|
|
1163
|
+
({ name } = resolveCreatePaths(baseDir, projectName, here));
|
|
1164
|
+
port = isAgentTemplate ? parseInt(options.port || '2468', 10) : 0;
|
|
1165
|
+
skillsDir = '';
|
|
1166
|
+
}
|
|
1167
|
+
else {
|
|
1168
|
+
const answers = await inquirer.prompt([
|
|
1169
|
+
{
|
|
1170
|
+
type: 'input',
|
|
1171
|
+
name: 'projectName',
|
|
1172
|
+
message: '项目目录名:',
|
|
1173
|
+
default: projectName ?? (here ? path.basename(baseDir) : undefined),
|
|
1174
|
+
when: !projectName && !here,
|
|
1175
|
+
validate: (input) => {
|
|
1176
|
+
if (!input.trim())
|
|
1177
|
+
return '项目名称不能为空';
|
|
1178
|
+
if (/[/\\:*?"<>|]/.test(input))
|
|
1179
|
+
return '项目名称不能包含特殊字符';
|
|
914
1180
|
return true;
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1181
|
+
},
|
|
1182
|
+
},
|
|
1183
|
+
{
|
|
1184
|
+
type: 'input',
|
|
1185
|
+
name: 'port',
|
|
1186
|
+
message: '本地 Agent 端口:',
|
|
1187
|
+
default: '2468',
|
|
1188
|
+
when: isAgentTemplate,
|
|
1189
|
+
validate: (input) => {
|
|
1190
|
+
const parsedPort = parseInt(input, 10);
|
|
1191
|
+
if (isNaN(parsedPort) || parsedPort < 1 || parsedPort > 65535) {
|
|
1192
|
+
return '端口号必须是 1-65535 之间的数字';
|
|
1193
|
+
}
|
|
919
1194
|
return true;
|
|
920
|
-
}
|
|
921
|
-
catch {
|
|
922
|
-
return '目录不存在,请检查路径';
|
|
923
|
-
}
|
|
1195
|
+
},
|
|
924
1196
|
},
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1197
|
+
{
|
|
1198
|
+
type: 'input',
|
|
1199
|
+
name: 'skillsDir',
|
|
1200
|
+
message: 'Skills 目录路径 (可选,供 playcraft skills 命令使用,直接回车跳过):',
|
|
1201
|
+
default: '',
|
|
1202
|
+
when: isAgentTemplate,
|
|
1203
|
+
validate: async (input) => {
|
|
1204
|
+
if (!input.trim())
|
|
1205
|
+
return true;
|
|
1206
|
+
try {
|
|
1207
|
+
const stat = await fs.stat(path.resolve(input));
|
|
1208
|
+
if (!stat.isDirectory())
|
|
1209
|
+
return '路径不是一个目录';
|
|
1210
|
+
return true;
|
|
1211
|
+
}
|
|
1212
|
+
catch {
|
|
1213
|
+
return '目录不存在,请检查路径';
|
|
1214
|
+
}
|
|
1215
|
+
},
|
|
1216
|
+
},
|
|
1217
|
+
{
|
|
1218
|
+
type: 'input',
|
|
1219
|
+
name: 'token',
|
|
1220
|
+
message: '认证令牌 (可选,可稍后通过 playcraft config set 更新):',
|
|
1221
|
+
default: '',
|
|
1222
|
+
when: isAgentTemplate,
|
|
1223
|
+
},
|
|
1224
|
+
]);
|
|
1225
|
+
name = here
|
|
1226
|
+
? (projectName?.trim() || path.basename(baseDir) || 'project')
|
|
1227
|
+
: (projectName || answers.projectName);
|
|
1228
|
+
port = isAgentTemplate ? parseInt(answers.port ?? '2468', 10) : 0;
|
|
1229
|
+
skillsDir = isAgentTemplate ? (answers.skillsDir?.trim() || '') : '';
|
|
1230
|
+
}
|
|
937
1231
|
const projectId = generateMockProjectId();
|
|
938
|
-
const targetDir =
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
name: 'overwrite',
|
|
949
|
-
message: pc.yellow(`目录 ${name} 已存在且非空,继续会覆盖同名文件,是否继续?`),
|
|
950
|
-
default: false,
|
|
951
|
-
},
|
|
952
|
-
]);
|
|
953
|
-
if (!overwrite) {
|
|
954
|
-
console.log(pc.yellow('已取消'));
|
|
955
|
-
process.exit(0);
|
|
956
|
-
}
|
|
1232
|
+
const { targetDir } = resolveCreatePaths(baseDir, name, here);
|
|
1233
|
+
const overwriteLabel = here ? targetDir : name;
|
|
1234
|
+
let projectBranchName = `project/${toPackageName(path.basename(name))}`;
|
|
1235
|
+
const shouldOverwrite = await confirmTargetDirOverwrite(targetDir, overwriteLabel, force);
|
|
1236
|
+
let isWorktreeMode = isInsideGitWorktree(path.dirname(targetDir));
|
|
1237
|
+
if (isWorktreeMode) {
|
|
1238
|
+
const wtSpinner = ora('创建 Git worktree...').start();
|
|
1239
|
+
try {
|
|
1240
|
+
if (shouldOverwrite) {
|
|
1241
|
+
await fs.rm(targetDir, { recursive: true, force: true });
|
|
957
1242
|
}
|
|
1243
|
+
await initParentGitWorktree(targetDir, name);
|
|
1244
|
+
projectBranchName = getUniqueProjectBranchName(targetDir, name);
|
|
1245
|
+
wtSpinner.succeed('Git worktree 创建完成');
|
|
1246
|
+
}
|
|
1247
|
+
catch (error) {
|
|
1248
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1249
|
+
wtSpinner.warn(`Git worktree 创建跳过: ${message}`);
|
|
1250
|
+
isWorktreeMode = false;
|
|
958
1251
|
}
|
|
959
1252
|
}
|
|
960
|
-
|
|
961
|
-
// 目录不存在,正常继续
|
|
962
|
-
}
|
|
963
|
-
const spinner = ora('复制项目模板...').start();
|
|
1253
|
+
const spinner = ora(`复制项目模板 (${template.id})...`).start();
|
|
964
1254
|
try {
|
|
965
|
-
|
|
966
|
-
const templateDir = path.resolve(__dirname, '..', '..', 'project-template');
|
|
1255
|
+
const templateDir = resolveCreateTemplateDir(template.id);
|
|
967
1256
|
try {
|
|
968
1257
|
await fs.access(templateDir);
|
|
969
1258
|
}
|
|
@@ -971,87 +1260,78 @@ export async function createCommand(projectName, options) {
|
|
|
971
1260
|
spinner.fail(`找不到项目模板目录: ${templateDir}`);
|
|
972
1261
|
process.exit(1);
|
|
973
1262
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
await setupPlatformSkillLinks(targetDir, __dirname);
|
|
979
|
-
// 为 CodeBuddy Code 生成独立的 .codebuddy/ 配置目录
|
|
980
|
-
const codebuddySpinner = ora('生成 CodeBuddy Code 配置...').start();
|
|
981
|
-
try {
|
|
982
|
-
await setupCodeBuddyEnv(targetDir, __dirname);
|
|
983
|
-
codebuddySpinner.succeed('CodeBuddy Code 配置生成完成');
|
|
984
|
-
}
|
|
985
|
-
catch (error) {
|
|
986
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
987
|
-
codebuddySpinner.warn(`CodeBuddy Code 配置生成跳过: ${message}`);
|
|
1263
|
+
await copyDir(templateDir, targetDir, templateDir);
|
|
1264
|
+
spinner.succeed(`项目模板复制完成 (${template.label})`);
|
|
1265
|
+
if (template.kind === 'playable') {
|
|
1266
|
+
await patchPlayablePackageJson(targetDir, name);
|
|
988
1267
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
try {
|
|
992
|
-
await setupCursorEnv(targetDir);
|
|
993
|
-
cursorSpinner.succeed('Cursor 配置生成完成');
|
|
994
|
-
}
|
|
995
|
-
catch (error) {
|
|
996
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
997
|
-
cursorSpinner.warn(`Cursor 配置生成跳过: ${message}`);
|
|
998
|
-
}
|
|
999
|
-
// 修复 .claude/settings.local.json 中硬编码的开发者路径
|
|
1000
|
-
try {
|
|
1001
|
-
const localPath = path.join(targetDir, '.claude', 'settings.local.json');
|
|
1002
|
-
const localRaw = await fs.readFile(localPath, 'utf-8');
|
|
1003
|
-
const localSettings = JSON.parse(localRaw);
|
|
1004
|
-
fixSettingsLocalPermissions(localSettings, targetDir);
|
|
1005
|
-
await fs.writeFile(localPath, JSON.stringify(localSettings, null, 2) + '\n');
|
|
1006
|
-
}
|
|
1007
|
-
catch {
|
|
1008
|
-
// settings.local.json 不存在或解析失败,跳过
|
|
1268
|
+
else {
|
|
1269
|
+
await initializeAgentProject(targetDir);
|
|
1009
1270
|
}
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1271
|
+
if (isAgentTemplate) {
|
|
1272
|
+
const smokeSpinner = ora('Harness 自检...').start();
|
|
1273
|
+
try {
|
|
1274
|
+
const smokeResult = await runHarnessSmoke(targetDir);
|
|
1275
|
+
if (smokeResult.ok) {
|
|
1276
|
+
smokeSpinner.succeed('Harness 自检通过');
|
|
1277
|
+
}
|
|
1278
|
+
else {
|
|
1279
|
+
smokeSpinner.warn('Harness 自检未通过(项目仍可用)');
|
|
1280
|
+
for (const check of smokeResult.checks) {
|
|
1281
|
+
if (check.status === 'fail') {
|
|
1282
|
+
console.log(pc.yellow(` ✗ ${check.name}: ${check.message}`));
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
catch (error) {
|
|
1288
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1289
|
+
smokeSpinner.warn(`Harness 自检跳过: ${message}`);
|
|
1016
1290
|
}
|
|
1017
1291
|
}
|
|
1018
|
-
|
|
1019
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1020
|
-
ensureSpinner.fail(`项目工作流初始化失败: ${message}`);
|
|
1021
|
-
process.exit(1);
|
|
1022
|
-
}
|
|
1023
|
-
const gitSpinner = ora('初始化 Git 仓库...').start();
|
|
1292
|
+
const gitSpinner = ora(isWorktreeMode ? '提交项目分支...' : '初始化 Git 仓库...').start();
|
|
1024
1293
|
try {
|
|
1025
|
-
|
|
1026
|
-
|
|
1294
|
+
if (isWorktreeMode) {
|
|
1295
|
+
execSync(`git branch -m "${projectBranchName}"`, {
|
|
1296
|
+
cwd: targetDir,
|
|
1297
|
+
stdio: 'pipe',
|
|
1298
|
+
});
|
|
1299
|
+
execSync('git add -A', {
|
|
1300
|
+
cwd: targetDir,
|
|
1301
|
+
stdio: 'pipe',
|
|
1302
|
+
});
|
|
1303
|
+
execSync('git commit -m "chore: initial commit from playcraft create"', {
|
|
1304
|
+
cwd: targetDir,
|
|
1305
|
+
stdio: 'pipe',
|
|
1306
|
+
});
|
|
1307
|
+
gitSpinner.succeed('项目分支提交完成');
|
|
1308
|
+
}
|
|
1309
|
+
else {
|
|
1310
|
+
await initGitRepository(targetDir);
|
|
1311
|
+
gitSpinner.succeed('Git 仓库初始化完成');
|
|
1312
|
+
}
|
|
1027
1313
|
}
|
|
1028
1314
|
catch (error) {
|
|
1029
1315
|
const message = error instanceof Error ? error.message : String(error);
|
|
1030
1316
|
gitSpinner.warn(`Git 初始化跳过: ${message}`);
|
|
1031
1317
|
console.log(pc.dim(' 提示: 安装 git 后可在项目目录执行 git init && git add -A && git commit -m "initial"'));
|
|
1032
1318
|
}
|
|
1033
|
-
// 完成提示
|
|
1034
1319
|
console.log(pc.green(`\n✅ 项目创建完成!\n`));
|
|
1035
1320
|
console.log(pc.bold(' 目录: ') + pc.cyan(targetDir));
|
|
1036
|
-
console.log(pc.bold('
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1321
|
+
console.log(pc.bold(' 模板: ') + pc.cyan(template.id) + pc.dim(` (${template.label})`));
|
|
1322
|
+
if (isAgentTemplate) {
|
|
1323
|
+
console.log(pc.bold(' 项目ID: ') +
|
|
1324
|
+
pc.cyan(projectId) +
|
|
1325
|
+
pc.dim(' (临时占位 ID,正式接入 PlayCraft 后请更新)'));
|
|
1326
|
+
console.log(pc.bold(' 端口: ') + pc.cyan(String(port)));
|
|
1327
|
+
if (isWorktreeMode) {
|
|
1328
|
+
console.log(pc.bold(' 分支: ') + pc.cyan(projectBranchName));
|
|
1329
|
+
}
|
|
1330
|
+
if (skillsDir) {
|
|
1331
|
+
console.log(pc.bold(' Skills: ') + pc.cyan(skillsDir));
|
|
1332
|
+
}
|
|
1040
1333
|
}
|
|
1041
|
-
|
|
1042
|
-
console.log(pc.dim('下一步:'));
|
|
1043
|
-
console.log(` ${pc.cyan(`cd ${name}`)}`);
|
|
1044
|
-
console.log(` ${pc.cyan('npm install')}`);
|
|
1045
|
-
console.log(` ${pc.cyan('playcraft skills init-atom-tree --intent "<your game keywords>"')}`);
|
|
1046
|
-
console.log(` ${pc.cyan('playcraft remix init-template --from-atom-tree')}`);
|
|
1047
|
-
console.log(` ${pc.cyan('npm run verify:contract && npm run dev')}`);
|
|
1048
|
-
console.log();
|
|
1049
|
-
console.log(pc.dim('如需使用 PlayCraft CLI 打渠道包:'));
|
|
1050
|
-
console.log(` ${pc.cyan('playcraft build . -p facebook')}`);
|
|
1051
|
-
console.log();
|
|
1052
|
-
console.log(pc.dim('正式接入 PlayCraft 后,替换项目 ID:'));
|
|
1053
|
-
console.log(` ${pc.cyan(`playcraft config set --key projectId --value <真实项目ID>`)}`);
|
|
1054
|
-
console.log();
|
|
1334
|
+
printCreateNextSteps(template, targetDir, name, here, isWorktreeMode ? { projectBranchName } : undefined);
|
|
1055
1335
|
}
|
|
1056
1336
|
catch (error) {
|
|
1057
1337
|
spinner.fail('项目创建失败');
|