@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.
- package/dist/atom-plan/validate-asr-coverage.js +317 -0
- package/dist/commands/build.js +6 -6
- package/dist/commands/remix.js +4 -2
- package/dist/commands/skills.js +24 -0
- package/dist/index.js +0 -0
- package/package.json +3 -3
- package/project-template/.claude/agents/designer.md +26 -22
- package/project-template/.claude/agents/developer.md +2 -0
- package/project-template/.claude/agents/pm.md +3 -1
- package/project-template/.claude/agents/refs/designer-deliverable-spec.md +46 -7
- package/project-template/.claude/agents/refs/designer-handoff-v2-checklist.md +21 -13
- package/project-template/.claude/agents/refs/designer-style-exploration-flow.md +39 -9
- package/project-template/.claude/agents/refs/developer-dev-handoff.md +1 -1
- package/project-template/.claude/agents/refs/pm-workflow-detail.md +18 -2
- package/project-template/.claude/agents/refs/reviewer-convergence-eval.md +17 -5
- package/project-template/.claude/agents/refs/ta-pipeline-cookbook.md +42 -6
- package/project-template/.claude/agents/reviewer.md +8 -5
- package/project-template/.claude/agents/technical-artist.md +2 -0
- package/project-template/.claude/hooks/README.md +34 -6
- package/project-template/.claude/hooks/asr-coverage-validate.mjs +381 -0
- package/project-template/.claude/hooks/validate-workflow-stop.mjs +113 -7
- package/project-template/.claude/skills/playcraft-asset-state-sheet/SKILL.md +76 -22
- package/project-template/.claude/skills/playcraft-image-generation/SKILL.md +19 -0
- package/project-template/docs/team/agent-runtime-matrix.md +71 -39
- package/project-template/docs/team/atom-plan-format.md +68 -0
- package/project-template/docs/team/core-model.md +20 -19
- package/project-template/docs/team/workflow-consistency-checklist.md +52 -0
- package/project-template/templates/atom-plan.template.json +18 -0
- package/project-template/templates/designer-log.template.md +78 -5
- package/project-template/templates/layout-spec.template.md +48 -8
- package/project-template/templates/ta-log.template.md +50 -22
- package/dist/playable/base-builder.js +0 -265
- package/dist/playable/builder.js +0 -1462
- package/dist/playable/converter.js +0 -150
- package/dist/playable/index.js +0 -3
- package/dist/playable/platforms/base.js +0 -12
- package/dist/playable/platforms/facebook.js +0 -37
- package/dist/playable/platforms/index.js +0 -24
- package/dist/playable/platforms/snapchat.js +0 -59
- package/dist/playable/playable-builder.js +0 -521
- package/dist/playable/types.js +0 -1
- package/dist/playable/vite/config-builder.js +0 -136
- package/dist/playable/vite/platform-configs.js +0 -102
- package/dist/playable/vite/plugin-model-compression.js +0 -63
- package/dist/playable/vite/plugin-platform.js +0 -65
- package/dist/playable/vite/plugin-playcanvas.js +0 -454
- package/dist/playable/vite-builder.js +0 -125
- package/project-template/.claude/settings.local.json +0 -4
- package/project-template/logs/.gitkeep +0 -0
- package/project-template/ta-workspace/logs/.gitkeep +0 -0
- 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
|
+
}
|
package/dist/commands/build.js
CHANGED
|
@@ -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:
|
|
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: '
|
|
489
|
-
liftoff: { formats: ['zip'], default: '
|
|
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: '
|
|
493
|
-
mintegral: { formats: ['zip'], default: '
|
|
492
|
+
inmobi: { formats: ['html', 'zip'], default: 'html' },
|
|
493
|
+
mintegral: { formats: ['html', 'zip'], default: 'html' },
|
|
494
494
|
};
|
|
495
495
|
// 获取渠道支持的格式
|
|
496
496
|
const getPlatformFormats = (platform) => {
|
package/dist/commands/remix.js
CHANGED
|
@@ -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
|
-
|
|
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
|
]);
|
package/dist/commands/skills.js
CHANGED
|
@@ -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.
|
|
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.
|
|
31
|
-
"@playcraft/common": "^0.0.
|
|
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
|
|
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. **
|
|
64
|
-
2. **
|
|
65
|
-
3. **Deliver audio + digit strip
|
|
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
|
|
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
|
|
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. **
|
|
110
|
-
2.
|
|
111
|
-
3.
|
|
112
|
-
4. MC
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
6. **
|
|
116
|
-
7. **
|
|
117
|
-
8. **
|
|
118
|
-
9. **
|
|
119
|
-
10. **
|
|
120
|
-
11. **
|
|
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** binding — paths, 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`
|
|
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
|
|
90
|
-
| ------------------------------- | ------ |
|
|
91
|
-
| `elementId` from assetMapping | visual | ui / element |
|
|
92
|
-
| `staticTextId` from Text Assets | text | ui / MC
|
|
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.
|
|
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)
|