@playcraft/cli 0.0.42 → 0.0.44

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 (51) hide show
  1. package/dist/atom-plan/validate-asr-coverage.js +317 -0
  2. package/dist/commands/build.js +6 -6
  3. package/dist/commands/remix.js +4 -2
  4. package/dist/commands/skills.js +24 -0
  5. package/dist/index.js +0 -0
  6. package/package.json +3 -3
  7. package/project-template/.claude/agents/designer.md +26 -22
  8. package/project-template/.claude/agents/developer.md +2 -0
  9. package/project-template/.claude/agents/pm.md +3 -1
  10. package/project-template/.claude/agents/refs/designer-deliverable-spec.md +46 -7
  11. package/project-template/.claude/agents/refs/designer-handoff-v2-checklist.md +21 -13
  12. package/project-template/.claude/agents/refs/designer-style-exploration-flow.md +39 -9
  13. package/project-template/.claude/agents/refs/developer-dev-handoff.md +1 -1
  14. package/project-template/.claude/agents/refs/pm-workflow-detail.md +18 -2
  15. package/project-template/.claude/agents/refs/reviewer-convergence-eval.md +17 -5
  16. package/project-template/.claude/agents/refs/ta-pipeline-cookbook.md +42 -6
  17. package/project-template/.claude/agents/reviewer.md +8 -5
  18. package/project-template/.claude/agents/technical-artist.md +2 -0
  19. package/project-template/.claude/hooks/README.md +34 -6
  20. package/project-template/.claude/hooks/asr-coverage-validate.mjs +381 -0
  21. package/project-template/.claude/hooks/validate-workflow-stop.mjs +113 -7
  22. package/project-template/.claude/skills/playcraft-asset-state-sheet/SKILL.md +76 -22
  23. package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +19 -0
  24. package/project-template/docs/team/agent-runtime-matrix.md +71 -39
  25. package/project-template/docs/team/atom-plan-format.md +68 -0
  26. package/project-template/docs/team/core-model.md +20 -19
  27. package/project-template/docs/team/workflow-consistency-checklist.md +52 -0
  28. package/project-template/templates/atom-plan.template.json +18 -0
  29. package/project-template/templates/designer-log.template.md +78 -5
  30. package/project-template/templates/layout-spec.template.md +48 -8
  31. package/project-template/templates/ta-log.template.md +50 -22
  32. package/dist/playable/base-builder.js +0 -265
  33. package/dist/playable/builder.js +0 -1462
  34. package/dist/playable/converter.js +0 -150
  35. package/dist/playable/index.js +0 -3
  36. package/dist/playable/platforms/base.js +0 -12
  37. package/dist/playable/platforms/facebook.js +0 -37
  38. package/dist/playable/platforms/index.js +0 -24
  39. package/dist/playable/platforms/snapchat.js +0 -59
  40. package/dist/playable/playable-builder.js +0 -521
  41. package/dist/playable/types.js +0 -1
  42. package/dist/playable/vite/config-builder.js +0 -136
  43. package/dist/playable/vite/platform-configs.js +0 -102
  44. package/dist/playable/vite/plugin-model-compression.js +0 -63
  45. package/dist/playable/vite/plugin-platform.js +0 -65
  46. package/dist/playable/vite/plugin-playcanvas.js +0 -454
  47. package/dist/playable/vite-builder.js +0 -125
  48. package/project-template/.claude/settings.local.json +0 -4
  49. package/project-template/logs/.gitkeep +0 -0
  50. package/project-template/ta-workspace/logs/.gitkeep +0 -0
  51. package/project-template/ta-workspace/tmp/.gitkeep +0 -0
