@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.
Files changed (167) hide show
  1. package/README.md +21 -0
  2. package/REMIX.md +103 -19
  3. package/dist/atom-tree/fusion-hints.js +8 -3
  4. package/dist/atom-tree/fusion-intent.js +62 -0
  5. package/dist/atom-tree/selection.js +3 -1
  6. package/dist/commands/create.js +299 -85
  7. package/dist/commands/image.js +79 -69
  8. package/dist/commands/levels.js +29 -0
  9. package/dist/commands/patterns.js +75 -0
  10. package/dist/commands/remix.js +47 -0
  11. package/dist/commands/skills.js +48 -17
  12. package/dist/index.js +9 -5
  13. package/dist/remix/clone-source.js +208 -0
  14. package/dist/remix/clone.js +255 -0
  15. package/dist/scaffold/templates.js +1 -1
  16. package/dist/utils/agent-api-client.js +10 -3
  17. package/dist/utils/remix-image-attachments.js +4 -32
  18. package/dist/utils/sandbox-path.js +20 -0
  19. package/package.json +3 -3
  20. package/project-template/.claude/agents/creative-director.md +114 -0
  21. package/project-template/.claude/agents/developer.md +5 -2
  22. package/project-template/.claude/agents/game-designer.md +41 -20
  23. package/project-template/.claude/agents/refs/README.md +8 -0
  24. package/project-template/.claude/agents/refs/creative-director-guide.md +35 -0
  25. package/project-template/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
  26. package/project-template/.claude/agents/refs/creative-discovery-guide.md +76 -0
  27. package/project-template/.claude/agents/refs/creative-north-star-examples.md +82 -0
  28. package/project-template/.claude/agents/refs/developer-dev-handoff.md +1 -1
  29. package/project-template/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
  30. package/project-template/.claude/agents/refs/game-designer-gameplay-sufficiency.md +4 -4
  31. package/project-template/.claude/agents/reviewer.md +11 -0
  32. package/project-template/.claude/hooks/README.md +12 -12
  33. package/project-template/.claude/hooks/block-developer-preflight.mjs +49 -13
  34. package/project-template/.claude/hooks/block-ring1-tasklist.mjs +1 -1
  35. package/project-template/.claude/hooks/user-prompt.mjs +3 -13
  36. package/project-template/.claude/hooks/validate-workflow-stop.mjs +209 -12
  37. package/project-template/.claude/settings.json +2 -2
  38. package/project-template/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
  39. package/project-template/.claude/skills/session-analyzer/scripts/resolve-session.mjs +0 -1
  40. package/project-template/.claude/snapshot.mjs +0 -1
  41. package/project-template/.codebuddy/settings.json +1 -1
  42. package/project-template/.cursor/rules/playcraft-orchestrator.mdc +1 -1
  43. package/project-template/CLAUDE.md +9 -9
  44. package/project-template/docs/project-state.md +1 -1
  45. package/project-template/docs/team/agent-conduct.md +18 -18
  46. package/project-template/docs/team/collaboration.md +19 -7
  47. package/project-template/docs/team/workflow-changelog.md +15 -0
  48. package/project-template/fusion-patterns/catalog.json +77 -0
  49. package/project-template/package.json +2 -0
  50. package/project-template/skills/_shared/scripts/create-playable.mjs +31 -11
  51. package/project-template/skills/_shared/scripts/dev-start.mjs +3 -3
  52. package/project-template/skills/_shared/scripts/disclosure-config.mjs +65 -39
  53. package/project-template/skills/_shared/scripts/harness-smoke.mjs +3 -1
  54. package/project-template/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
  55. package/project-template/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
  56. package/project-template/skills/_shared/scripts/slice-set.mjs +29 -2
  57. package/project-template/skills/_shared/scripts/state-get.mjs +124 -7
  58. package/project-template/skills/_shared/scripts/state-store.mjs +25 -2
  59. package/project-template/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
  60. package/project-template/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
  61. package/project-template/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
  62. package/project-template/skills/playable-record/scripts/record-playable.mjs +2 -1
  63. package/project-template/skills/playable-record/scripts/verify-contract.mjs +4 -4
  64. package/project-template/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
  65. package/project-template/skills/playable-record/scripts/verify-fusion.mjs +37 -2
  66. package/project-template/skills/playable-record/scripts/verify-slice.mjs +2 -2
  67. package/project-template/templates/design-brief.template.md +34 -0
  68. package/project-template/templates/design-methodology.md +1 -2
  69. package/project-template/templates/design.template.md +65 -0
  70. package/project-template/templates/developer-log.template.md +12 -1
  71. package/project-template/templates/five-axis-framework.md +12 -0
  72. package/project-template/templates/game-designer-creative-process.template.md +99 -0
  73. package/project-template/templates/level-sketch.template.json +56 -0
  74. package/project-template/templates/north-star.template.md +43 -0
  75. package/project-template/templates/project-state.template.md +1 -1
  76. package/project-template/vite.config.ts +4 -5
  77. package/project-templates/phaser/.claude/agents/creative-director.md +114 -0
  78. package/project-templates/phaser/.claude/agents/game-designer.md +41 -20
  79. package/project-templates/phaser/.claude/agents/refs/creative-director-guide.md +35 -0
  80. package/project-templates/phaser/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
  81. package/project-templates/phaser/.claude/agents/refs/creative-discovery-guide.md +76 -0
  82. package/project-templates/phaser/.claude/agents/refs/creative-north-star-examples.md +82 -0
  83. package/project-templates/phaser/.claude/agents/refs/developer-dev-handoff.md +1 -1
  84. package/project-templates/phaser/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
  85. package/project-templates/phaser/.claude/agents/reviewer.md +11 -0
  86. package/project-templates/phaser/.claude/hooks/block-ring1-tasklist.mjs +2 -2
  87. package/project-templates/phaser/.claude/hooks/user-prompt.mjs +17 -18
  88. package/project-templates/phaser/.claude/hooks/validate-workflow-stop.mjs +209 -12
  89. package/project-templates/phaser/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
  90. package/project-templates/phaser/.cursor/rules/playcraft-orchestrator.mdc +1 -1
  91. package/project-templates/phaser/CLAUDE.md +88 -54
  92. package/project-templates/phaser/docs/DESIGN.md +25 -0
  93. package/project-templates/phaser/docs/project-state.md +1 -1
  94. package/project-templates/phaser/docs/team/collaboration.md +19 -7
  95. package/project-templates/phaser/docs/team/workflow-changelog.md +14 -0
  96. package/project-templates/phaser/fusion-patterns/catalog.json +77 -0
  97. package/project-templates/phaser/package.json +3 -1
  98. package/project-templates/phaser/skills/_shared/scripts/dev-start.mjs +3 -3
  99. package/project-templates/phaser/skills/_shared/scripts/disclosure-config.mjs +65 -39
  100. package/project-templates/phaser/skills/_shared/scripts/harness-smoke.mjs +3 -1
  101. package/project-templates/phaser/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
  102. package/project-templates/phaser/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
  103. package/project-templates/phaser/skills/_shared/scripts/state-get.mjs +124 -7
  104. package/project-templates/phaser/skills/_shared/scripts/state-store.mjs +25 -2
  105. package/project-templates/phaser/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
  106. package/project-templates/phaser/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
  107. package/project-templates/phaser/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
  108. package/project-templates/phaser/skills/playable-record/scripts/record-playable.mjs +2 -1
  109. package/project-templates/phaser/skills/playable-record/scripts/verify-contract.mjs +4 -4
  110. package/project-templates/phaser/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
  111. package/project-templates/phaser/skills/playable-record/scripts/verify-fusion.mjs +37 -2
  112. package/project-templates/phaser/skills/playable-record/scripts/verify-slice.mjs +2 -2
  113. package/project-templates/phaser/templates/design-brief.template.md +42 -11
  114. package/project-templates/phaser/templates/design-methodology.md +1 -2
  115. package/project-templates/phaser/templates/design.template.md +77 -0
  116. package/project-templates/phaser/templates/five-axis-framework.md +50 -1
  117. package/project-templates/phaser/templates/game-designer-creative-process.template.md +99 -0
  118. package/project-templates/phaser/templates/level-sketch.template.json +56 -0
  119. package/project-templates/phaser/templates/north-star.template.md +43 -0
  120. package/project-templates/phaser/templates/project-state.template.md +1 -1
  121. package/project-templates/phaser/vite.config.ts +4 -5
  122. package/project-templates/threejs/.claude/agents/creative-director.md +114 -0
  123. package/project-templates/threejs/.claude/agents/game-designer.md +41 -20
  124. package/project-templates/threejs/.claude/agents/refs/creative-director-guide.md +35 -0
  125. package/project-templates/threejs/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
  126. package/project-templates/threejs/.claude/agents/refs/creative-discovery-guide.md +76 -0
  127. package/project-templates/threejs/.claude/agents/refs/creative-north-star-examples.md +82 -0
  128. package/project-templates/threejs/.claude/agents/refs/developer-dev-handoff.md +1 -1
  129. package/project-templates/threejs/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
  130. package/project-templates/threejs/.claude/agents/reviewer.md +11 -0
  131. package/project-templates/threejs/.claude/hooks/block-ring1-tasklist.mjs +2 -2
  132. package/project-templates/threejs/.claude/hooks/user-prompt.mjs +17 -18
  133. package/project-templates/threejs/.claude/hooks/validate-workflow-stop.mjs +209 -12
  134. package/project-templates/threejs/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
  135. package/project-templates/threejs/.cursor/rules/playcraft-orchestrator.mdc +1 -1
  136. package/project-templates/threejs/CLAUDE.md +88 -54
  137. package/project-templates/threejs/docs/DESIGN.md +25 -0
  138. package/project-templates/threejs/docs/project-state.md +1 -1
  139. package/project-templates/threejs/docs/team/collaboration.md +19 -7
  140. package/project-templates/threejs/docs/team/workflow-changelog.md +14 -0
  141. package/project-templates/threejs/fusion-patterns/catalog.json +77 -0
  142. package/project-templates/threejs/package.json +3 -1
  143. package/project-templates/threejs/skills/_shared/scripts/dev-start.mjs +3 -3
  144. package/project-templates/threejs/skills/_shared/scripts/disclosure-config.mjs +65 -39
  145. package/project-templates/threejs/skills/_shared/scripts/harness-smoke.mjs +3 -1
  146. package/project-templates/threejs/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
  147. package/project-templates/threejs/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
  148. package/project-templates/threejs/skills/_shared/scripts/state-get.mjs +124 -7
  149. package/project-templates/threejs/skills/_shared/scripts/state-store.mjs +25 -2
  150. package/project-templates/threejs/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
  151. package/project-templates/threejs/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
  152. package/project-templates/threejs/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
  153. package/project-templates/threejs/skills/playable-record/scripts/record-playable.mjs +2 -1
  154. package/project-templates/threejs/skills/playable-record/scripts/verify-contract.mjs +4 -4
  155. package/project-templates/threejs/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
  156. package/project-templates/threejs/skills/playable-record/scripts/verify-fusion.mjs +37 -2
  157. package/project-templates/threejs/skills/playable-record/scripts/verify-slice.mjs +2 -2
  158. package/project-templates/threejs/templates/design-brief.template.md +42 -11
  159. package/project-templates/threejs/templates/design-methodology.md +1 -2
  160. package/project-templates/threejs/templates/design.template.md +77 -0
  161. package/project-templates/threejs/templates/five-axis-framework.md +50 -1
  162. package/project-templates/threejs/templates/game-designer-creative-process.template.md +99 -0
  163. package/project-templates/threejs/templates/level-sketch.template.json +56 -0
  164. package/project-templates/threejs/templates/north-star.template.md +43 -0
  165. package/project-templates/threejs/templates/project-state.template.md +1 -1
  166. package/project-templates/threejs/vite.config.ts +4 -5
  167. package/project-template/docs/DESIGN.md +0 -65
