@playcraft/cli 0.1.69 → 0.1.71

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 (34) hide show
  1. package/README.md +20 -1
  2. package/dist/commands/skills.js +68 -4
  3. package/dist/project-skills/discovery.js +248 -0
  4. package/dist/project-skills/messages.js +80 -0
  5. package/dist/project-skills/remote-activation.js +500 -0
  6. package/dist/project-skills/remote-client.js +117 -0
  7. package/dist/project-skills/remote-commands.js +87 -0
  8. package/dist/project-skills/remote-prepare.js +223 -0
  9. package/dist/project-skills/search-command.js +47 -0
  10. package/dist/project-skills/search.js +96 -0
  11. package/dist/recording/project-digest.js +1 -1
  12. package/dist/workspace-runtime/batch/content-changes.js +3 -0
  13. package/dist/workspace-runtime/batch/generate.js +4 -1
  14. package/dist/workspace-runtime/batch/prepare.js +5 -1
  15. package/dist/workspace-runtime/batch/source-capture.js +43 -4
  16. package/dist/workspace-runtime/codex/app-server.js +25 -1
  17. package/dist/workspace-runtime/codex/model-broker.js +93 -6
  18. package/dist/workspace-runtime/codex/skill-discovery-broker.js +174 -0
  19. package/dist/workspace-runtime/serve.js +5 -0
  20. package/dist/workspace-runtime/server/dispatch.js +121 -28
  21. package/dist/workspace-runtime/server/http.js +15 -0
  22. package/dist/workspace-runtime/server/operation-record.js +3 -1
  23. package/dist/workspace-runtime/verify-artifact.js +2 -1
  24. package/dist/workspace-runtime/workspaces/files.js +20 -14
  25. package/dist/workspace-runtime/workspaces/remix-references.js +119 -2
  26. package/dist/workspace-runtime/workspaces/skill-activation-recovery.js +54 -0
  27. package/dist/workspace-runtime/workspaces/skill-activation-requests.js +512 -0
  28. package/dist/workspace-runtime/workspaces/skills.js +13 -176
  29. package/package.json +3 -3
  30. package/project-template/.claude/skills/playcraft-skill-recommender/SKILL.md +28 -6
  31. package/project-template-v2/.claude/skills/playcraft-skill-recommender/SKILL.md +28 -6
  32. package/project-templates/empty/gitignore +1 -0
  33. package/project-templates/phaser/.claude/skills/playcraft-skill-recommender/SKILL.md +28 -6
  34. package/project-templates/threejs/.claude/skills/playcraft-skill-recommender/SKILL.md +28 -6
@@ -1,26 +1,17 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { constants } from 'node:fs';
3
- import { cp, lstat, open, readFile, readdir, rm, writeFile } from 'node:fs/promises';
4
- import { basename, dirname, join } from 'node:path';
5
- import { load } from 'js-yaml';
6
- import { canonicalProjectSkillsJson, createCanonicalBundleArchive, effectiveProjectSkills, extractBundleArchive, inspectProjectSkills, readProjectSkillSource, readProjectSkillsLockFile, sourceTreeDigest, validateProjectSkillsSnapshot, } from '@playcraft/common/project-skills';
2
+ import { cp, readdir, rm } from 'node:fs/promises';
3
+ import { basename, join } from 'node:path';
4
+ import { canonicalProjectSkillsJson, inspectProjectSkills, readProjectSkillsLockFile } from '@playcraft/common/project-skills';
5
+ import { ensureProjectSkillDirectory as safeDirectory, readLockedProjectSkillFiles as lockedFiles, projectSkillMetadata as metadata, readLockedProjectSkillSnapshot as readWorkspaceSkillSnapshot, freezeLockedProjectSkills, } from '../../project-skills/discovery.js';
6
+ export { readLockedProjectSkillSnapshot as readWorkspaceSkillSnapshot } from '../../project-skills/discovery.js';
7
7
  import { initializeLocalProjectSkills } from '../../project-skills/local-store.js';
8
8
  import { resolveCliPackageRoot } from '../../scaffold/compose.js';
9
9
  import { selectPlatformSkillSnapshot } from '../../project-skills/builtin-import.js';
10
10
  import { EMPTY_WORKSPACE_REPOSITORY_SKILLS, EMPTY_WORKSPACE_PLATFORM_SKILLS } from './empty-template.js';
11
11
  import { excludeFromGit } from './dependencies.js';
12
12
  import { selectOptionalPlatformSkills } from './optional-platform-skills.js';
13
- import { ensureSharedDirectory, writeSharedFile } from './shared-write.js';
13
+ import { ensureSharedDirectory } from './shared-write.js';
14
14
  import { ensureWorkspaceRemixReferenceSkill } from './remix-reference-skill.js';