@@ -0,0 +1,317 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ const PLACEHOLDER_RE = /\{\{[^}]+\}\}/;
4
+ const MIN_TAKEAWAY_LEN = 8;
5
+ export const IMAGE_PRODUCTION_PREFLIGHT_HEADING = '## Image Production Preflight';
6
+ export const ASR_COVERAGE_HEADING = '## ASR Coverage Matrix';
7
+ export const REQUIRED_IMAGE_SKILLS = [
8
+ 'playcraft-storyboard',
9
+ 'playcraft-asset-state-sheet',
10
+ 'playcraft-image-generation',
11
+ ];
12
+ export function readSelectedMcOption(content) {
13
+ const m = content.match(/selectedMcOption:\s*["']?([ABC])["']?/i) ||
14
+ content.match(/selectedMcOption\s*=\s*([ABC])/i);
15
+ return m ? m[1].toUpperCase() : null;
16
+ }
17
+ function extractSection(content, heading) {
18
+ const start = content.indexOf(heading);
19
+ if (start === -1)
20
+ return '';
21
+ const after = content.slice(start + heading.length);
22
+ const next = after.search(/\n##\s+/);
23
+ return next === -1 ? after : after.slice(0, next);
24
+ }
25
+ export function parseLayoutSpecContractIds(layoutSpec) {
26
+ const elementIds = [];
27
+ const staticTextIds = [];
28
+ const assetSection = extractSection(layoutSpec, '## Asset Mapping');
29
+ for (const line of assetSection.split('\n')) {
30
+ const trimmed = line.trim();
31
+ if (!trimmed.startsWith('|'))
32
+ continue;
33
+ if (/^\|\s*---/.test(trimmed))
34
+ continue;
35
+ if (/^\|\s*elementId/i.test(trimmed))
36
+ continue;
37
+ const parts = trimmed
38
+ .split('|')
39
+ .map((p) => p.trim())
40
+ .filter((_, i, arr) => i > 0 && i < arr.length - 1);
41
+ if (parts.length < 1)
42
+ continue;
43
+ const id = parts[0].replace(/`/g, '');
44
+ if (!id || id === '—' || id === '-' || PLACEHOLDER_RE.test(id))
45
+ continue;
46
+ elementIds.push(id);
47
+ }
48
+ const textSection = extractSection(layoutSpec, '### Static Text');
49
+ for (const line of textSection.split('\n')) {
50
+ const trimmed = line.trim();
51
+ if (!trimmed.startsWith('|'))
52
+ continue;
53
+ if (/^\|\s*---/.test(trimmed))
54
+ continue;
55
+ if (/^\|\s*id/i.test(trimmed))
56
+ continue;
57
+ const parts = trimmed
58
+ .split('|')
59
+ .map((p) => p.trim())
60
+ .filter((_, i, arr) => i > 0 && i < arr.length - 1);
61
+ if (parts.length < 1)
62
+ continue;
63
+ const id = parts[0].replace(/`/g, '');
64
+ if (!id || id === '—' || PLACEHOLDER_RE.test(id))
65
+ continue;
66
+ staticTextIds.push(id);
67
+ }
68
+ return { elementIds, staticTextIds };
69
+ }
70
+ function isReadChecked(cell) {
71
+ const t = cell.trim();
72
+ if (/[✓✔☑]/.test(t))
73
+ return true;
74
+ if (/\[x\]/i.test(t))
75
+ return true;
76
+ if (/^(yes|done|true|read)$/i.test(t))
77
+ return true;
78
+ return false;
79
+ }
80
+ export function validateImageProductionPreflight(content) {
81
+ const errors = [];
82
+ const start = content.indexOf(IMAGE_PRODUCTION_PREFLIGHT_HEADING);
83
+ if (start === -1) {
84
+ return [`Missing "${IMAGE_PRODUCTION_PREFLIGHT_HEADING}" section`];
85
+ }
86
+ const after = content.slice(start + IMAGE_PRODUCTION_PREFLIGHT_HEADING.length);
87
+ const nextHeading = after.search(/\n##\s+/);
88
+ const section = nextHeading === -1 ? after : after.slice(0, nextHeading);
89
+ const rows = [];
90
+ for (const line of section.split('\n')) {
91
+ const trimmed = line.trim();
92
+ if (!trimmed.startsWith('|'))
93
+ continue;
94
+ if (/^\|\s*---/.test(trimmed))
95
+ continue;
96
+ if (/^\|\s*Skill/i.test(trimmed))
97
+ continue;
98
+ const parts = trimmed
99
+ .split('|')
100
+ .map((p) => p.trim())
101
+ .filter((_, i, arr) => i > 0 && i < arr.length - 1);
102
+ if (parts.length < 3)
103
+ continue;
104
+ rows.push({ skill: parts[0], read: parts[1], summary: parts[2] });
105
+ }
106
+ if (rows.length === 0) {
107
+ errors.push(`${IMAGE_PRODUCTION_PREFLIGHT_HEADING}: table has no data rows`);
108
+ return errors;
109
+ }
110
+ for (const required of REQUIRED_IMAGE_SKILLS) {
111
+ const row = rows.find((r) => r.skill.includes(required));
112
+ if (!row) {
113
+ errors.push(`${IMAGE_PRODUCTION_PREFLIGHT_HEADING}: missing row for ${required}`);
114
+ continue;
115
+ }
116
+ if (!isReadChecked(row.read)) {
117
+ errors.push(`${IMAGE_PRODUCTION_PREFLIGHT_HEADING}: "${required}" — mark 已读 (✓)`);
118
+ }
119
+ if (!row.summary || PLACEHOLDER_RE.test(row.summary) || row.summary.length < MIN_TAKEAWAY_LEN) {
120
+ errors.push(`${IMAGE_PRODUCTION_PREFLIGHT_HEADING}: "${required}" — add decision summary (≥${MIN_TAKEAWAY_LEN} chars)`);
121
+ }
122
+ }
123
+ return errors;
124
+ }
125
+ export function parseCoverageMatrixRows(content) {
126
+ const section = extractSection(content, ASR_COVERAGE_HEADING);
127
+ const coverageStart = section.indexOf('### Coverage');
128
+ const coverageBlock = coverageStart === -1 ? section : section.slice(coverageStart);
129
+ const rows = [];
130
+ for (const line of coverageBlock.split('\n')) {
131
+ const trimmed = line.trim();
132
+ if (!trimmed.startsWith('|'))
133
+ continue;
134
+ if (/^\|\s*---/.test(trimmed))
135
+ continue;
136
+ if (/^\|\s*Contract id/i.test(trimmed))
137
+ continue;
138
+ const parts = trimmed
139
+ .split('|')
140
+ .map((p) => p.trim())
141
+ .filter((_, i, arr) => i > 0 && i < arr.length - 1);
142
+ if (parts.length < 4)
143
+ continue;
144
+ const contractId = parts[0].replace(/`/g, '');
145
+ if (!contractId || PLACEHOLDER_RE.test(contractId))
146
+ continue;
147
+ const typeValues = new Set(['visual', 'audio', 'text', 'digit', 'sfx']);
148
+ let elementType = '';
149
+ let coverageLayer = '';
150
+ if (parts.length >= 4 && typeValues.has(parts[2]?.toLowerCase())) {
151
+ // id | elementType | Type | CoverageLayer | ...
152
+ elementType = parts[1];
153
+ coverageLayer = parts[3];
154
+ }
155
+ else if (parts.length >= 3 && typeValues.has(parts[1]?.toLowerCase())) {
156
+ // Legacy: id | Type | CoverageLayer | ...
157
+ coverageLayer = parts[2];
158
+ }
159
+ else {
160
+ elementType = parts.length >= 6 ? parts[1] : '';
161
+ coverageLayer = parts.length >= 4 ? parts[3] : parts[1] || '';
162
+ }
163
+ rows.push({
164
+ contractId,
165
+ elementType: elementType.replace(/`/g, ''),
166
+ coverageLayer: coverageLayer.toLowerCase(),
167
+ });
168
+ }
169
+ return rows;
170
+ }
171
+ export function validateSheetGridMetadata(content) {
172
+ const errors = [];
173
+ const section = extractSection(content, ASR_COVERAGE_HEADING);
174
+ if (!section.includes('### Sheet grid metadata')) {
175
+ errors.push('ASR Coverage Matrix: missing ### Sheet grid metadata table');
176
+ return errors;
177
+ }
178
+ const gridStart = section.indexOf('### Sheet grid metadata');
179
+ const gridBlock = section.slice(gridStart);
180
+ const gridEnd = gridBlock.search(/\n###\s+/);
181
+ const gridSection = gridEnd === -1 ? gridBlock : gridBlock.slice(0, gridEnd);
182
+ if (PLACEHOLDER_RE.test(gridSection)) {
183
+ errors.push('Sheet grid metadata: contains {{placeholders}} — fill real rows/cols/cell dimensions');
184
+ }
185
+ let dataRows = 0;
186
+ for (const line of gridSection.split('\n')) {
187
+ const trimmed = line.trim();
188
+ if (!trimmed.startsWith('|'))
189
+ continue;
190
+ if (/^\|\s*---/.test(trimmed))
191
+ continue;
192
+ if (/^\|\s*Sheet/i.test(trimmed))
193
+ continue;
194
+ dataRows++;
195
+ }
196
+ if (dataRows < 2) {
197
+ errors.push('Sheet grid metadata: need at least ui + element sheet rows');
198
+ }
199
+ return errors;
200
+ }
201
+ export function validateCoverageRowCount(designerLog, layoutSpec) {
202
+ const errors = [];
203
+ const { elementIds, staticTextIds } = parseLayoutSpecContractIds(layoutSpec);
204
+ const expected = elementIds.length + staticTextIds.length;
205
+ const matrixRows = parseCoverageMatrixRows(designerLog);
206
+ if (expected === 0) {
207
+ errors.push('layout-spec: could not parse assetMapping/static text ids — fill Asset Mapping table');
208
+ return errors;
209
+ }
210
+ if (matrixRows.length < expected) {
211
+ errors.push(`Coverage Matrix: ${matrixRows.length} rows but layout-spec expects ${expected} (assetMapping + static text)`);
212
+ }
213
+ const contractSet = new Set([...elementIds, ...staticTextIds]);
214
+ const matrixIds = new Set(matrixRows.map((r) => r.contractId));
215
+ for (const id of contractSet) {
216
+ if (!matrixIds.has(id)) {
217
+ errors.push(`Coverage Matrix: missing row for contract id "${id}"`);
218
+ }
219
+ }
220
+ return errors;
221
+ }
222
+ export function validateTypeRepresentatives(designerLog) {
223
+ const errors = [];
224
+ const rows = parseCoverageMatrixRows(designerLog);
225
+ const typeLayers = new Map();
226
+ for (const row of rows) {
227
+ const type = row.elementType && row.elementType !== '—' ? row.elementType : row.contractId.split('_')[0];
228
+ if (!type)
229
+ continue;
230
+ if (!typeLayers.has(type))
231
+ typeLayers.set(type, new Set());
232
+ typeLayers.get(type).add(row.coverageLayer);
233
+ }
234
+ for (const [type, layers] of typeLayers) {
235
+ const hasRep = [...layers].some((l) => l.includes('on-asr')) || [...layers].some((l) => l.includes('mc-crop'));
236
+ if (!hasRep && layers.size > 0) {
237
+ const allExtends = [...layers].every((l) => l.includes('ta-extends') || l.includes('ta extends'));
238
+ if (allExtends) {
239
+ errors.push(`Coverage Matrix: elementType "${type}" has no on-asr or mc-crop representative`);
240
+ }
241
+ }
242
+ }
243
+ return errors;
244
+ }
245
+ export function validateAsrPngFiles(projectDir, option, designerLog) {
246
+ const errors = [];
247
+ const ui = path.join(projectDir, `assets/images/reference/ui_state_sheet_${option}.png`);
248
+ const el = path.join(projectDir, `assets/images/reference/element_state_sheet_${option}.png`);
249
+ const mc = path.join(projectDir, `assets/images/storyboard/master_composite_option_${option}.png`);
250
+ if (!fs.existsSync(mc)) {
251
+ errors.push(`Missing MC: assets/images/storyboard/master_composite_option_${option}.png`);
252
+ }
253
+ if (!fs.existsSync(ui)) {
254
+ errors.push(`Missing ASR: assets/images/reference/ui_state_sheet_${option}.png`);
255
+ }
256
+ if (!fs.existsSync(el)) {
257
+ errors.push(`Missing ASR: assets/images/reference/element_state_sheet_${option}.png`);
258
+ }
259
+ if (designerLog && /element_2|element_state_sheet_\w+_2\.png/i.test(designerLog)) {
260
+ const el2 = path.join(projectDir, `assets/images/reference/element_state_sheet_${option}_2.png`);
261
+ if (!fs.existsSync(el2)) {
262
+ errors.push(`Missing ASR overflow board: assets/images/reference/element_state_sheet_${option}_2.png`);
263
+ }
264
+ }
265
+ return errors;
266
+ }
267
+ export function validateDesignerGate2b(projectDir) {
268
+ const errors = [];
269
+ const logPath = path.join(projectDir, 'logs/designer-log.md');
270
+ const layoutPath = path.join(projectDir, 'docs/layout-spec.md');
271
+ const statePath = path.join(projectDir, 'docs/project-state.md');
272
+ if (!fs.existsSync(logPath)) {
273
+ return ['Create logs/designer-log.md before Gate #2b STOP'];
274
+ }
275
+ if (!fs.existsSync(layoutPath)) {
276
+ return ['Missing docs/layout-spec.md'];
277
+ }
278
+ const designerLog = fs.readFileSync(logPath, 'utf8');
279
+ const layoutSpec = fs.readFileSync(layoutPath, 'utf8');
280
+ errors.push(...validateImageProductionPreflight(designerLog));
281
+ if (!designerLog.includes(ASR_COVERAGE_HEADING)) {
282
+ errors.push(`Missing "${ASR_COVERAGE_HEADING}" section`);
283
+ }
284
+ else {
285
+ errors.push(...validateSheetGridMetadata(designerLog));
286
+ errors.push(...validateCoverageRowCount(designerLog, layoutSpec));
287
+ errors.push(...validateTypeRepresentatives(designerLog));
288
+ }
289
+ let option = 'A';
290
+ if (fs.existsSync(statePath)) {
291
+ const opt = readSelectedMcOption(fs.readFileSync(statePath, 'utf8'));
292
+ if (opt)
293
+ option = opt;
294
+ }
295
+ errors.push(...validateAsrPngFiles(projectDir, option, designerLog));
296
+ return errors;
297
+ }
298
+ export function validateAsrCoverageProject(projectDir) {
299
+ const layoutPath = path.join(projectDir, 'docs/layout-spec.md');
300
+ const logPath = path.join(projectDir, 'logs/designer-log.md');
301
+ let expectedContractRows = 0;
302
+ let matrixRows = 0;
303
+ if (fs.existsSync(layoutPath)) {
304
+ const ids = parseLayoutSpecContractIds(fs.readFileSync(layoutPath, 'utf8'));
305
+ expectedContractRows = ids.elementIds.length + ids.staticTextIds.length;
306
+ }
307
+ if (fs.existsSync(logPath)) {
308
+ matrixRows = parseCoverageMatrixRows(fs.readFileSync(logPath, 'utf8')).length;
309
+ }
310
+ const errors = validateDesignerGate2b(projectDir);
311
+ return {
312
+ ok: errors.length === 0,
313
+ errors,
314
+ expectedContractRows,
315
+ matrixRows,
316
+ };
317
+ }
@@ -466,8 +466,8 @@ export async function buildCommand(projectPath, options) {
466
466
  ];
467
467
  // 渠道输出格式配置(根据各渠道Playable规格对照表)
468
468
  // 只支持 HTML: applovin, ironsource, unity, moloco, adikteev, remerge
469
- // 只支持 ZIP: tiktok, liftoff, bigo, snapchat, inmobi
470
- // 支持两种: facebook, google
469
+ // 只支持 ZIP: bigo, snapchat
470
+ // 支持两种: facebook, google, tiktok, liftoff, inmobi, mintegral
471
471
  const platformFormatConfig = {
472
472
  facebook: {
473
473
  formats: ['html', 'zip'],
@@ -485,12 +485,12 @@ export async function buildCommand(projectPath, options) {
485
485
  default: 'html',
486
486
  description: 'Google Ads 支持两种格式'
487
487
  },
488
- tiktok: { formats: ['zip'], default: 'zip' },
489
- liftoff: { formats: ['zip'], default: 'zip' },
488
+ tiktok: { formats: ['html', 'zip'], default: 'html' },
489
+ liftoff: { formats: ['html', 'zip'], default: 'html' },
490
490
  bigo: { formats: ['zip'], default: 'zip' },
491
491
  snapchat: { formats: ['zip'], default: 'zip' },
492
- inmobi: { formats: ['zip'], default: 'zip' },
493
- mintegral: { formats: ['zip'], default: 'zip' },
492
+ inmobi: { formats: ['html', 'zip'], default: 'html' },
493
+ mintegral: { formats: ['html', 'zip'], default: 'html' },
494
494
  };
495
495
  // 获取渠道支持的格式
496
496
  const getPlatformFormats = (platform) => {
@@ -120,7 +120,8 @@ async function remixEnsureStream(options) {
120
120
  throw new Error(event.message || 'ensure-stream 返回 error 事件');
121
121
  }
122
122
  else if (event.type === 'capacity_exceeded') {
123
- if (!event.candidates.length) {
123
+ const selectableCandidates = event.candidates.filter((c) => c.evictable !== false);
124
+ if (!selectableCandidates.length) {
124
125
  throw new Error(`容量已满 (${event.current}/${event.max}),但服务端未返回可驱逐候选`);
125
126
  }
126
127
  const selected = await inquirer.prompt([
@@ -129,8 +130,9 @@ async function remixEnsureStream(options) {
129
130
  name: 'slotId',
130
131
  message: `沙箱容量已满 (${event.current}/${event.max}),请选择一个候选项继续驱逐:`,
131
132
  choices: event.candidates.map((c) => ({
132
- name: `${c.slotId} (${c.projectId}/${c.branchName}) idle=${formatIdleSec(c.idleSec)}${c.hasUnpushedChanges ? ' [unpushed]' : ''}`,
133
+ name: `${c.slotId} (${c.projectId}/${c.branchName}) status=${c.agentStatus ?? 'unknown'} idle=${formatIdleSec(c.idleSec)}${c.hasUnpushedChanges ? ' [unpushed]' : ''}`,
133
134
  value: c.slotId,
135
+ disabled: c.evictable === false ? (c.disabledReason ?? 'not evictable') : false,
134
136
  })),
135
137
  },
136
138
  ]);
@@ -20,6 +20,7 @@ import * as fs from 'node:fs';
20
20
  import * as path from 'node:path';
21
21
  import { fileURLToPath } from 'node:url';
22
22
  import { parseSkillRefsFromAtomPlanProject, validateAtomPlanProject } from '../atom-plan/validate-atom-plan.js';
23
+ import { validateAsrCoverageProject } from '../atom-plan/validate-asr-coverage.js';
23
24
  import { loadConfig } from '../config.js';
24
25
  const KNOWN_ENGINES = ['phaser', 'threejs'];
25
26
  const SCORE_THRESHOLD_FULL = 8;
@@ -1352,6 +1353,29 @@ export function registerSkillsCommands(program) {
1352
1353
  if (!result.ok)
1353
1354
  process.exit(1);
1354
1355
  });
1356
+ // ── validate-asr-coverage ─────────────────────────────────────────────────
1357
+ addSkillsDirOption(skills
1358
+ .command('validate-asr-coverage')
1359
+ .description('校验 Gate #2b ASR Coverage Matrix、Image Production Preflight 与 ASR/MC PNG 文件')
1360
+ .option('--project-dir <path>', '项目根目录', process.cwd())
1361
+ .option('--json', '输出 JSON 格式')).action(async (opts) => {
1362
+ void opts.skillsDir;
1363
+ const result = validateAsrCoverageProject(opts.projectDir);
1364
+ if (opts.json) {
1365
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
1366
+ }
1367
+ else if (result.ok) {
1368
+ console.log(`\n✓ ASR coverage 校验通过(${result.matrixRows}/${result.expectedContractRows} Matrix rows)\n`);
1369
+ }
1370
+ else {
1371
+ console.error('\n✗ ASR coverage 校验失败\n');
1372
+ for (const e of result.errors)
1373
+ console.error(` - ${e}`);
1374
+ console.error();
1375
+ }
1376
+ if (!result.ok)
1377
+ process.exit(1);
1378
+ });
1355
1379
  // ── read ──────────────────────────────────────────────────────────────────
1356
1380
  addSkillsDirOption(skills
1357
1381
  .command('read <atomId>')
package/dist/index.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcraft/cli",
3
- "version": "0.0.42",
3
+ "version": "0.0.44",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,8 +27,8 @@
27
27
  "@gltf-transform/core": "^4.3.0",
28
28
  "@gltf-transform/extensions": "^4.3.0",
29
29
  "@gltf-transform/functions": "^4.3.0",
30
- "@playcraft/build": "^0.0.43",
31
- "@playcraft/common": "^0.0.31",
30
+ "@playcraft/build": "^0.0.45",
31
+ "@playcraft/common": "^0.0.33",
32
32
  "chokidar": "^4.0.3",
33
33
  "commander": "^13.1.0",
34
34
  "cors": "^2.8.6",
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: "Designer: (1) Gate #2 visual+narrative (MC = Developer UI spec), (2) TA-ready handoff, (3) audio + digit strip — not full asset production."
2
+ description: "Designer: (1) Gate #2 image production (MC + ASR), (2) TA-ready handoff, (3) audio + digit strip after #2b — not TA mass production."
3
3
  allowedTools:
4
4
  - "Read"
5
5
  - "Grep"
@@ -25,6 +25,8 @@ allowedTools:
25
25
  ---
26
26
 
27
27
  > **First Step**: Read `docs/project-state.md` → **`## Agent handoff`** → **## Runtime** → branch; read `refs/` only when Runtime says so. STOP: [STOP sync checklist](../../docs/team/collaboration.md#stop-sync-checklist).
28
+ >
29
+ > **Workflow spec**: `convergence-v1` + `designer-handoff-v2` — stage chain `pm → style_exploration → production → ui_pass → ui_review ⇄ ui_rework → gameplay_pass → done`. See [workflow-changelog.md](../../docs/team/workflow-changelog.md).
28
30
 
29
31
  # Designer — Playable Ads Design Agent
30
32
 
@@ -60,16 +62,16 @@ allowedTools:
60
62
 
61
63
  When trade-offs arise, preserve **#1 over #2 over #3**.
62
64
 
63
- 1. **Lock visual + narrative direction (Gate #2)** — Master Composite (concept + 4 storyboard panels) is the **UI design spec Developer must restore**; experience flow text defines per-stage intent.
64
- 2. **Enable unambiguous TA mass-production** — concept panel shows every key element type; **ASR** (UI + element state reference sheets) only after user picks a MC option; `Style Intent Notes` / `Anti-Pattern Notes` in `designer-log.md`.
65
- 3. **Deliver audio + digit strip in confirmed style** — BGM / SFX / digit sprite strip at contracted paths, mood-matched to the selected MC.
65
+ 1. **Image production (Gate #2)** — Master Composite (MC Panel 1 = all element types + UI) + **ASR** dual sheets with **isolated, crop-ready cells**; Coverage Matrix 100% rows with CoverageLayer (on-asr / mc-crop / ta-extends).
66
+ 2. **Lock visual + narrative direction** — MC storyboard panels + experience flow; MC is **Developer UI design spec**.
67
+ 3. **Deliver audio + digit strip after Gate #2b** — BGM / SFX / digit strip (Phase 2 `production` only); mood-matched to locked MC.
66
68
 
67
69
  ### Success criteria
68
70
 
69
- | Phase | You succeed when |
70
- | --------------------------------- | ----------------------------------------------------------------------------------------------------- |
71
- | **Phase 1** (`style_exploration`) | ≥2 MC; ASR dual sheets; Handoff Pack (Coverage Matrix, Palette Locked, Motion Notes); MC ratio checks |
72
- | **Phase 2** (`production`) | BGM/SFX/digit strip; VisualAtoms `done` per v2 rules; no open ICP for Designer |
71
+ | Phase | You succeed when |
72
+ | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
73
+ | **Phase 1** (`style_exploration`) | ≥2 MC; ASR dual sheets (cells isolated); Image Production Preflight; Handoff Pack (Coverage Matrix 100% rows + CoverageLayer, Palette Locked, Motion Notes); MC ratio checks |
74
+ | **Phase 2** (`production`) | BGM/SFX/digit strip; VisualAtoms `done` per v2 rules; no open ICP for Designer |
73
75
 
74
76
  ### Deprioritized / Non-goals (summary)
75
77
 
@@ -83,7 +85,7 @@ Full element sets & TA pipelines; single MC before Gate #2; audio before style l
83
85
 
84
86
  ## Identity
85
87
 
86
- **Art Director + Sound Designer** (designer-handoff-v2): you **生** direction — MC, ASR, audio. TA **产** — extract, atlases, completions. Ph.2: **no** batch `tile_sample_*.png`; visual baseline = ASR. TA tools **not yours**: `sprite-sheet`, `animate`, `use-vfx`, `segment`, `3d *`.
88
+ **Art Director** (image-first, designer-handoff-v2): you **生** direction — **MC + ASR** (Phase 1), then audio (Phase 2). TA **产** — crop, remove-background, atlases. Ph.2: **no** batch `tile_sample_*.png`; visual baseline = MC + ASR. **Do not** run `remove-background` on ASR sheets — TA owns that. TA tools **not yours**: `sprite-sheet`, `animate`, `use-vfx`, `segment`, `3d *`.
87
89
 
88
90
  ## File Access
89
91
 
@@ -103,18 +105,20 @@ Full element sets & TA pipelines; single MC before Gate #2; audio before style l
103
105
 
104
106
  **Skills:** `playcraft-audio-generation`; `playcraft-style-qa` (ICP supplementary PNGs only) | **Refs:** `designer-color-audio-recipes.md`, `designer-deliverable-spec.md` § Phase 2 | Mark VisualAtoms `actualOutput: ASR:...` when covered.
105
107
 
106
- ## DAG & critical rules (max 9)
108
+ ## DAG & critical rules
107
109
 
108
110
  0. **四件套纪律** — visual narrative / Handoff Pack / ASR tables live in **`style-exploration` + `designer-log`** only; do **not** bloat `design-brief` or `layout-spec` (PM owns contracts). Palette deltas → `designer-log` § Palette Locked.
109
- 1. **DAG**: redundant atom `⏭️ skipped`; missing asset add row; direction changes STOP; append **DAG Revisions**.
110
- 2. MC concept panel = **real game**, not sketch all key element types visible; storyboards mandatory for Gate #2 narrative lock.
111
- 3. **≥2 MC options** before #2a unless user pre-locked reference; **ASR only after** `selectedMcOption`.
112
- 4. MC = **5×完整 9:16 H5** (e.g. 3600×1280 or 4096×1455); never `1:1` / `10:3`; verify with `playcraft image info`.
113
- 4b. **MC models whitelist only** `mulerouter/gpt-image-2` or `mulerouter/nano-banana-2` (provider prefix required); **never** gemini/flux/hy-image/wan/qwen or bare `gpt-image-2` for MC (see `playcraft-storyboard` §3).
114
- 5. **ASR: zero overlap** — UI 板与元素板各自网格无接触;有重叠则重生成。
115
- 6. **No audio** until style confirmed (#2b).
116
- 7. **Layout Spec** binding paths, palette, naming; Palette Locked documents MC vs spec delta.
117
- 8. **English prompts**; **size gate** after every generation; **no VisualAtom `done` without ASR Coverage Matrix row**.
118
- 9. **mediaGroups first for audio** read `atom-plan.json` → `skillsMatch.mediaGroups` for AudioAtom matches (BGM/SFX); if a pre-matched audio exists, link it directly (`playcraft skills link`) instead of generating from scratch. Skip only when quality/mood mismatch (document reason in `dagRevisions`).
119
- 10. **Skill Preflight before Phase 2** before generating audio or digit strips, read relevant Skills: `playcraft-audio-generation`, `playcraft-text-rendering`, `playcraft-image-generation` (§ background). Fill `designer-log.md` § Skill Preflight table. Do not "freelance" — follow platform patterns.
120
- 11. **Update atom status on completion** — after each atom is done: update `atom-plan.json` → `atoms[].status = "done"` + `atoms[].actualOutput = "<path>"` (or `"ASR:<sheet>:<region>"`). Write `atom-plan.md` § Asset Skill Context. **Never** leave atoms at `pending` after delivery.
111
+ 1. **Image Production Preflight (Phase 1)** before MC/ASR: fill `designer-log.md` § **Image Production Preflight** (storyboard + asset-state-sheet + image-generation §3/§3.1).
112
+ 2. **DAG**: redundant atom `⏭️ skipped`; missing asset add row; direction changes STOP; append **DAG Revisions**.
113
+ 3. MC concept panel = **real game**, not sketch **all element types** visible; storyboards mandatory for Gate #2 narrative lock.
114
+ 4. **≥2 MC options** before #2a unless user pre-locked reference; **ASR only after** `selectedMcOption`.
115
+ 5. MC = **5×完整 9:16 H5** (e.g. 3600×1280 or 4096×1455); never `1:1` / `10:3`; verify with `playcraft image info`.
116
+ 5b. **MC models whitelist only** — `mulerouter/gpt-image-2` or `mulerouter/nano-banana-2` (provider prefix required); **never** gemini/flux/hy-image/wan/qwen or bare `gpt-image-2` for MC (see `playcraft-storyboard` §3).
117
+ 6. **ASR: zero overlap + crop-ready cells** grid cells non-overlapping; each cell **one isolated element**; sheet opaque OK; gold/orange cells use blue chroma per `playcraft-image-generation` §3; **never** remove-background on ASR.
118
+ 7. **Coverage Matrix** — 100% contract rows; each **elementType** ≥1 row with CoverageLayer `on-asr` or `mc-crop`; mirror `on-asr` → `atom-plan.json` `asrSlot`.
119
+ 8. **No audio** until Gate #2b passed (Phase 2 only).
120
+ 9. **Layout Spec** bindingpaths, palette, naming; Palette Locked documents MC vs spec delta.
121
+ 10. **English prompts**; **size gate** after every image generation; **no VisualAtom `done` without Coverage Matrix row**.
122
+ 11. **mediaGroups first for audio (Phase 2)** — read `atom-plan.json` → `skillsMatch.mediaGroups` for AudioAtom matches; link before generate.
123
+ 12. **Skill Preflight before Phase 2** — audio/digit Skills in `designer-log.md` § Skill Preflight (Phase 2 section).
124
+ 13. **Update atom status on completion** — `atom-plan.json` status + `actualOutput` (`ASR:...` or path); **never** leave atoms `pending` after delivery.
@@ -19,6 +19,8 @@ allowedTools:
19
19
  ---
20
20
 
21
21
  > **First Step**: Read `docs/project-state.md` → **`## Agent handoff`** → **## Runtime** → branch; read `refs/` only when Runtime says so. STOP: [STOP sync checklist](../../docs/team/collaboration.md#stop-sync-checklist).
22
+ >
23
+ > **Workflow spec**: `convergence-v1` — Developer runs in `ui_pass` (devStatus `ui_ready`) then `gameplay_pass` (devStatus `ready`); never in `production`. See [workflow-changelog.md](../../docs/team/workflow-changelog.md).
22
24
 
23
25
  # Developer — Playable Ads Developer Agent
24
26
 
@@ -17,6 +17,8 @@ allowedTools:
17
17
  ---
18
18
 
19
19
  > **First Step**: Read `docs/project-state.md` → **`## Agent handoff`** → **## Runtime** → branch; read `refs/` only when Runtime says so. STOP: [STOP sync checklist](../../docs/team/collaboration.md#stop-sync-checklist).
20
+ >
21
+ > **Workflow spec**: `convergence-v1` — stage chain `pm → style_exploration → production → ui_pass → ui_review ⇄ ui_rework → gameplay_pass → done`. See [workflow-changelog.md](../../docs/team/workflow-changelog.md).
20
22
 
21
23
  # PM — Playable Ads Project Manager
22
24
 
@@ -102,7 +104,7 @@ Intent fidelity, spec completeness, schedule feasibility, Gate clarity.
102
104
  | --- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
103
105
  | 1 | `docs/project-state.md` | `stage: pm`, `gates.#1 = pending`, handoff STOP (`gate_pending: "1"`) |
104
106
  | 2 | `docs/design-brief.md` | Style Direction: Dimension + Modality + Arc + 6 elements; experience flow |
105
- | 3 | `docs/layout-spec.md` | `assetMapping` (locked paths) + atlas grouping table (tiles / ui / vfx) |
107
+ | 3 | `docs/layout-spec.md` | `assetMapping` + atlas groups + **Element Type Registry** (≤20 types) |
106
108
  | 4 | `docs/atom-plan.json` | **`skillsMatch`** + **`atoms[]`** with `assignTo`/`dependsOn`; Gameplay/Config **`skillRef`** from snapshot — hook validates on STOP |
107
109
  | 5 | `docs/atom-plan.md` | **Acceptance criteria only** at Gate #1 (no Atom List / DAG Order mirror) |
108
110
 
@@ -29,10 +29,12 @@ All visible text is image assets — **zero font files**. Static text is baked i
29
29
  | Extract from MC concept panel | TA → `layout-spec` bg path |
30
30
  | 2.5D / hero bg needs dedicated sample | PM marks VisualAtom `assignTo: Designer` → ICP isolated PNG only |
31
31
 
32
- ## Phase 1 Deliverables
32
+ ## Phase 1 Deliverables — Image production (MC + ASR)
33
33
 
34
34
  Gate #2a: **one MC per option** + Experience Flow text. Gate #2b (after `selectedMcOption`): **ASR dual sheets** + **Handoff Pack** (below).
35
35
 
36
+ > **Phase 1 scope = images only.** Audio/digit strip is **Phase 2** after Gate #2b — see § Phase 2 Deliverables.
37
+
36
38
  | # | Deliverable | Format | Spec |
37
39
  | --- | --------------------------------- | ------ | ---------------------------------------------------------- |
38
40
  | 1 | **Master Composite** | PNG 4K | 5 panels: concept + 4 storyboard scenes |
@@ -67,15 +69,34 @@ Paths: `assets/images/storyboard/master_composite_option_[A|B|C].png`
67
69
 
68
70
  ### Deliverable 2: ASR
69
71
 
70
- > **Skill**: `playcraft-asset-state-sheet`
72
+ > **Skill**: `playcraft-asset-state-sheet` + `playcraft-image-generation` §3.1 (ASR branch)
71
73
 
72
74
  Only after `selectedMcOption` is set. `--reference-image` = confirmed MC.
73
75
 
74
76
  ```
75
77
  assets/images/reference/ui_state_sheet_[X].png
76
78
  assets/images/reference/element_state_sheet_[X].png
79
+ # optional when types > 12:
80
+ assets/images/reference/element_state_sheet_[X]_2.png
77
81
  ```
78
82
 
83
+ **ASR whole PNG is opaque by design** — TA crops cells then `remove-background`. Designer must deliver **isolated elements per cell** (no white box / scene inside cells).
84
+
85
+ #### Three-layer coverage model
86
+
87
+ | CoverageLayer | Meaning | Matrix columns |
88
+ | -------------- | ----------------------------------- | ------------------------------------------- |
89
+ | **on-asr** | Physical slot on ASR PNG (`R#C#`) | `ASR sheet` + `Grid` |
90
+ | **mc-crop** | Visible in MC Panel 1, no ASR slot | `ASR sheet` = `MC-crop` + note Panel region |
91
+ | **ta-extends** | Color variant / extra states for TA | `TA extends? = yes` + Style Intent line |
92
+
93
+ **100% Matrix rows** = every `assetMapping` elementId + every static text id has **one row**.
94
+ **NOT** every row needs a physical ASR slot — use layers above.
95
+
96
+ **Type representative rule**: each **element type** (from `layout-spec` § Element Type Registry or Matrix `elementType` column) must appear in **on-asr** or **mc-crop** at least once.
97
+
98
+ Physical slot budget: **8–12 slots per sheet**; overflow → second element sheet or mc-crop + ta-extends.
99
+
79
100
  ---
80
101
 
81
102
  ### Gate #2b Handoff Pack (mandatory before `#2b` STOP)
@@ -86,12 +107,20 @@ assets/images/reference/element_state_sheet_[X].png
86
107
 
87
108
  One row per contract item from `layout-spec.md`:
88
109
 
89
- | Contract id | Type | ASR sheet | Grid | State(s) | TA extends? |
90
- | ------------------------------- | ------ | ------------ | ------- | ------------ | ----------- |
91
- | `elementId` from assetMapping | visual | ui / element | row,col | e.g. default | yes/no |
92
- | `staticTextId` from Text Assets | text | ui / MC crop | — | baked | yes/no |
110
+ | Contract id | elementType | Type | CoverageLayer | ASR sheet | Grid | State(s) | TA extends? |
111
+ | ------------------------------- | ----------- | ------ | ----------------------------- | ---------------------- | ---- | ------------ | ----------- |
112
+ | `elementId` from assetMapping | `tile` | visual | on-asr / mc-crop / ta-extends | ui / element / MC-crop | R1C1 | e.g. default | yes/no |
113
+ | `staticTextId` from Text Assets | — | text | mc-crop or on-asr | ui / MC-crop | — | baked | yes/no |
93
114
 
94
- **100% rule**: every `elementId` in `assetMapping` and every static text id must have a row. Empty ASR slot → `TA extends? = yes` + note in Style Intent Notes.
115
+ **100% rule**: every `elementId` in `assetMapping` and every static text id must have a row.
116
+
117
+ **CoverageLayer rules**:
118
+
119
+ - **on-asr**: must have valid `Grid` (R#C#) and matching PNG slot; mirror to `atom-plan.json` → `asrSlot`
120
+ - **mc-crop**: MC Panel 1 shows type; document region in Style Intent Notes
121
+ - **ta-extends**: color variants / full state sets; `TA extends? = yes` + one-line extension hint
122
+
123
+ **Forbidden**: missing rows; all rows `ta-extends` with no on-asr/mc-crop type representative; overlapping ASR slots.
95
124
 
96
125
  #### 2. Palette Locked (`logs/designer-log.md`)
97
126
 
@@ -172,6 +201,16 @@ For each `assignTo: Designer` VisualAtom:
172
201
 
173
202
  **Forbidden**: mark VisualAtom `done` without ASR row or supplementary file.
174
203
 
204
+ #### ICP supplementary PNG — when (and only when) to produce
205
+
206
+ Designer Phase 2 supplementary PNGs are a **fallback path**, not a default. Produce one **only if all three** are true:
207
+
208
+ 1. **TA Step 0c Style Interpretation** marks that atom's `confidence = low` in `logs/ta-log.md` (i.e. Designer's MC/ASR didn't tell TA enough).
209
+ 2. **Crop / segment from ASR or MC failed** — TA tried `playcraft image crop` (grid coord from ASR Coverage Matrix) and `playcraft image segment --boxes` / `--text` on the MC concept panel, and the extracted region is unusable (chroma residue, partial element, wrong perspective).
210
+ 3. **TA opens an ICP entry** in `docs/intent-clarifications.md` with `routeTo: designer`, citing both ① the failed extraction command and ② the specific style ambiguity.
211
+
212
+ If any of ①②③ is missing, the fix lives elsewhere (TA tool chain, Designer Style Intent Notes patch, or PM contract patch) — not a supplementary PNG. Document the chosen route in `designer-log.md`.
213
+
175
214
  Run **`playcraft-style-qa` §1** only for **ICP supplementary** image files (not for ASR sheets themselves).
176
215
 
177
216
  ### TA ownership in Phase 2 (not Designer)