@@ -21,6 +21,43 @@ function toPackageName(name) {
21
21
  .replace(/[^a-z0-9\-]/g, '')
22
22
  .replace(/^-+|-+$/g, '');
23
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
+ }
24
61
  /** 初始化 git 仓库并创建初始提交(供 Claude Code 子 agent worktree 隔离使用) */
25
62
  async function initGitRepository(targetDir) {
26
63
  const gitDir = path.join(targetDir, '.git');
@@ -46,6 +83,48 @@ async function initGitRepository(targetDir) {
46
83
  env: gitEnv,
47
84
  });
48
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
+ /** 任何层级都要排除的通用产物(文件名 / 目录名) */
49
128
  const TEMPLATE_COPY_EXCLUDES = new Set([
50
129
  '.DS_Store',
51
130
  '.git',
@@ -53,35 +132,42 @@ const TEMPLATE_COPY_EXCLUDES = new Set([
53
132
  'dist',
54
133
  'package-lock.json',
55
134
  'playable.ui.profile.yaml',
56
- 'playground',
57
- 'recordings',
58
- 'validate',
59
135
  ]);
60
- function shouldCopyTemplateEntry(srcPath, entryName) {
61
- if (srcPath.includes(`${path.sep}skills${path.sep}playable-focus${path.sep}templates${path.sep}`)) {
62
- return true;
63
- }
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) {
64
144
  if (TEMPLATE_COPY_EXCLUDES.has(entryName))
65
145
  return false;
146
+ if (TEMPLATE_ROOTLEVEL_EXCLUDES.has(entryName) && path.dirname(srcPath) === templateRoot) {
147
+ return false;
148
+ }
66
149
  if (srcPath.includes(`${path.sep}ta-workspace${path.sep}logs${path.sep}`))
67
150
  return entryName === '.gitkeep';
68
151
  if (srcPath.includes(`${path.sep}ta-workspace${path.sep}tmp${path.sep}`))
69
152
  return entryName === '.gitkeep';
70
- if (srcPath.includes(`${path.sep}focus-lab${path.sep}`))
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))
71
157
  return entryName === '.gitkeep';
72
158
  return true;
73
159
  }