15
- /** Runtime availability is determined by the locked metadata, independent of unsynced source edits. */
16
- export async function readWorkspaceSkillSnapshot(root) {
17
- const management = await lstat(join(root, '.playcraft'));
18
- const declaration = join(root, '.playcraft/skills.json');
19
- const stat = await lstat(declaration);
20
- if (!management.isDirectory() || management.isSymbolicLink() || !stat.isFile() || stat.isSymbolicLink() || stat.size > 1024 * 1024)
21
- throw new Error('workspace_skills_invalid');
22
- return validateProjectSkillsSnapshot(await readFile(declaration, 'utf8'), await readProjectSkillsLockFile(root));
23
- }
24
15
  /** Maintains the bundled reference guide while preserving project selection and custom revisions. */
25
16
  export async function ensureWorkspaceSkills(root, cache, access, options = {}) {
26
17
  await excludeFromGit(root, '/.playcraft/skills/');
@@ -61,177 +52,23 @@ export async function ensureWorkspaceSkills(root, cache, access, options = {}) {
61
52
  },
62
53
  });
63
54
  }
64
- async function safeDirectory(root, path) {
65
- let current = root;
66
- for (const segment of path.split('/')) {
67
- current = join(current, segment);
68
- // Validate every existing segment before creating anything, so a link never redirects the tree.
69
- const stat = await lstat(current).catch((error) => {
70
- if (error.code === 'ENOENT')
71
- return undefined;
72
- throw error;
73
- });
74
- if (stat && (!stat.isDirectory() || stat.isSymbolicLink()))
75
- throw new Error('workspace_skills_invalid');
76
- }
77
- await ensureSharedDirectory(root, current);
78
- return current;
79
- }
80
- /** Cache misses may reconstruct only the exact locked repository bytes, never a newer source. */
81
- async function lockedFiles(root, cache, entry, view, loadArtifact) {
82
- const persistent = await safeDirectory(root, '.playcraft/skill-artifacts');
83
- const saved = join(persistent, `${entry.revision.artifactHash}.tar.gz`);
84
- const path = join(cache, `${entry.revision.artifactHash}.tar.gz`);
85
- const contentHash = view.lock.lockVersion === 4 ? entry.revision.contentHash : undefined;
86
- const contentSaved = contentHash ? join(persistent, `content-${contentHash}.tar.gz`) : undefined;
87
- const contentPath = contentHash ? join(cache, `content-${contentHash}.tar.gz`) : undefined;
88
- let archive;
89
- let contentAddressed = false;
90
- try {
91
- const stat = await lstat(saved);
92
- if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > 80 * 1024 * 1024)
93
- throw new Error('workspace_skills_invalid_bundle');
94
- archive = await readFile(saved);
95
- }
96
- catch (error) {
97
- if (error.code !== 'ENOENT')
98
- throw error;
99
- try {
100
- if (!contentSaved)
101
- throw error;
102
- const stat = await lstat(contentSaved);
103
- if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > 80 * 1024 * 1024)
104
- throw new Error('workspace_skills_invalid_bundle');
105
- archive = await readFile(contentSaved);
106
- contentAddressed = true;
107
- }
108
- catch (cacheError) {
109
- if (cacheError.code !== 'ENOENT')
110
- throw cacheError;
111
- try {
112
- archive = await readFile(path);
113
- }
114
- catch (error) {
115
- if (error.code !== 'ENOENT')
116
- throw error;
117
- try {
118
- if (!contentPath)
119
- throw error;
120
- archive = await readFile(contentPath);
121
- contentAddressed = true;
122
- }
123
- catch (contentError) {
124
- if (contentError.code !== 'ENOENT')
125
- throw contentError;
126
- const declaration = view.declaration.entries.find(item => item.entryKey === entry.entryKey);
127
- if (entry.revision.kind === 'source' && declaration.source.kind === 'repository') {
128
- const source = await readProjectSkillSource(root, declaration);
129
- if (source.sourceTreeHash !== entry.sourceTreeHash)
130
- throw new Error('workspace_skills_source_changed');
131
- if (contentHash) {
132
- if (source.sourceTreeHash !== contentHash)
133
- throw new Error('workspace_skills_digest_mismatch');
134
- return source.files.map(file => ({ path: file.path, data: Buffer.from(file.content) }));
135
- }
136
- archive = createCanonicalBundleArchive(source.files.map(file => ({ path: file.path, data: Buffer.from(file.content) })));
137
- }
138
- else {
139
- if (!loadArtifact)
140
- throw new Error('workspace_skills_artifact_unavailable');
141
- archive = await loadArtifact(entry);
142
- contentAddressed = Boolean(contentHash);
143
- }
144
- }
145
- }
146
- }
147
- }
148
- if (!contentAddressed && createHash('sha256').update(archive).digest('hex') !== entry.revision.artifactHash)
149
- throw new Error('workspace_skills_digest_mismatch');
150
- const extracted = extractBundleArchive(archive);
151
- if (extracted.issues.length)
152
- throw new Error('workspace_skills_invalid_bundle');
153
- if (contentHash && sourceTreeDigest(extracted.files.map(file => ({ path: file.path, content: file.data }))) !== contentHash)
154
- throw new Error('workspace_skills_digest_mismatch');
155
- if (entry.revision.kind === 'source' && sourceTreeDigest(extracted.files.map(file => ({ path: file.path, content: file.data }))) !== entry.sourceTreeHash)
156
- throw new Error('workspace_skills_digest_mismatch');
157
- await writeSharedFile(root, contentAddressed ? contentSaved : saved, archive);
158
- return extracted.files;
159
- }
160
- function metadata(entryKey, content) {
161
- const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content);
162
- const parsed = frontmatter ? load(frontmatter[1]) : {};
163
- const data = parsed && typeof parsed === 'object' ? parsed : {};
164
- return { entryKey, name: typeof data.name === 'string' ? data.name : entryKey,
165
- description: typeof data.description === 'string' ? data.description : '', content };
166
- }
167
55
  export async function readWorkspaceSkills(root, cache, loadArtifact) {
168
56
  const snapshot = await readWorkspaceSkillSnapshot(root);
169
- const skills = await Promise.all(snapshot.lock.entries.map(async (entry) => {
57
+ const reads = await Promise.allSettled(snapshot.lock.entries.map(async (entry) => {
170
58
  const files = await lockedFiles(root, cache, entry, snapshot, loadArtifact);
171
59
  const body = files.find(file => file.path === 'SKILL.md');
172
60
  if (!body)
173
61
  throw new Error('workspace_skills_body_missing');
174
62
  return metadata(entry.entryKey, body.data.toString('utf8'));
175
63
  }));
64
+ const skills = reads.map(result => {
65
+ if (result.status === 'rejected')
66
+ throw result.reason;
67
+ return result.value;
68
+ });
176
69
  return { ...snapshot, lockHash: createHash('sha256').update(canonicalProjectSkillsJson(snapshot.lock)).digest('hex'), skills };
177
70
  }
178
71
  /** Read-only task generation lives outside source discovery and is omitted from Git/snapshots. */
179
72
  export async function freezeWorkspaceSkills(root, cache, loadArtifact) {
180
- const snapshot = await readWorkspaceSkillSnapshot(root);
181
- const entries = effectiveProjectSkills(snapshot.declaration, snapshot.lock, 'sandbox-agent');
182
- const base = await safeDirectory(root, '.playcraft/skills');
183
- const lockHash = createHash('sha256').update(canonicalProjectSkillsJson(snapshot.lock)).digest('hex');
184
- const generation = await safeDirectory(base, lockHash);
185
- const skills = [];
186
- const expectedFiles = new Set();
187
- for (const entry of entries) {
188
- const files = await lockedFiles(root, cache, entry, snapshot, loadArtifact);
189
- for (const file of files) {
190
- const target = join(generation, entry.entryKey, file.path);
191
- const relative = join(entry.entryKey, file.path);
192
- expectedFiles.add(relative);
193
- await safeDirectory(generation, dirname(relative));
194
- // shared
195
- await writeFile(target, file.data, { flag: 'wx', mode: 0o444 }).catch(error => {
196
- if (error.code !== 'EEXIST')
197
- throw error;
198
- });
199
- const handle = await open(target, constants.O_RDONLY | constants.O_NOFOLLOW);
200
- try {
201
- const stat = await handle.stat();
202
- if (!stat.isFile() || stat.nlink !== 1 || !file.data.equals(await handle.readFile()))
203
- throw new Error('workspace_skills_digest_mismatch');
204
- // A private controller umask must not remove the developer's read access.
205
- // Repair only the verified inode, keeping every frozen byte read-only.
206
- await handle.chmod(0o444);
207
- }
208
- finally {
209
- await handle.close();
210
- }
211
- }
212
- const body = files.find(file => file.path === 'SKILL.md');
213
- if (!body)
214
- throw new Error('workspace_skills_body_missing');
215
- const info = metadata(entry.entryKey, body.data.toString('utf8'));
216
- skills.push({ name: info.name, description: info.description, skillRef: entry.revision.skillRef,
217
- revision: entry.revision.kind === 'source' ? entry.revision.sourceRevision : entry.revision.version,
218
- path: join(generation, entry.entryKey, 'SKILL.md') });
219
- }
220
- const verifyTree = async (directory, relative = '') => {
221
- for (const item of await readdir(directory, { withFileTypes: true })) {
222
- const path = join(relative, item.name);
223
- if (item.isSymbolicLink())
224
- throw new Error('workspace_skills_invalid_bundle');
225
- if (item.isDirectory()) {
226
- if (![...expectedFiles].some(file => file.startsWith(`${path}/`)))
227
- throw new Error('workspace_skills_invalid_bundle');
228
- await verifyTree(join(directory, item.name), path);
229
- }
230
- else if (!item.isFile() || !expectedFiles.has(path))
231
- throw new Error('workspace_skills_invalid_bundle');
232
- }
233
- };
234
- await verifyTree(generation);
235
- return { declarationHash: snapshot.lock.declarationHash,
236
- lockHash, skills };
73
+ return freezeLockedProjectSkills(root, cache, loadArtifact);
237
74
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcraft/cli",
3
- "version": "0.1.69",
3
+ "version": "0.1.71",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -56,8 +56,8 @@
56
56
  "@gltf-transform/core": "^4.3.0",
57
57
  "@gltf-transform/extensions": "^4.3.0",
58
58
  "@gltf-transform/functions": "^4.3.0",
59
- "@playcraft/build": "0.1.69",
60
- "@playcraft/common": "0.1.69",
59
+ "@playcraft/build": "0.1.71",
60
+ "@playcraft/common": "0.1.71",
61
61
  "better-sqlite3-pinned": "npm:better-sqlite3@13.0.3",
62
62
  "chokidar": "^4.0.3",
63
63
  "commander": "^13.1.0",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: playcraft-skill-recommender
3
- description: 构建项目前的 Skill 推荐与脚手架工具。在开始任何新游戏项目前必须先调用,根据引擎(threejs/phaser)和意图关键词,自动推荐完整的代码 skill 集合(含引擎层/布局层/输入层/渲染层/动画层/玩法层)和媒体资产(背景图/BGM/SFX/UI 面板),并生成可直接执行的 scaffold 命令。
3
+ description: 开发前从远端技能库发现候选,按意图、引擎、触发词和能力匹配;选定精确版本后下载技能及依赖。普通技能与 Atom 均可发现,不依赖本地安装目录。已锁定技能仍由当前执行锁约束。
4
4
  triggers: 构建新游戏项目, 选择 skill, 查找可用组件, 3D项目搭建, Three.js项目, Phaser项目
5
5
  compatibility: agent,opencode
6
6
  ---
@@ -13,6 +13,28 @@ compatibility: agent,opencode
13
13
 
14
14
  ---
15
15
 
16
+ ## 默认流程:远端发现 → 精确选择 → 按需下载
17
+
18
+ `skills match` 默认查询当前身份有权访问的远端技能元数据,不依赖本地技能目录,也不会下载所有正文:
19
+
20
+ ```bash
21
+ playcraft skills match --intent "military,merge,base-building,strategy,island,battle" --engine threejs --json > /tmp/skills-match.json
22
+ ```
23
+
24
+ 候选包含 `skillRef`、`skillVersionId`、`version`、`artifactHash`、名称、描述、引擎、能力与匹配词。依据任务选择候选,再提交原精确引用,不能重新取最新版:
25
+
26
+ ```bash
27
+ playcraft skills prepare --from-match /tmp/skills-match.json --skills "<匹配结果中的namespace/slug>" --json
28
+ ```
29
+
30
+ - 选择阶段复验权限、引擎和硬依赖,生成独立选择锁;准备阶段只下载该锁的精确制品,校验后存入 `.playcraft/skill-selections/<selectionId>/`,相同选择可复用已校验缓存。
31
+ - 在云端工程会话中,`prepare` 下载校验成功后默认返回 `activation_requested` / `executionReady: false`。请正常结束本回合,不要执行准备副本或重复申请同一接续;平台等待当前回合及其他原生执行结束后,在同一会话的新回合激活精确技能、读取新锁正文并继续原任务,保留已有修改。当前回合的锁始终不变。
32
+ - `already_active` 表示这些精确技能已在当前锁中启用且满足本回合的执行要求,不会重复启动新回合;使用当前锁中的冻结路径。需要独占执行的技能若尚未获得本回合授权,仍会安排独占后续回合。需要只下载时使用 `--prepare-only`。本地无云端执行代理时仍返回 `prepared_requires_activation`,不会自行启动对话。
33
+ - 激活保留原有技能及版本,遇到同引用不同修订、用户中途改锁、工程改绑定或权限撤回会明确失败,不会换最新版重试。来源回合取消、失败、结果未知或已有新的用户回合时,不自动重放原任务。
34
+ - 当前可执行技能可用 `playcraft skills search --query "<关键词>" --json` 或 `playcraft skills match --installed --intent "<关键词>" --json` 查询,只返回当前锁内已启用的技能。普通技能无需 Atom manifest,使用前读取返回的冻结 `path`。
35
+ - 远端失败须报告真实失败,不能退回目录扫描,也不能把失败当作没有匹配。沙箱自动复用当前执行身份;本地使用已登录的平台环境。不要索取或输出控制凭据。
36
+ - 只有确实在本地编写 Atom 库时,才显式使用 `match --local` 或 `--skills-dir`。下文旧目录工具仅适用于这一场景,不能用于绕过运行时技能锁。
37
+
16
38
  ## 按场景选择命令
17
39
 
18
40
  | 你的场景 | 推荐命令 | 说明 |
@@ -33,10 +55,10 @@ compatibility: agent,opencode
33
55
 
34
56
  ```bash
35
57
  # 自动推断引擎
36
- playcraft skills match --intent "<关键词,逗号分隔>" --json
58
+ playcraft skills match --local --intent "<关键词,逗号分隔>" --json
37
59
 
38
60
  # 指定引擎
39
- playcraft skills match --engine <threejs|phaser> --intent "<关键词>" --json
61
+ playcraft skills match --local --engine <threejs|phaser> --intent "<关键词>" --json
40
62
  ```
41
63
 
42
64
  **适用时机**:你已经知道要做什么类型的游戏,需要一次性获取完整的技术栈推荐。
@@ -51,13 +73,13 @@ playcraft skills match --engine <threejs|phaser> --intent "<关键词>" --json
51
73
 
52
74
  ```bash
53
75
  # 三消游戏
54
- playcraft skills match --intent "match3,grid,tile,eliminate" --json
76
+ playcraft skills match --local --intent "match3,grid,tile,eliminate" --json
55
77
 
56
78
  # 麻将收集消除
57
- playcraft skills match --intent "mahjong,tile,match,stacking,puzzle" --json
79
+ playcraft skills match --local --intent "mahjong,tile,match,stacking,puzzle" --json
58
80
 
59
81
  # Three.js 3D 棋盘
60
- playcraft skills match --engine threejs --intent "board,3d,path,animation" --json
82
+ playcraft skills match --local --engine threejs --intent "board,3d,path,animation" --json
61
83
  ```
62
84
 
63
85
  ---
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: playcraft-skill-recommender
3
- description: 构建项目前的 Skill 推荐与脚手架工具。在开始任何新游戏项目前必须先调用,根据引擎(threejs/phaser)和意图关键词,自动推荐完整的代码 skill 集合(含引擎层/布局层/输入层/渲染层/动画层/玩法层)和媒体资产(背景图/BGM/SFX/UI 面板),并生成可直接执行的 scaffold 命令。
3
+ description: 开发前从远端技能库发现候选,按意图、引擎、触发词和能力匹配;选定精确版本后下载技能及依赖。普通技能与 Atom 均可发现,不依赖本地安装目录。已锁定技能仍由当前执行锁约束。
4
4
  triggers: 构建新游戏项目, 选择 skill, 查找可用组件, 3D项目搭建, Three.js项目, Phaser项目
5
5
  compatibility: agent,opencode
6
6
  ---
@@ -13,6 +13,28 @@ compatibility: agent,opencode
13
13
 
14
14
  ---
15
15
 
16
+ ## 默认流程:远端发现 → 精确选择 → 按需下载
17
+
18
+ `skills match` 默认查询当前身份有权访问的远端技能元数据,不依赖本地技能目录,也不会下载所有正文:
19
+
20
+ ```bash
21
+ playcraft skills match --intent "military,merge,base-building,strategy,island,battle" --engine threejs --json > /tmp/skills-match.json
22
+ ```
23
+
24
+ 候选包含 `skillRef`、`skillVersionId`、`version`、`artifactHash`、名称、描述、引擎、能力与匹配词。依据任务选择候选,再提交原精确引用,不能重新取最新版:
25
+
26
+ ```bash
27
+ playcraft skills prepare --from-match /tmp/skills-match.json --skills "<匹配结果中的namespace/slug>" --json
28
+ ```
29
+
30
+ - 选择阶段复验权限、引擎和硬依赖,生成独立选择锁;准备阶段只下载该锁的精确制品,校验后存入 `.playcraft/skill-selections/<selectionId>/`,相同选择可复用已校验缓存。
31
+ - 在云端工程会话中,`prepare` 下载校验成功后默认返回 `activation_requested` / `executionReady: false`。请正常结束本回合,不要执行准备副本或重复申请同一接续;平台等待当前回合及其他原生执行结束后,在同一会话的新回合激活精确技能、读取新锁正文并继续原任务,保留已有修改。当前回合的锁始终不变。
32
+ - `already_active` 表示这些精确技能已在当前锁中启用且满足本回合的执行要求,不会重复启动新回合;使用当前锁中的冻结路径。需要独占执行的技能若尚未获得本回合授权,仍会安排独占后续回合。需要只下载时使用 `--prepare-only`。本地无云端执行代理时仍返回 `prepared_requires_activation`,不会自行启动对话。
33
+ - 激活保留原有技能及版本,遇到同引用不同修订、用户中途改锁、工程改绑定或权限撤回会明确失败,不会换最新版重试。来源回合取消、失败、结果未知或已有新的用户回合时,不自动重放原任务。
34
+ - 当前可执行技能可用 `playcraft skills search --query "<关键词>" --json` 或 `playcraft skills match --installed --intent "<关键词>" --json` 查询,只返回当前锁内已启用的技能。普通技能无需 Atom manifest,使用前读取返回的冻结 `path`。
35
+ - 远端失败须报告真实失败,不能退回目录扫描,也不能把失败当作没有匹配。沙箱自动复用当前执行身份;本地使用已登录的平台环境。不要索取或输出控制凭据。
36
+ - 只有确实在本地编写 Atom 库时,才显式使用 `match --local` 或 `--skills-dir`。下文旧目录工具仅适用于这一场景,不能用于绕过运行时技能锁。
37
+
16
38
  ## 按场景选择命令
17
39
 
18
40
  | 你的场景 | 推荐命令 | 说明 |
@@ -35,10 +57,10 @@ compatibility: agent,opencode
35
57
 
36
58
  ```bash
37
59
  # 自动推断引擎
38
- playcraft skills match --intent "<关键词,逗号分隔>" --json
60
+ playcraft skills match --local --intent "<关键词,逗号分隔>" --json
39
61
 
40
62
  # 指定引擎
41
- playcraft skills match --engine <threejs|phaser> --intent "<关键词>" --json
63
+ playcraft skills match --local --engine <threejs|phaser> --intent "<关键词>" --json
42
64
  ```
43
65
 
44
66
  **适用时机**:你已经知道要做什么类型的游戏,需要一次性获取完整的技术栈推荐。
@@ -53,13 +75,13 @@ playcraft skills match --engine <threejs|phaser> --intent "<关键词>" --json
53
75
 
54
76
  ```bash
55
77
  # 三消游戏
56
- playcraft skills match --intent "match3,grid,tile,eliminate" --json
78
+ playcraft skills match --local --intent "match3,grid,tile,eliminate" --json
57
79
 
58
80
  # 麻将收集消除
59
- playcraft skills match --intent "mahjong,tile,match,stacking,puzzle" --json
81
+ playcraft skills match --local --intent "mahjong,tile,match,stacking,puzzle" --json
60
82
 
61
83
  # Three.js 3D 棋盘
62
- playcraft skills match --engine threejs --intent "board,3d,path,animation" --json
84
+ playcraft skills match --local --engine threejs --intent "board,3d,path,animation" --json
63
85
  ```
64
86
 
65
87
  ---
@@ -31,3 +31,4 @@ playcraft.config.json
31
31
  Thumbs.db
32
32
  .idea/
33
33
  .vscode/
34
+ .playcraft/skill-selections/
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: playcraft-skill-recommender
3
- description: 构建项目前的 Skill 推荐与脚手架工具。在开始任何新游戏项目前必须先调用,根据引擎(threejs/phaser)和意图关键词,自动推荐完整的代码 skill 集合(含引擎层/布局层/输入层/渲染层/动画层/玩法层)和媒体资产(背景图/BGM/SFX/UI 面板),并生成可直接执行的 scaffold 命令。
3
+ description: 开发前从远端技能库发现候选,按意图、引擎、触发词和能力匹配;选定精确版本后下载技能及依赖。普通技能与 Atom 均可发现,不依赖本地安装目录。已锁定技能仍由当前执行锁约束。
4
4
  triggers: 构建新游戏项目, 选择 skill, 查找可用组件, 3D项目搭建, Three.js项目, Phaser项目
5
5
  compatibility: agent,opencode
6
6
  ---
@@ -13,6 +13,28 @@ compatibility: agent,opencode
13
13
 
14
14
  ---
15
15
 
16
+ ## 默认流程:远端发现 → 精确选择 → 按需下载
17
+
18
+ `skills match` 默认查询当前身份有权访问的远端技能元数据,不依赖本地技能目录,也不会下载所有正文:
19
+
20
+ ```bash
21
+ playcraft skills match --intent "military,merge,base-building,strategy,island,battle" --engine threejs --json > /tmp/skills-match.json
22
+ ```
23
+
24
+ 候选包含 `skillRef`、`skillVersionId`、`version`、`artifactHash`、名称、描述、引擎、能力与匹配词。依据任务选择候选,再提交原精确引用,不能重新取最新版:
25
+
26
+ ```bash
27
+ playcraft skills prepare --from-match /tmp/skills-match.json --skills "<匹配结果中的namespace/slug>" --json
28
+ ```
29
+
30
+ - 选择阶段复验权限、引擎和硬依赖,生成独立选择锁;准备阶段只下载该锁的精确制品,校验后存入 `.playcraft/skill-selections/<selectionId>/`,相同选择可复用已校验缓存。
31
+ - 在云端工程会话中,`prepare` 下载校验成功后默认返回 `activation_requested` / `executionReady: false`。请正常结束本回合,不要执行准备副本或重复申请同一接续;平台等待当前回合及其他原生执行结束后,在同一会话的新回合激活精确技能、读取新锁正文并继续原任务,保留已有修改。当前回合的锁始终不变。
32
+ - `already_active` 表示这些精确技能已在当前锁中启用且满足本回合的执行要求,不会重复启动新回合;使用当前锁中的冻结路径。需要独占执行的技能若尚未获得本回合授权,仍会安排独占后续回合。需要只下载时使用 `--prepare-only`。本地无云端执行代理时仍返回 `prepared_requires_activation`,不会自行启动对话。
33
+ - 激活保留原有技能及版本,遇到同引用不同修订、用户中途改锁、工程改绑定或权限撤回会明确失败,不会换最新版重试。来源回合取消、失败、结果未知或已有新的用户回合时,不自动重放原任务。
34
+ - 当前可执行技能可用 `playcraft skills search --query "<关键词>" --json` 或 `playcraft skills match --installed --intent "<关键词>" --json` 查询,只返回当前锁内已启用的技能。普通技能无需 Atom manifest,使用前读取返回的冻结 `path`。
35
+ - 远端失败须报告真实失败,不能退回目录扫描,也不能把失败当作没有匹配。沙箱自动复用当前执行身份;本地使用已登录的平台环境。不要索取或输出控制凭据。
36
+ - 只有确实在本地编写 Atom 库时,才显式使用 `match --local` 或 `--skills-dir`。下文旧目录工具仅适用于这一场景,不能用于绕过运行时技能锁。
37
+
16
38
  ## 按场景选择命令
17
39
 
18
40
  | 你的场景 | 推荐命令 | 说明 |
@@ -33,10 +55,10 @@ compatibility: agent,opencode
33
55
 
34
56
  ```bash
35
57
  # 自动推断引擎
36
- playcraft skills match --intent "<关键词,逗号分隔>" --json
58
+ playcraft skills match --local --intent "<关键词,逗号分隔>" --json
37
59
 
38
60
  # 指定引擎
39
- playcraft skills match --engine <threejs|phaser> --intent "<关键词>" --json
61
+ playcraft skills match --local --engine <threejs|phaser> --intent "<关键词>" --json
40
62
  ```
41
63
 
42
64
  **适用时机**:你已经知道要做什么类型的游戏,需要一次性获取完整的技术栈推荐。
@@ -51,13 +73,13 @@ playcraft skills match --engine <threejs|phaser> --intent "<关键词>" --json
51
73
 
52
74
  ```bash
53
75
  # 三消游戏
54
- playcraft skills match --intent "match3,grid,tile,eliminate" --json
76
+ playcraft skills match --local --intent "match3,grid,tile,eliminate" --json
55
77
 
56
78
  # 麻将收集消除
57
- playcraft skills match --intent "mahjong,tile,match,stacking,puzzle" --json
79
+ playcraft skills match --local --intent "mahjong,tile,match,stacking,puzzle" --json
58
80
 
59
81
  # Three.js 3D 棋盘
60
- playcraft skills match --engine threejs --intent "board,3d,path,animation" --json
82
+ playcraft skills match --local --engine threejs --intent "board,3d,path,animation" --json
61
83
  ```
62
84
 
63
85
  ---
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: playcraft-skill-recommender
3
- description: 构建项目前的 Skill 推荐与脚手架工具。在开始任何新游戏项目前必须先调用,根据引擎(threejs/phaser)和意图关键词,自动推荐完整的代码 skill 集合(含引擎层/布局层/输入层/渲染层/动画层/玩法层)和媒体资产(背景图/BGM/SFX/UI 面板),并生成可直接执行的 scaffold 命令。
3
+ description: 开发前从远端技能库发现候选,按意图、引擎、触发词和能力匹配;选定精确版本后下载技能及依赖。普通技能与 Atom 均可发现,不依赖本地安装目录。已锁定技能仍由当前执行锁约束。
4
4
  triggers: 构建新游戏项目, 选择 skill, 查找可用组件, 3D项目搭建, Three.js项目, Phaser项目
5
5
  compatibility: agent,opencode
6
6
  ---
@@ -13,6 +13,28 @@ compatibility: agent,opencode
13
13
 
14
14
  ---
15
15
 
16
+ ## 默认流程:远端发现 → 精确选择 → 按需下载
17
+
18
+ `skills match` 默认查询当前身份有权访问的远端技能元数据,不依赖本地技能目录,也不会下载所有正文:
19
+
20
+ ```bash
21
+ playcraft skills match --intent "military,merge,base-building,strategy,island,battle" --engine threejs --json > /tmp/skills-match.json
22
+ ```
23
+
24
+ 候选包含 `skillRef`、`skillVersionId`、`version`、`artifactHash`、名称、描述、引擎、能力与匹配词。依据任务选择候选,再提交原精确引用,不能重新取最新版:
25
+
26
+ ```bash
27
+ playcraft skills prepare --from-match /tmp/skills-match.json --skills "<匹配结果中的namespace/slug>" --json
28
+ ```
29
+
30
+ - 选择阶段复验权限、引擎和硬依赖,生成独立选择锁;准备阶段只下载该锁的精确制品,校验后存入 `.playcraft/skill-selections/<selectionId>/`,相同选择可复用已校验缓存。
31
+ - 在云端工程会话中,`prepare` 下载校验成功后默认返回 `activation_requested` / `executionReady: false`。请正常结束本回合,不要执行准备副本或重复申请同一接续;平台等待当前回合及其他原生执行结束后,在同一会话的新回合激活精确技能、读取新锁正文并继续原任务,保留已有修改。当前回合的锁始终不变。
32
+ - `already_active` 表示这些精确技能已在当前锁中启用且满足本回合的执行要求,不会重复启动新回合;使用当前锁中的冻结路径。需要独占执行的技能若尚未获得本回合授权,仍会安排独占后续回合。需要只下载时使用 `--prepare-only`。本地无云端执行代理时仍返回 `prepared_requires_activation`,不会自行启动对话。
33
+ - 激活保留原有技能及版本,遇到同引用不同修订、用户中途改锁、工程改绑定或权限撤回会明确失败,不会换最新版重试。来源回合取消、失败、结果未知或已有新的用户回合时,不自动重放原任务。
34
+ - 当前可执行技能可用 `playcraft skills search --query "<关键词>" --json` 或 `playcraft skills match --installed --intent "<关键词>" --json` 查询,只返回当前锁内已启用的技能。普通技能无需 Atom manifest,使用前读取返回的冻结 `path`。
35
+ - 远端失败须报告真实失败,不能退回目录扫描,也不能把失败当作没有匹配。沙箱自动复用当前执行身份;本地使用已登录的平台环境。不要索取或输出控制凭据。
36
+ - 只有确实在本地编写 Atom 库时,才显式使用 `match --local` 或 `--skills-dir`。下文旧目录工具仅适用于这一场景,不能用于绕过运行时技能锁。
37
+
16
38
  ## 按场景选择命令
17
39
 
18
40
  | 你的场景 | 推荐命令 | 说明 |
@@ -33,10 +55,10 @@ compatibility: agent,opencode
33
55
 
34
56
  ```bash
35
57
  # 自动推断引擎
36
- playcraft skills match --intent "<关键词,逗号分隔>" --json
58
+ playcraft skills match --local --intent "<关键词,逗号分隔>" --json
37
59
 
38
60
  # 指定引擎
39
- playcraft skills match --engine <threejs|phaser> --intent "<关键词>" --json
61
+ playcraft skills match --local --engine <threejs|phaser> --intent "<关键词>" --json
40
62
  ```
41
63
 
42
64
  **适用时机**:你已经知道要做什么类型的游戏,需要一次性获取完整的技术栈推荐。
@@ -51,13 +73,13 @@ playcraft skills match --engine <threejs|phaser> --intent "<关键词>" --json
51
73
 
52
74
  ```bash
53
75
  # 三消游戏
54
- playcraft skills match --intent "match3,grid,tile,eliminate" --json
76
+ playcraft skills match --local --intent "match3,grid,tile,eliminate" --json
55
77
 
56
78
  # 麻将收集消除
57
- playcraft skills match --intent "mahjong,tile,match,stacking,puzzle" --json
79
+ playcraft skills match --local --intent "mahjong,tile,match,stacking,puzzle" --json
58
80
 
59
81
  # Three.js 3D 棋盘
60
- playcraft skills match --engine threejs --intent "board,3d,path,animation" --json
82
+ playcraft skills match --local --engine threejs --intent "board,3d,path,animation" --json
61
83
  ```
62
84
 
63
85
  ---