@playcraft/cli 0.0.55 → 0.0.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/REMIX.md +115 -19
- package/dist/atom-tree/fusion-hints.js +8 -3
- package/dist/atom-tree/fusion-intent.js +62 -0
- package/dist/atom-tree/selection.js +3 -1
- package/dist/commands/create.js +299 -85
- package/dist/commands/image.js +79 -69
- package/dist/commands/levels.js +29 -0
- package/dist/commands/patterns.js +75 -0
- package/dist/commands/remix.js +93 -0
- package/dist/commands/skills.js +48 -17
- package/dist/index.js +9 -5
- package/dist/remix/clone-source.js +208 -0
- package/dist/remix/clone.js +256 -0
- package/dist/remix/local-git.js +56 -0
- package/dist/remix/pull.js +64 -0
- package/dist/remix/push.js +70 -0
- package/dist/scaffold/templates.js +1 -1
- package/dist/utils/agent-api-client.js +10 -3
- package/dist/utils/remix-image-attachments.js +4 -32
- package/dist/utils/sandbox-path.js +20 -0
- package/package.json +3 -3
- package/project-template/.claude/agents/creative-director.md +114 -0
- package/project-template/.claude/agents/developer.md +5 -2
- package/project-template/.claude/agents/game-designer.md +41 -20
- package/project-template/.claude/agents/refs/README.md +8 -0
- package/project-template/.claude/agents/refs/creative-director-guide.md +35 -0
- package/project-template/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
- package/project-template/.claude/agents/refs/creative-discovery-guide.md +76 -0
- package/project-template/.claude/agents/refs/creative-north-star-examples.md +82 -0
- package/project-template/.claude/agents/refs/developer-dev-handoff.md +1 -1
- package/project-template/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
- package/project-template/.claude/agents/refs/game-designer-gameplay-sufficiency.md +4 -4
- package/project-template/.claude/agents/reviewer.md +11 -0
- package/project-template/.claude/hooks/README.md +12 -12
- package/project-template/.claude/hooks/block-developer-preflight.mjs +49 -13
- package/project-template/.claude/hooks/block-ring1-tasklist.mjs +1 -1
- package/project-template/.claude/hooks/user-prompt.mjs +3 -13
- package/project-template/.claude/hooks/validate-workflow-stop.mjs +209 -12
- package/project-template/.claude/settings.json +2 -2
- package/project-template/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
- package/project-template/.claude/skills/session-analyzer/scripts/resolve-session.mjs +0 -1
- package/project-template/.claude/snapshot.mjs +0 -1
- package/project-template/.codebuddy/settings.json +1 -1
- package/project-template/.cursor/rules/playcraft-orchestrator.mdc +1 -1
- package/project-template/CLAUDE.md +9 -9
- package/project-template/docs/project-state.md +1 -1
- package/project-template/docs/team/agent-conduct.md +18 -18
- package/project-template/docs/team/collaboration.md +19 -7
- package/project-template/docs/team/workflow-changelog.md +15 -0
- package/project-template/fusion-patterns/catalog.json +77 -0
- package/project-template/package.json +2 -0
- package/project-template/skills/_shared/scripts/create-playable.mjs +31 -11
- package/project-template/skills/_shared/scripts/dev-start.mjs +3 -3
- package/project-template/skills/_shared/scripts/disclosure-config.mjs +65 -39
- package/project-template/skills/_shared/scripts/harness-smoke.mjs +3 -1
- package/project-template/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
- package/project-template/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
- package/project-template/skills/_shared/scripts/slice-set.mjs +29 -2
- package/project-template/skills/_shared/scripts/state-get.mjs +124 -7
- package/project-template/skills/_shared/scripts/state-store.mjs +25 -2
- package/project-template/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
- package/project-template/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
- package/project-template/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
- package/project-template/skills/playable-record/scripts/record-playable.mjs +2 -1
- package/project-template/skills/playable-record/scripts/verify-contract.mjs +4 -4
- package/project-template/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
- package/project-template/skills/playable-record/scripts/verify-fusion.mjs +37 -2
- package/project-template/skills/playable-record/scripts/verify-slice.mjs +2 -2
- package/project-template/templates/design-brief.template.md +34 -0
- package/project-template/templates/design-methodology.md +1 -2
- package/project-template/templates/design.template.md +65 -0
- package/project-template/templates/developer-log.template.md +12 -1
- package/project-template/templates/five-axis-framework.md +12 -0
- package/project-template/templates/game-designer-creative-process.template.md +99 -0
- package/project-template/templates/level-sketch.template.json +56 -0
- package/project-template/templates/north-star.template.md +43 -0
- package/project-template/templates/project-state.template.md +1 -1
- package/project-template/vite.config.ts +4 -5
- package/project-templates/phaser/.claude/agents/creative-director.md +114 -0
- package/project-templates/phaser/.claude/agents/game-designer.md +41 -20
- package/project-templates/phaser/.claude/agents/refs/creative-director-guide.md +35 -0
- package/project-templates/phaser/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
- package/project-templates/phaser/.claude/agents/refs/creative-discovery-guide.md +76 -0
- package/project-templates/phaser/.claude/agents/refs/creative-north-star-examples.md +82 -0
- package/project-templates/phaser/.claude/agents/refs/developer-dev-handoff.md +1 -1
- package/project-templates/phaser/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
- package/project-templates/phaser/.claude/agents/reviewer.md +11 -0
- package/project-templates/phaser/.claude/hooks/block-ring1-tasklist.mjs +2 -2
- package/project-templates/phaser/.claude/hooks/user-prompt.mjs +17 -18
- package/project-templates/phaser/.claude/hooks/validate-workflow-stop.mjs +209 -12
- package/project-templates/phaser/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
- package/project-templates/phaser/.cursor/rules/playcraft-orchestrator.mdc +1 -1
- package/project-templates/phaser/CLAUDE.md +88 -54
- package/project-templates/phaser/docs/DESIGN.md +25 -0
- package/project-templates/phaser/docs/project-state.md +1 -1
- package/project-templates/phaser/docs/team/collaboration.md +19 -7
- package/project-templates/phaser/docs/team/workflow-changelog.md +14 -0
- package/project-templates/phaser/fusion-patterns/catalog.json +77 -0
- package/project-templates/phaser/package.json +3 -1
- package/project-templates/phaser/skills/_shared/scripts/dev-start.mjs +3 -3
- package/project-templates/phaser/skills/_shared/scripts/disclosure-config.mjs +65 -39
- package/project-templates/phaser/skills/_shared/scripts/harness-smoke.mjs +3 -1
- package/project-templates/phaser/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
- package/project-templates/phaser/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
- package/project-templates/phaser/skills/_shared/scripts/state-get.mjs +124 -7
- package/project-templates/phaser/skills/_shared/scripts/state-store.mjs +25 -2
- package/project-templates/phaser/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
- package/project-templates/phaser/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
- package/project-templates/phaser/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
- package/project-templates/phaser/skills/playable-record/scripts/record-playable.mjs +2 -1
- package/project-templates/phaser/skills/playable-record/scripts/verify-contract.mjs +4 -4
- package/project-templates/phaser/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
- package/project-templates/phaser/skills/playable-record/scripts/verify-fusion.mjs +37 -2
- package/project-templates/phaser/skills/playable-record/scripts/verify-slice.mjs +2 -2
- package/project-templates/phaser/templates/design-brief.template.md +42 -11
- package/project-templates/phaser/templates/design-methodology.md +1 -2
- package/project-templates/phaser/templates/design.template.md +77 -0
- package/project-templates/phaser/templates/five-axis-framework.md +50 -1
- package/project-templates/phaser/templates/game-designer-creative-process.template.md +99 -0
- package/project-templates/phaser/templates/level-sketch.template.json +56 -0
- package/project-templates/phaser/templates/north-star.template.md +43 -0
- package/project-templates/phaser/templates/project-state.template.md +1 -1
- package/project-templates/phaser/vite.config.ts +4 -5
- package/project-templates/threejs/.claude/agents/creative-director.md +114 -0
- package/project-templates/threejs/.claude/agents/game-designer.md +41 -20
- package/project-templates/threejs/.claude/agents/refs/creative-director-guide.md +35 -0
- package/project-templates/threejs/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
- package/project-templates/threejs/.claude/agents/refs/creative-discovery-guide.md +76 -0
- package/project-templates/threejs/.claude/agents/refs/creative-north-star-examples.md +82 -0
- package/project-templates/threejs/.claude/agents/refs/developer-dev-handoff.md +1 -1
- package/project-templates/threejs/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
- package/project-templates/threejs/.claude/agents/reviewer.md +11 -0
- package/project-templates/threejs/.claude/hooks/block-ring1-tasklist.mjs +2 -2
- package/project-templates/threejs/.claude/hooks/user-prompt.mjs +17 -18
- package/project-templates/threejs/.claude/hooks/validate-workflow-stop.mjs +209 -12
- package/project-templates/threejs/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
- package/project-templates/threejs/.cursor/rules/playcraft-orchestrator.mdc +1 -1
- package/project-templates/threejs/CLAUDE.md +88 -54
- package/project-templates/threejs/docs/DESIGN.md +25 -0
- package/project-templates/threejs/docs/project-state.md +1 -1
- package/project-templates/threejs/docs/team/collaboration.md +19 -7
- package/project-templates/threejs/docs/team/workflow-changelog.md +14 -0
- package/project-templates/threejs/fusion-patterns/catalog.json +77 -0
- package/project-templates/threejs/package.json +3 -1
- package/project-templates/threejs/skills/_shared/scripts/dev-start.mjs +3 -3
- package/project-templates/threejs/skills/_shared/scripts/disclosure-config.mjs +65 -39
- package/project-templates/threejs/skills/_shared/scripts/harness-smoke.mjs +3 -1
- package/project-templates/threejs/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
- package/project-templates/threejs/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
- package/project-templates/threejs/skills/_shared/scripts/state-get.mjs +124 -7
- package/project-templates/threejs/skills/_shared/scripts/state-store.mjs +25 -2
- package/project-templates/threejs/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
- package/project-templates/threejs/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
- package/project-templates/threejs/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
- package/project-templates/threejs/skills/playable-record/scripts/record-playable.mjs +2 -1
- package/project-templates/threejs/skills/playable-record/scripts/verify-contract.mjs +4 -4
- package/project-templates/threejs/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
- package/project-templates/threejs/skills/playable-record/scripts/verify-fusion.mjs +37 -2
- package/project-templates/threejs/skills/playable-record/scripts/verify-slice.mjs +2 -2
- package/project-templates/threejs/templates/design-brief.template.md +42 -11
- package/project-templates/threejs/templates/design-methodology.md +1 -2
- package/project-templates/threejs/templates/design.template.md +77 -0
- package/project-templates/threejs/templates/five-axis-framework.md +50 -1
- package/project-templates/threejs/templates/game-designer-creative-process.template.md +99 -0
- package/project-templates/threejs/templates/level-sketch.template.json +56 -0
- package/project-templates/threejs/templates/north-star.template.md +43 -0
- package/project-templates/threejs/templates/project-state.template.md +1 -1
- package/project-templates/threejs/vite.config.ts +4 -5
- package/project-template/docs/DESIGN.md +0 -65
package/dist/commands/create.js
CHANGED
|
@@ -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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1115
|
+
return false;
|
|
977
1116
|
const entries = await fs.readdir(targetDir);
|
|
978
1117
|
if (entries.length === 0)
|
|
979
|
-
return
|
|
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
|
|
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
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
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
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
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
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1102
|
-
|
|
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: ') +
|
|
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('项目创建失败');
|