74
160
  /** 递归复制目录,跳过模板运行时产物 */
75
- async function copyDir(src, dest) {
161
+ async function copyDir(src, dest, templateRoot) {
76
162
  await fs.mkdir(dest, { recursive: true });
77
163
  const entries = await fs.readdir(src, { withFileTypes: true });
78
164
  for (const entry of entries) {
79
165
  const srcPath = path.join(src, entry.name);
80
- if (!shouldCopyTemplateEntry(srcPath, entry.name))
166
+ if (!shouldCopyTemplateEntry(srcPath, entry.name, templateRoot))
81
167
  continue;
82
168
  const destPath = path.join(dest, entry.name);
83
169
  if (entry.isDirectory()) {
84
- await copyDir(srcPath, destPath);
170
+ await copyDir(srcPath, destPath, templateRoot);
85
171
  }
86
172
  else {
87
173
  await fs.copyFile(srcPath, destPath);
@@ -861,7 +947,12 @@ async function setupPlatformSkillLinks(targetDir, cliCommandsDir) {
861
947
  }
862
948
  async function runEnsureProject(targetDir) {
863
949
  const ensureScript = path.join(targetDir, 'skills', '_shared', 'scripts', 'ensure-project.mjs');
864
- await fs.access(ensureScript);
950
+ try {
951
+ await fs.access(ensureScript);
952
+ }
953
+ catch {
954
+ return { warnings: ['ensure-project script not found; skipped workflow bootstrap'] };
955
+ }
865
956
  const output = execFileSync(process.execPath, [ensureScript, `--root=${targetDir}`, '--no-install', '--json'], {
866
957
  cwd: targetDir,
867
958
  encoding: 'utf8',
@@ -874,6 +965,31 @@ async function runEnsureProject(targetDir) {
874
965
  return { warnings: ['ensure-project completed but returned non-JSON (JavaScript Object Notation, JavaScript 对象表示法) output'] };
875
966
  }
876
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
+ }
877
993
  async function patchPlayablePackageJson(targetDir, name) {
878
994
  const pkgPath = path.join(targetDir, 'package.json');
879
995
  const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
@@ -911,6 +1027,17 @@ async function initializeAgentProject(targetDir) {
911
1027
  catch {
912
1028
  // settings.local.json 不存在或解析失败,跳过
913
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
+ }
914
1041
  const ensureSpinner = ora('初始化项目工作流...').start();
915
1042
  try {
916
1043
  const ensureResult = await runEnsureProject(targetDir);
@@ -921,17 +1048,28 @@ async function initializeAgentProject(targetDir) {
921
1048
  }
922
1049
  catch (error) {
923
1050
  const message = error instanceof Error ? error.message : String(error);
1051
+ const stderr = error.stderr?.trimEnd();
1052
+ const stdout = error.stdout?.trimEnd();
924
1053
  ensureSpinner.fail(`项目工作流初始化失败: ${message}`);
1054
+ if (stderr)
1055
+ console.error(pc.red(` ${stderr}`));
1056
+ if (stdout)
1057
+ console.error(pc.dim(` ${stdout}`));
925
1058
  process.exit(1);
926
1059
  }
927
1060
  }
928
- function printCreateNextSteps(template, _targetDir, name, here) {
1061
+ function printCreateNextSteps(template, _targetDir, name, here, worktree) {
929
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
+ }
930
1069
  console.log(pc.dim('下一步:'));
931
1070
  if (!here) {
932
1071
  console.log(` ${pc.cyan(`cd ${name}`)}`);
933
1072
  }
934
- console.log(` ${pc.cyan('npm install')}`);
935
1073
  if (template.kind === 'agent') {
936
1074
  console.log(` ${pc.cyan('playcraft skills init-atom-tree --intent "<your game keywords>"')}`);
937
1075
  console.log(` ${pc.cyan('playcraft remix init-template --from-atom-tree')}`);
@@ -944,6 +1082,7 @@ function printCreateNextSteps(template, _targetDir, name, here) {
944
1082
  console.log(` ${pc.cyan('playcraft config set --key projectId --value <真实项目ID>')}`);
945
1083
  }
946
1084
  else {
1085
+ console.log(` ${pc.cyan('npm install')}`);
947
1086
  console.log(` ${pc.cyan('npm run dev')}`);
948
1087
  console.log();
949
1088
  console.log(pc.dim('构建渠道包:'));
@@ -968,15 +1107,15 @@ export function resolveCreatePaths(baseDir, projectName, here) {
968
1107
  }
969
1108
  return { targetDir: path.join(resolvedBase, name), name };
970
1109
  }
971
- /** Returns false when the user declines overwrite; exits the process. */
1110
+ /** Returns whether the target directory should be removed before worktree creation. */
972
1111
  export async function confirmTargetDirOverwrite(targetDir, displayLabel, force) {
973
1112
  try {
974
1113
  const stat = await fs.stat(targetDir);
975
1114
  if (!stat.isDirectory())
976
- return true;
1115
+ return false;
977
1116
  const entries = await fs.readdir(targetDir);
978
1117
  if (entries.length === 0)
979
- return true;
1118
+ return false;
980
1119
  if (force)
981
1120
  return true;
982
1121
  const { overwrite } = await inquirer.prompt([
@@ -994,7 +1133,7 @@ export async function confirmTargetDirOverwrite(targetDir, displayLabel, force)
994
1133
  return true;
995
1134
  }
996
1135
  catch {
997
- return true;
1136
+ return false;
998
1137
  }
999
1138
  }
1000
1139
  export async function createCommand(projectName, options) {
@@ -1011,73 +1150,106 @@ export async function createCommand(projectName, options) {
1011
1150
  const baseDir = options.dir ? path.resolve(options.dir) : process.cwd();
1012
1151
  const here = options.here ?? false;
1013
1152
  const force = options.force ?? false;
1153
+ const nonInteractive = options.nonInteractive === true;
1014
1154
  const isAgentTemplate = template.kind === 'agent';
1015
- const answers = await inquirer.prompt([
1016
- {
1017
- type: 'input',
1018
- name: 'projectName',
1019
- message: '项目目录名:',
1020
- default: projectName ?? (here ? path.basename(baseDir) : undefined),
1021
- when: !projectName && !here,
1022
- validate: (input) => {
1023
- if (!input.trim())
1024
- return '项目名称不能为空';
1025
- if (/[/\\:*?"<>|]/.test(input))
1026
- return '项目名称不能包含特殊字符';
1027
- return true;
1028
- },
1029
- },
1030
- {
1031
- type: 'input',
1032
- name: 'port',
1033
- message: '本地 Agent 端口:',
1034
- default: '2468',
1035
- when: isAgentTemplate,
1036
- validate: (input) => {
1037
- const port = parseInt(input, 10);
1038
- if (isNaN(port) || port < 1 || port > 65535) {
1039
- return '端口号必须是 1-65535 之间的数字';
1040
- }
1041
- return true;
1042
- },
1043
- },
1044
- {
1045
- type: 'input',
1046
- name: 'skillsDir',
1047
- message: 'Skills 目录路径 (可选,供 playcraft skills 命令使用,直接回车跳过):',
1048
- default: '',
1049
- when: isAgentTemplate,
1050
- validate: async (input) => {
1051
- if (!input.trim())
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 '项目名称不能包含特殊字符';
1052
1180
  return true;
1053
- try {
1054
- const stat = await fs.stat(path.resolve(input));
1055
- if (!stat.isDirectory())
1056
- return '路径不是一个目录';
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
+ }
1057
1194
  return true;
1058
- }
1059
- catch {
1060
- return '目录不存在,请检查路径';
1061
- }
1195
+ },
1062
1196
  },
1063
- },
1064
- {
1065
- type: 'input',
1066
- name: 'token',
1067
- message: '认证令牌 (可选,可稍后通过 playcraft config set 更新):',
1068
- default: '',
1069
- when: isAgentTemplate,
1070
- },
1071
- ]);
1072
- const name = here
1073
- ? (projectName?.trim() || path.basename(baseDir) || 'project')
1074
- : (projectName || answers.projectName);
1075
- const port = isAgentTemplate ? parseInt(answers.port ?? '2468', 10) : 0;
1076
- const skillsDir = isAgentTemplate ? (answers.skillsDir?.trim() || '') : '';
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
+ }
1077
1231
  const projectId = generateMockProjectId();
1078
1232
  const { targetDir } = resolveCreatePaths(baseDir, name, here);
1079
1233
  const overwriteLabel = here ? targetDir : name;
1080
- await confirmTargetDirOverwrite(targetDir, overwriteLabel, force);
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 });
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;
1251
+ }
1252
+ }
1081
1253
  const spinner = ora(`复制项目模板 (${template.id})...`).start();
1082
1254
  try {
1083
1255
  const templateDir = resolveCreateTemplateDir(template.id);
@@ -1088,7 +1260,7 @@ export async function createCommand(projectName, options) {
1088
1260
  spinner.fail(`找不到项目模板目录: ${templateDir}`);
1089
1261
  process.exit(1);
1090
1262
  }
1091
- await copyDir(templateDir, targetDir);
1263
+ await copyDir(templateDir, targetDir, templateDir);
1092
1264
  spinner.succeed(`项目模板复制完成 (${template.label})`);
1093
1265
  if (template.kind === 'playable') {
1094
1266
  await patchPlayablePackageJson(targetDir, name);
@@ -1096,28 +1268,70 @@ export async function createCommand(projectName, options) {
1096
1268
  else {
1097
1269
  await initializeAgentProject(targetDir);
1098
1270
  }
1099
- const gitSpinner = ora('初始化 Git 仓库...').start();
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}`);
1290
+ }
1291
+ }
1292
+ const gitSpinner = ora(isWorktreeMode ? '提交项目分支...' : '初始化 Git 仓库...').start();
1100
1293
  try {
1101
- await initGitRepository(targetDir);
1102
- gitSpinner.succeed('Git 仓库初始化完成');
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
+ }
1103
1313
  }
1104
1314
  catch (error) {
1105
1315
  const message = error instanceof Error ? error.message : String(error);
1106
1316
  gitSpinner.warn(`Git 初始化跳过: ${message}`);
1107
1317
  console.log(pc.dim(' 提示: 安装 git 后可在项目目录执行 git init && git add -A && git commit -m "initial"'));
1108
1318
  }
1109
- // 完成提示
1110
1319
  console.log(pc.green(`\n✅ 项目创建完成!\n`));
1111
1320
  console.log(pc.bold(' 目录: ') + pc.cyan(targetDir));
1112
1321
  console.log(pc.bold(' 模板: ') + pc.cyan(template.id) + pc.dim(` (${template.label})`));
1113
1322
  if (isAgentTemplate) {
1114
- console.log(pc.bold(' 项目ID: ') + pc.cyan(projectId) + pc.dim(' (临时占位 ID,正式接入 PlayCraft 后请更新)'));
1323
+ console.log(pc.bold(' 项目ID: ') +
1324
+ pc.cyan(projectId) +
1325
+ pc.dim(' (临时占位 ID,正式接入 PlayCraft 后请更新)'));
1115
1326
  console.log(pc.bold(' 端口: ') + pc.cyan(String(port)));
1327
+ if (isWorktreeMode) {
1328
+ console.log(pc.bold(' 分支: ') + pc.cyan(projectBranchName));
1329
+ }
1116
1330
  if (skillsDir) {
1117
1331
  console.log(pc.bold(' Skills: ') + pc.cyan(skillsDir));
1118
1332
  }
1119
1333
  }
1120
- printCreateNextSteps(template, targetDir, name, here);
1334
+ printCreateNextSteps(template, targetDir, name, here, isWorktreeMode ? { projectBranchName } : undefined);
1121
1335
  }
1122
1336
  catch (error) {
1123
1337
  spinner.fail('项目创建失败');