cardbuddy 0.15.0 → 0.17.0
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/init.js +0 -2
- package/dist/preview-editor/assets/index-CXDf5tVg.css +1 -0
- package/dist/preview-editor/assets/{index-CIIfMlwI.js → index-DLQkCPLT.js} +113 -86
- package/dist/preview-editor/index.html +2 -2
- package/dist/vendored/card-editor/runtime/adapter.js +2 -2
- package/dist/vendored/mcp/client.mjs +1 -1
- package/dist/vendored/server/core/ai-chat.js +251 -80
- package/dist/vendored/server/core/browser.js +17 -0
- package/dist/vendored/server/core/cardFiles.js +49 -1
- package/dist/vendored/server/core/cardImage.js +103 -2
- package/dist/vendored/server/core/cardValidate.js +16 -5
- package/dist/vendored/server/core/screenshot.js +94 -0
- package/dist/vendored/skills/card-skill-trainer/SKILL.md +68 -13
- package/dist/vendored/skills/card-skill-trainer/scripts/diagnose_case.mjs +14 -82
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/diagnosis.mjs +207 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/layout-correction-sim.mjs +62 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/paradigm-router.mjs +76 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/pixel-worker-runner.mjs +31 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/pixel-worker.mjs +138 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/scoring.mjs +24 -20
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/telemetry.mjs +46 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/plan_next_round.mjs +5 -2
- package/dist/vendored/skills/card-skill-trainer/scripts/run_round.mjs +286 -92
- package/dist/vendored/skills/card-skill-trainer/scripts/score_round.mjs +74 -2
- package/dist/vendored/skills/card-skill-trainer/scripts/sink_rules.mjs +127 -0
- package/dist/vendored/skills/dsl-card-generator/SKILL.md +93 -27
- package/dist/vendored/skills/dsl-card-generator/references/component_rules.md +14 -0
- package/dist/vendored/skills/dsl-card-generator/references/dsl_structure.md +1 -15
- package/dist/vendored/skills/dsl-card-generator/references/expression_engine.md +11 -20
- package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +217 -44
- package/dist/vendored/skills/dsl-card-generator/references/ir-format.md +87 -3
- package/dist/vendored/skills/dsl-card-generator/references/iteration_protocol.md +35 -6
- package/dist/vendored/skills/dsl-card-generator/references/previewer_contract.md +21 -0
- package/dist/vendored/skills/dsl-card-generator/references/quickref.md +28 -27
- package/dist/vendored/skills/dsl-card-generator/references/recipes.md +30 -8
- package/dist/vendored/skills/dsl-card-generator/references/rules.md +3 -2
- package/dist/vendored/skills/dsl-card-generator/references/scripts_reference.md +5 -2
- package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +2 -3
- package/dist/vendored/skills/dsl-card-generator/references/spec_to_schema_patterns.md +1 -4
- package/dist/vendored/skills/dsl-card-generator/scripts/add_component.mjs +5 -14
- package/dist/vendored/skills/dsl-card-generator/scripts/autofix_schema.py +88 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/check_fidelity.mjs +143 -42
- package/dist/vendored/skills/dsl-card-generator/scripts/check_topology.py +0 -35
- package/dist/vendored/skills/dsl-card-generator/scripts/expand_ir.mjs +99 -53
- package/dist/vendored/skills/dsl-card-generator/scripts/gate.mjs +5 -2
- package/dist/vendored/skills/dsl-card-generator/scripts/iterate.mjs +388 -29
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/component_contract.mjs +315 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/layout-tokens.mjs +1 -1
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.mjs +23 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.py +0 -37
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/python.mjs +99 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/adaptive-width.mjs +105 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/bindings.mjs +260 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/expr.mjs +52 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/image.mjs +96 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/registry.mjs +166 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/structure.mjs +127 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/walk.mjs +61 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/scaffold.mjs +48 -1
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/scope_alias.mjs +125 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_assemble.mjs +366 -45
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_extract.mjs +169 -12
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_shared.mjs +17 -9
- package/dist/vendored/skills/dsl-card-generator/scripts/lint_card.mjs +119 -78
- package/dist/vendored/skills/dsl-card-generator/scripts/preflight.mjs +48 -50
- package/dist/vendored/skills/dsl-card-generator/scripts/readiness.mjs +634 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/register_card_template.mjs +5 -8
- package/dist/vendored/skills/dsl-card-generator/scripts/route_input.mjs +5 -10
- package/dist/vendored/skills/dsl-card-generator/scripts/spec_audit.mjs +122 -8
- package/dist/vendored/skills/dsl-card-generator/scripts/v3_regression.mjs +11 -2
- package/dist/vendored/skills/dsl-card-generator/scripts/validate_schema.py +23 -15
- package/dist/vendored/skills/finmall-card-sync/scripts/api_batch_sync.mjs +46 -2
- package/dist/vendored/skills/mbank-design-gen/SKILL.md +76 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/icon-apps.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/icon-arrow-right-blue.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/icon-check.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/icon-settings.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/icon-sound.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/icon-voice-switch.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/progress-thumb.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/tag-bg.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/icons/vs-badge.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/ref/ref-chat-options.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/ref/ref-home-scene.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/ref/ref-product-detail.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/ref/ref-rate-table.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/assets/robot.png +0 -0
- package/dist/vendored/skills/mbank-design-gen/references/component-library.md +127 -0
- package/dist/vendored/skills/mbank-design-gen/references/design-spec.md +261 -0
- package/dist/vendored/skills/mbank-design-gen/templates/mockup-template.html +138 -0
- package/dist/vendored/skills/mbank-design-gen/tools/screenshot.mjs +34 -0
- package/dist/vendored/skills/shared/exit-codes.md +79 -0
- package/dist/vendored/skills/shared/spec-contract.md +127 -0
- package/dist/vendored/skills/sketch-spec-to-card/SKILL.md +27 -10
- package/dist/vendored/skills/sketch-spec-to-card/references/spec-schema.md +3 -3
- package/dist/vendored/skills/sketch-spec-to-card/scripts/build_prompt.py +0 -8
- package/dist/vendored/skills/sketch-spec-to-card/scripts/cluster_states.py +14 -25
- package/dist/vendored/skills/sketch-spec-to-card/scripts/parse_meaxure.py +121 -47
- package/dist/vendored/skills/sketch-spec-to-card/scripts/png_utils.py +0 -5
- package/dist/vendored/skills/sketch-spec-to-card/scripts/test_text_only_artboard.py +98 -16
- package/dist/vendored/skills/sketch-spec-to-card/scripts/vision.py +55 -37
- package/dist/vendored/templates/helloCard/schema.json +62 -1
- package/dist/vendored/tools/finmall-env.mjs +1 -18
- package/package.json +1 -1
- package/dist/preview-editor/assets/index-DQn0zQBP.css +0 -1
- package/dist/vendored/skills/card-skill-trainer/scripts/test_missing_slice.py +0 -122
- package/dist/vendored/skills/dsl-card-generator/scripts/analyze_component_usage.mjs +0 -153
|
@@ -49,6 +49,22 @@ async function getBrowser() {
|
|
|
49
49
|
return browserPromise
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/** 立即关闭单例浏览器并复位(测试 / 脚本收尾用;生产路径靠空闲 5 分钟自动关) */
|
|
53
|
+
async function closeBrowser() {
|
|
54
|
+
if (idleTimer) {
|
|
55
|
+
clearTimeout(idleTimer)
|
|
56
|
+
idleTimer = null
|
|
57
|
+
}
|
|
58
|
+
const p = browserPromise
|
|
59
|
+
browserPromise = null
|
|
60
|
+
if (p) {
|
|
61
|
+
try {
|
|
62
|
+
const b = await p
|
|
63
|
+
if (b && typeof b.close === 'function') await b.close()
|
|
64
|
+
} catch (_) { /* 关闭失败忽略 */ }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
52
68
|
function resetIdleTimer() {
|
|
53
69
|
if (idleTimer) clearTimeout(idleTimer)
|
|
54
70
|
idleTimer = setTimeout(async () => {
|
|
@@ -112,6 +128,7 @@ export {
|
|
|
112
128
|
NAV_TIMEOUT_MS,
|
|
113
129
|
SETTLE_MS,
|
|
114
130
|
getBrowser,
|
|
131
|
+
closeBrowser,
|
|
115
132
|
serialize,
|
|
116
133
|
openCardScreen,
|
|
117
134
|
}
|
|
@@ -8,6 +8,54 @@ import { deleteJournal, turnsDir } from './turnJournal.js'
|
|
|
8
8
|
import { log } from './logger.js'
|
|
9
9
|
import { buildDataFromSchema } from './buildDataFromSchema.js'
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* 空白卡根样式:margin / padding / gap 显式归零(而非「未设置」)。
|
|
13
|
+
* 渲染器对无显式样式的容器会注入默认值(padding:2px / gap:4px / justify-content:center),
|
|
14
|
+
* 空白卡必须显式归零压掉注入,否则新卡自带隐性间距且属性面板显示「未设置」。
|
|
15
|
+
* 形状取自存量卡根节点(见 style-meta.json:marginType / paddingType,'0' = 全边统一值)。
|
|
16
|
+
* 同一形状还有两份内联副本(禁跨包 import):skills/dsl-card-generator lib/scaffold.mjs、
|
|
17
|
+
* skills/finmall-card-sync api_batch_sync.mjs 的 buildBlankSchema —— 改这里必须同步改那两处。
|
|
18
|
+
*/
|
|
19
|
+
function blankResetStyle() {
|
|
20
|
+
return {
|
|
21
|
+
properties: [
|
|
22
|
+
{
|
|
23
|
+
itemId: '100',
|
|
24
|
+
itemKey: 'flex',
|
|
25
|
+
itemValueFrom: '0',
|
|
26
|
+
itemValue: {},
|
|
27
|
+
itemChildren: {
|
|
28
|
+
properties: [{ itemId: '003', itemKey: 'gap', itemValueFrom: '0', itemValue: '0' }],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
itemId: '400',
|
|
33
|
+
itemKey: 'padding',
|
|
34
|
+
itemValueFrom: '0',
|
|
35
|
+
itemValue: {},
|
|
36
|
+
itemChildren: {
|
|
37
|
+
properties: [
|
|
38
|
+
{ itemId: '001', itemKey: 'paddingType', itemValueFrom: '0', itemValue: '0' },
|
|
39
|
+
{ itemId: '002', itemKey: 'padding', itemValueFrom: '0', itemValue: '0' },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
itemId: '500',
|
|
45
|
+
itemKey: 'margin',
|
|
46
|
+
itemValueFrom: '0',
|
|
47
|
+
itemValue: {},
|
|
48
|
+
itemChildren: {
|
|
49
|
+
properties: [
|
|
50
|
+
{ itemId: '001', itemKey: 'marginType', itemValueFrom: '0', itemValue: '0' },
|
|
51
|
+
{ itemId: '002', itemKey: 'margin', itemValueFrom: '0', itemValue: '0' },
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
11
59
|
/**
|
|
12
60
|
* 单张卡片 / 卡片列表的按需读取 / 写回服务端处理(dev 下实时读磁盘,无缓存)。
|
|
13
61
|
* - GET /__cards :实时扫描 src/cards/* 目录,返回卡片列表 [{id,name,pageId}]。
|
|
@@ -344,7 +392,7 @@ export function createCardFilesMiddleware({ root, notify = null }) {
|
|
|
344
392
|
view: {
|
|
345
393
|
width: '375',
|
|
346
394
|
className: 'Root',
|
|
347
|
-
style:
|
|
395
|
+
style: blankResetStyle(),
|
|
348
396
|
properties: [],
|
|
349
397
|
eventactions: [],
|
|
350
398
|
components: [],
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
-
import { takeScreenshot } from './screenshot.js'
|
|
3
|
+
import { takeScreenshot, generateCardCover } from './screenshot.js'
|
|
4
|
+
import { serialize, MAX_QUEUED } from './browser.js'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* 卡片预览图本地静态服务:把卡片目录下的「同名预览图」映射为
|
|
@@ -14,6 +15,15 @@ import { takeScreenshot } from './screenshot.js'
|
|
|
14
15
|
* 若卡片目录没有同名预览图,则回退到实时无头截图(takeScreenshot,复用 /__screenshot
|
|
15
16
|
* 同一套渲染内核),保证任何卡片都能出预览。截图失败(如缺 Chrome)则 404,
|
|
16
17
|
* 由调用方回退占位。仅服务 src/cards 下的卡片预览图,禁止目录穿越。
|
|
18
|
+
*
|
|
19
|
+
* POST /__card-image/<id>:为该卡生成封面预览图并写盘(顶栏「预览图」按钮触发)。
|
|
20
|
+
* 无头渲染磁盘态 schema → 透明底、与卡片实际渲染尺寸 1:1 的 PNG → 覆盖写 src/cards/<id>/<id>.png。
|
|
21
|
+
* 用于手动搭建 / 生成卡片后补齐封面:省去手工截图 + 裁切。约定:
|
|
22
|
+
* - 未保存的编辑器改动不参与渲染,前端负责先落盘再调用;
|
|
23
|
+
* - 同 id 其它扩展名的旧预览图(jpg/webp 等)一并清除,保证「同名预览图」单一来源
|
|
24
|
+
* (GET 按 readdir 顺序取第一个同名位图,双封面并存会让新 PNG 输给陈旧旧图);
|
|
25
|
+
* - 空白卡(schema 无 components)拒绝生成(422),与 GET 回退 404 的语义一致;
|
|
26
|
+
* - 原子写(临时文件 + rename),并发读取不会拿到半截 PNG。
|
|
17
27
|
*/
|
|
18
28
|
|
|
19
29
|
const MIME = {
|
|
@@ -46,6 +56,98 @@ export function createCardImageMiddleware({ root }) {
|
|
|
46
56
|
if (!dir.startsWith(cardsRoot + path.sep) && dir !== cardsRoot) return next()
|
|
47
57
|
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) return next()
|
|
48
58
|
|
|
59
|
+
// POST:生成封面预览图并覆盖写 <id>.png(顶栏「预览图」按钮触发,见文件头注释)
|
|
60
|
+
if ((req.method || 'GET').toUpperCase() === 'POST') {
|
|
61
|
+
// 空白卡(schema 无 components)截出来是无意义纯透明图:直接拒绝
|
|
62
|
+
let cardWidth = 375
|
|
63
|
+
try {
|
|
64
|
+
const schema = JSON.parse(fs.readFileSync(path.join(dir, 'schema.json'), 'utf8'))
|
|
65
|
+
const comps = schema && schema.view && schema.view.components
|
|
66
|
+
if (Array.isArray(comps) && !comps.length) {
|
|
67
|
+
res.statusCode = 422
|
|
68
|
+
res.setHeader('Content-Type', 'application/json')
|
|
69
|
+
return res.end(
|
|
70
|
+
JSON.stringify({ error: 'empty-card', message: '空白卡(无组件)没有可预览的内容,不生成预览图' }),
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
const w = parseInt((schema && schema.view && schema.view.width) || '', 10)
|
|
74
|
+
if (w > 0) cardWidth = w
|
|
75
|
+
} catch {
|
|
76
|
+
/* schema 缺失 / 损坏:按默认卡宽走,渲染失败会有结构化报错 */
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const shot = await serialize(() => generateCardCover(id, { width: cardWidth }))
|
|
80
|
+
if (shot.tooBusy) {
|
|
81
|
+
res.statusCode = 429
|
|
82
|
+
res.setHeader('Content-Type', 'application/json')
|
|
83
|
+
return res.end(
|
|
84
|
+
JSON.stringify({ error: 'too-busy', message: '截图队列已满(并发上限 ' + MAX_QUEUED + '),请稍后重试' }),
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
if (!shot.buffer) {
|
|
88
|
+
res.statusCode = shot.code || 503
|
|
89
|
+
res.setHeader('Content-Type', 'application/json')
|
|
90
|
+
return res.end(JSON.stringify({ error: shot.error, message: shot.message, fix: shot.fix }))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const file = path.join(dir, id + '.png')
|
|
94
|
+
const overwritten = fs.existsSync(file)
|
|
95
|
+
// 原子写:先落临时文件再 rename,并发读取(网格缩略图)不会拿到半截 PNG
|
|
96
|
+
const tmp = file + '.tmp'
|
|
97
|
+
fs.writeFileSync(tmp, shot.buffer)
|
|
98
|
+
fs.renameSync(tmp, file)
|
|
99
|
+
// 清掉同 id 其它扩展名的旧预览图:GET 按 readdir 顺序取第一个同名位图,
|
|
100
|
+
// 双封面并存会让「刚生成的 PNG」可能输给陈旧的 jpg/webp(单一来源纪律)
|
|
101
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
102
|
+
const dot = entry.lastIndexOf('.')
|
|
103
|
+
if (dot < 0) continue
|
|
104
|
+
const base = entry.slice(0, dot)
|
|
105
|
+
const ext = entry.slice(dot).toLowerCase()
|
|
106
|
+
if (base.toLowerCase() === id.toLowerCase() && MIME[ext] && entry !== id + '.png') {
|
|
107
|
+
try {
|
|
108
|
+
fs.unlinkSync(path.join(dir, entry))
|
|
109
|
+
} catch {
|
|
110
|
+
/* 清理失败不阻塞:多一张旧图只是回退序靠前,不影响本次交付 */
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
res.statusCode = 200
|
|
116
|
+
res.setHeader('Content-Type', 'application/json')
|
|
117
|
+
res.setHeader('Cache-Control', 'no-store')
|
|
118
|
+
return res.end(
|
|
119
|
+
JSON.stringify({
|
|
120
|
+
ok: true,
|
|
121
|
+
id,
|
|
122
|
+
width: shot.width,
|
|
123
|
+
height: shot.height,
|
|
124
|
+
file: 'src/cards/' + id + '/' + id + '.png',
|
|
125
|
+
overwritten,
|
|
126
|
+
}),
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// GET ?probe=1:轻量探测「同名预览图是否已存在」——只 readdir、命中即返回,
|
|
131
|
+
// 不触发回退截图。供顶栏「截图」按钮在覆盖已有封面之前弹二次确认。
|
|
132
|
+
const q = new URL(req.url, 'http://localhost')
|
|
133
|
+
if (q.searchParams.get('probe') === '1') {
|
|
134
|
+
let found = null
|
|
135
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
136
|
+
const dot = entry.lastIndexOf('.')
|
|
137
|
+
if (dot < 0) continue
|
|
138
|
+
const base = entry.slice(0, dot)
|
|
139
|
+
const ext = entry.slice(dot).toLowerCase()
|
|
140
|
+
if (base.toLowerCase() === id.toLowerCase() && MIME[ext]) {
|
|
141
|
+
found = 'src/cards/' + id + '/' + entry
|
|
142
|
+
break
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
res.statusCode = 200
|
|
146
|
+
res.setHeader('Content-Type', 'application/json')
|
|
147
|
+
res.setHeader('Cache-Control', 'no-store')
|
|
148
|
+
return res.end(JSON.stringify({ exists: !!found, file: found }))
|
|
149
|
+
}
|
|
150
|
+
|
|
49
151
|
// 取与 id 同名的位图(大小写不敏感,扩展名见 MIME)
|
|
50
152
|
let file = null
|
|
51
153
|
for (const entry of fs.readdirSync(dir)) {
|
|
@@ -84,7 +186,6 @@ export function createCardImageMiddleware({ root }) {
|
|
|
84
186
|
|
|
85
187
|
// 无同名预览图:回退实时无头截图(复用 /__screenshot 渲染内核)。
|
|
86
188
|
// width 由查询参数决定(默认 390),与 /__screenshot 一致。
|
|
87
|
-
const q = new URL(req.url, 'http://localhost')
|
|
88
189
|
const width = parseInt(q.searchParams.get('width') || '390', 10) || 390
|
|
89
190
|
const shot = await takeScreenshot(id, width, 'png')
|
|
90
191
|
if (!shot || !shot.buffer) {
|
|
@@ -17,7 +17,8 @@ const VALIDATE_SCRIPT_REL = 'dsl-card-generator/scripts/validate_schema.py'
|
|
|
17
17
|
* - GET /__validate :全量校验,逐卡跑 Python 校验器,汇总返回
|
|
18
18
|
*
|
|
19
19
|
* 实现:子进程调用 `skills/dsl-card-generator/scripts/validate_schema.py --json`。
|
|
20
|
-
* Python 解释器解析顺序:`
|
|
20
|
+
* Python 解释器解析顺序:`DSL_CARD_PYTHON`(权威)→ `WORKBUDDY_PYTHON` → `DSL_PYTHON`
|
|
21
|
+
* → `PYTHON` → 探测 `python3` / `python` / `py`;都不存在时返回
|
|
21
22
|
* **503 {error:'python-not-found'}** 而非 500,让 Agent / CI 能区分「环境没装」和「卡片有问题」。
|
|
22
23
|
*
|
|
23
24
|
* @param {{ root: string }} opts
|
|
@@ -32,10 +33,20 @@ export function scriptPath(root) {
|
|
|
32
33
|
return resolveSkillPathOrProject(root, VALIDATE_SCRIPT_REL)
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* 解析可用的 python 解释器;找不到返回 null(导出供 MCP 复用)。
|
|
38
|
+
*
|
|
39
|
+
* 能力对齐 `skills/dsl-card-generator/scripts/lib/python.mjs`(权威实现):环境变量链
|
|
40
|
+
* 四个键 + 自动探测含 `py`。此前只认 `DSL_PYTHON` 且不含 `py` —— Windows 上 Python 从
|
|
41
|
+
* Store 安装时往往只有 `py` 启动器,会在这里被判成「环境没装 Python」并回 503,
|
|
42
|
+
* 而 skill 脚本侧其实是能跑的(同一台机器两种结论)。
|
|
43
|
+
*/
|
|
36
44
|
export function resolvePython() {
|
|
37
|
-
|
|
38
|
-
|
|
45
|
+
for (const key of ['DSL_CARD_PYTHON', 'WORKBUDDY_PYTHON', 'DSL_PYTHON', 'PYTHON']) {
|
|
46
|
+
const val = process.env[key]
|
|
47
|
+
if (val) return val
|
|
48
|
+
}
|
|
49
|
+
for (const cmd of ['python3', 'python', 'py']) {
|
|
39
50
|
try {
|
|
40
51
|
execFileSync(cmd, ['--version'], { stdio: 'ignore', timeout: 5000 })
|
|
41
52
|
return cmd
|
|
@@ -92,7 +103,7 @@ export function createValidateMiddleware({ root }) {
|
|
|
92
103
|
res.setHeader('Content-Type', 'application/json')
|
|
93
104
|
return res.end(JSON.stringify({
|
|
94
105
|
error: 'python-not-found',
|
|
95
|
-
message: '未找到 python3/python,请在 PATH 中安装 Python 或设置
|
|
106
|
+
message: '未找到 python3/python/py,请在 PATH 中安装 Python 或设置 DSL_CARD_PYTHON 环境变量',
|
|
96
107
|
}))
|
|
97
108
|
}
|
|
98
109
|
|
|
@@ -12,6 +12,25 @@ import { getBrowser, serialize, openCardScreen, MAX_QUEUED } from './browser.js'
|
|
|
12
12
|
* 浏览器单例/页面加载逻辑已抽到 ./browser.js,与 /__bbox 共用同一实例。
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* 压掉编辑器壳层的悬浮物(AI FAB / toast / 抽屉面板 / 右键菜单等 position:fixed 元素)。
|
|
17
|
+
* 元素截图(ElementHandle.screenshot)截的是页面合成结果:任何叠在目标区域上的
|
|
18
|
+
* fixed 悬浮物都会被一起画进去 —— 「封面截图把 ai-fab 圆钮截进去」事故的根因。
|
|
19
|
+
* 按 computed position 判定而非类名清单:悬浮组件会不断增加,清单式每加一个就漏一个。
|
|
20
|
+
* 卡片自身(.ce_root 子树内)的 fixed 定位属于卡片设计,保留。页面随后即 close,无需恢复。
|
|
21
|
+
* (导出仅为测试可断言其 DOM 语义。)
|
|
22
|
+
*/
|
|
23
|
+
export async function hideFloatingOverlays(page) {
|
|
24
|
+
await page.evaluate(() => {
|
|
25
|
+
document.querySelectorAll('body *').forEach((el) => {
|
|
26
|
+
if (el.closest('.ce_root')) return
|
|
27
|
+
if (getComputedStyle(el).position === 'fixed') {
|
|
28
|
+
el.style.setProperty('visibility', 'hidden', 'important')
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
15
34
|
/**
|
|
16
35
|
* 对单卡截图。纯函数、无 HTTP。任何失败都返回 { error, code, message, ... } 而非抛出。
|
|
17
36
|
* @param {string} id 卡片 id
|
|
@@ -42,6 +61,7 @@ export async function takeScreenshot(id, width, format) {
|
|
|
42
61
|
if (!screen) {
|
|
43
62
|
return { error: 'capture-target-not-found', code: 500, message: '.phone__screen 元素未找到' }
|
|
44
63
|
}
|
|
64
|
+
await hideFloatingOverlays(page)
|
|
45
65
|
|
|
46
66
|
const buf = await screen.screenshot({ type: 'png' })
|
|
47
67
|
if (format === 'json') {
|
|
@@ -76,6 +96,80 @@ export async function takeScreenshot(id, width, format) {
|
|
|
76
96
|
}
|
|
77
97
|
}
|
|
78
98
|
|
|
99
|
+
/**
|
|
100
|
+
* 生成卡片封面预览图:透明底(不含编辑器外壳背景色)、尺寸 = 卡片实际渲染尺寸 1:1。
|
|
101
|
+
* 与 takeScreenshot 的三点差异:
|
|
102
|
+
* - deviceScaleFactor:1 —— 截图像素 = CSS 像素(takeScreenshot 是 2 倍视网膜);
|
|
103
|
+
* - 截图目标是卡片根元素 .ce_root —— .phone__screen 是固定高度的手机屏滚动容器,
|
|
104
|
+
* 直截会带大片空白;.ce_root 高度即内容实际高度;
|
|
105
|
+
* - omitBackground + 透明化外壳底色(chrome=off 下手机屏固定挂 --bg-white),
|
|
106
|
+
* 产出透明底 PNG;卡片自身根节点的背景 / 渐变属于卡片设计,保留。
|
|
107
|
+
* 纯函数、无 HTTP、不写盘:返回 { buffer, width, height },落盘由调用方(/__card-image POST)负责。
|
|
108
|
+
* 任何失败返回 { error, code, message, fix? } 结构化结果,不抛出(与 takeScreenshot 同约定)。
|
|
109
|
+
*/
|
|
110
|
+
export async function generateCardCover(id, { width: cardWidth = 375 } = {}) {
|
|
111
|
+
let browser
|
|
112
|
+
try {
|
|
113
|
+
browser = await getBrowser()
|
|
114
|
+
} catch (e) {
|
|
115
|
+
return {
|
|
116
|
+
error: 'browser-launch-failed',
|
|
117
|
+
code: 503,
|
|
118
|
+
message: String((e && e.message) || e),
|
|
119
|
+
fix: '系统 Chrome 未找到或未启动。请安装 Google Chrome,并确保 playwright 以 channel:"chrome" 启动(本端点固定使用该通道)。',
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let context
|
|
124
|
+
try {
|
|
125
|
+
context = await browser.newContext({
|
|
126
|
+
viewport: { width: cardWidth + 80, height: 900 },
|
|
127
|
+
deviceScaleFactor: 1,
|
|
128
|
+
})
|
|
129
|
+
const { page, screen } = await openCardScreen(context, id)
|
|
130
|
+
try {
|
|
131
|
+
if (!screen) {
|
|
132
|
+
return { error: 'capture-target-not-found', code: 500, message: '.phone__screen 元素未找到' }
|
|
133
|
+
}
|
|
134
|
+
// 压掉编辑器外壳的底色与投影:omitBackground 只能去掉「页面默认白底」,管不到
|
|
135
|
+
// 元素自己的样式 —— .app(深色)/.phone(径向渐变)/.phone__screen--bg-white 这些
|
|
136
|
+
// 祖先/外壳会把不透明底画在卡片后面,整条祖先链必须一并透明化。
|
|
137
|
+
await page.addStyleTag({
|
|
138
|
+
content:
|
|
139
|
+
'html,body,.app,.app__stage,.phone,.phone__frame,.phone__scaler,.phone-viewport,.phone__screen' +
|
|
140
|
+
'{background:transparent!important;background-image:none!important;box-shadow:none!important}',
|
|
141
|
+
})
|
|
142
|
+
await hideFloatingOverlays(page)
|
|
143
|
+
const cardEl = await page.$('.phone__screen .ce_root')
|
|
144
|
+
if (!cardEl) {
|
|
145
|
+
return { error: 'card-root-not-found', code: 500, message: '.ce_root 卡片根元素未找到' }
|
|
146
|
+
}
|
|
147
|
+
const buf = await cardEl.screenshot({ type: 'png', omitBackground: true })
|
|
148
|
+
const box = await cardEl.boundingBox()
|
|
149
|
+
return {
|
|
150
|
+
buffer: buf,
|
|
151
|
+
width: box ? Math.round(box.width) : null,
|
|
152
|
+
height: box ? Math.round(box.height) : null,
|
|
153
|
+
}
|
|
154
|
+
} finally {
|
|
155
|
+
await page.close().catch(() => {})
|
|
156
|
+
}
|
|
157
|
+
} catch (e) {
|
|
158
|
+
// 导航超时 / data-card-ready 未出现 / 截图异常:转成结构化错误,绝不向上抛
|
|
159
|
+
const isTimeout = /Timeout\s*\d+ms exceeded/.test(String((e && e.message) || e))
|
|
160
|
+
return {
|
|
161
|
+
error: isTimeout ? 'render-wait-timeout' : 'screenshot-failed',
|
|
162
|
+
code: isTimeout ? 504 : 500,
|
|
163
|
+
message: String((e && e.message) || e),
|
|
164
|
+
fix: isTimeout
|
|
165
|
+
? '卡片首帧未在限定时间内挂载(data-card-ready 未出现)。检查该卡是否在 chrome=off 模式下渲染报错,或卡片依赖真实运行时数据(customAction/RPC)而无法在纯渲染模式落定。'
|
|
166
|
+
: undefined,
|
|
167
|
+
}
|
|
168
|
+
} finally {
|
|
169
|
+
if (context) await context.close().catch(() => {})
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
79
173
|
export function createScreenshotMiddleware({ root }) {
|
|
80
174
|
return async function screenshotMiddleware(req, res, next) {
|
|
81
175
|
const pathname = decodeURIComponent((req.url || '').split('?')[0])
|
|
@@ -49,7 +49,7 @@ DSL_CARD_SLICES_DIR=<training-run>/slices
|
|
|
49
49
|
5. 复制 schema 实际引用的 slices 到当前 run 的隔离资源目录。
|
|
50
50
|
6. 执行 Python 结构校验、Node lint、独立 Chrome 渲染、重复内容检测和像素差异计算。
|
|
51
51
|
7. 聚合 Round 报告(`run_round.mjs` 收尾自动调用 `score_round.mjs`),按主失败签名与失败类出直方图。
|
|
52
|
-
8. `plan_next_round.mjs` 把「归因 → 聚类 → 焦点集 → 门禁判定 → 收敛信号 → lesson 草稿」一次算完,产出 `next-round-plan.json` 与 `lesson-drafts.jsonl`(`status=candidate
|
|
52
|
+
8. **轮后自动落盘**(`run_round.mjs` 收尾自动执行,`--no-sink` 跳过):`plan_next_round.mjs` 把「归因 → 聚类 → 焦点集 → 门禁判定 → 收敛信号 → lesson 草稿」一次算完,产出 `next-round-plan.json` 与 `lesson-drafts.jsonl`(`status=candidate`);lesson 草稿按 signature 去重后自动追加到 `training/lessons/lessons.jsonl`;最后写 `TRAINING-SUMMARY.md`(人读训练总结含门禁结果表)。Agent 按聚类逐例复核 `designImage + render.png + spec.json`,把失败归入 PARSER / DSL / STYLE / DATA / INTERACTION / ASSET / EVALUATOR,并只推广通用规则。
|
|
53
53
|
|
|
54
54
|
## 高效迭代闭环(r172 起;r263 补自动归因与增量轮补齐)
|
|
55
55
|
|
|
@@ -62,8 +62,9 @@ DSL_CARD_SLICES_DIR=<training-run>/slices
|
|
|
62
62
|
(多案例共用一套渲染会话):门禁未达标维度(按缺口降序)、失败类归因、掩膜覆盖率、上游聚焦门、
|
|
63
63
|
side-by-side、像素热区、元素偏差表、`__prov` 源码行溯源、失败类建议。先看偏差表与热区,
|
|
64
64
|
再决定是否动生成器。
|
|
65
|
-
3. **修复**:改动
|
|
66
|
-
|
|
65
|
+
3. **修复**:改动 v3 生成器的通用几何规则——`scripts/lib/v3_extract.mjs`(识别)/ `v3_shared.mjs`
|
|
66
|
+
(共享 helper)/ `v3_assemble.mjs`(组装),2026-09-21 起自 `spec_to_schema_v3.mjs`(现为 CLI 编排壳)
|
|
67
|
+
拆出——以及 `vision.py` / `parse_meaxure.py`(禁业务词硬编码)。
|
|
67
68
|
4. **增量验证**:`run_round.mjs --round <新轮> --focus training/runs/<上轮> --inherit training/runs/<上轮>`
|
|
68
69
|
只重跑焦点集(失败例 + 低分通过例),其余案例报告与截图从上一轮继承 → 新轮仍是**完整可比轮**
|
|
69
70
|
(passRate 分母正确、`round_diff` 不会把未重跑案例算成 removed)。只重跑失败例用
|
|
@@ -102,7 +103,7 @@ DSL_CARD_SLICES_DIR=<training-run>/slices
|
|
|
102
103
|
|
|
103
104
|
- `annotationCompleteness`:画板内叶子标注是否被完整归属到聚焦卡。
|
|
104
105
|
- `textRetention`:卡片内文案是否被完整保留。
|
|
105
|
-
- `structuralFocus
|
|
106
|
+
- `structuralFocus`:候选是否具备卡片级结构,而不是气泡、按钮条或空壳。**图像卡通道(r264 起)**:整卡被设计稿导成一张大切图时(解析器兜底产 1 张卡、卡内只有该 slice),`sliceAreaRatio ≥ 0.6` 即视为结构成立,按正常像素/尺寸门评分。
|
|
106
107
|
- `trainingEligible`:弹窗、toast、底部抽屉、纯文本板(`artboardType=text_only`,整板只有对话/问答/提示文字)必须为 false。
|
|
107
108
|
|
|
108
109
|
任一门失败优先归因 `PARSER`,修复目标是 `vision.py` / `parse_meaxure.py` / `boundary.json` 的通用几何规则,不允许用业务名救某个样本。
|
|
@@ -177,7 +178,7 @@ DSL_CARD_SLICES_DIR=<training-run>/slices
|
|
|
177
178
|
> 基线载体是**冻结锚点轮目录**(含 `reports/` 全量证据)+ **入库压缩锚点** `training/anchors/<round>.json`
|
|
178
179
|
> (P0-2 起:干净克隆与 CI 上唯一的锚点载体,`round_diff.mjs` / `plan_next_round.mjs` 都直接吃它)。
|
|
179
180
|
> 本 Skill 目录下的历史 `baselines/v3-r0*.json` 范式已废弃并删除(指标快照改由 summary.json + 压缩锚点承载)。
|
|
180
|
-
> 晋级 =
|
|
181
|
+
> 晋级 = 新轮成为下一冻结锚点候选,通用规则回灌 `training/lessons/lessons.jsonl`(`sink_rules.mjs`),并重新生成压缩锚点入库。
|
|
181
182
|
|
|
182
183
|
### 泛化盲测门(generalization gate)
|
|
183
184
|
|
|
@@ -196,7 +197,7 @@ DSL_CARD_SLICES_DIR=<training-run>/slices
|
|
|
196
197
|
**固定跨批次晋级盲测集**(解决单批次 eval/holdout 只有 2 例、凑不出 24 例的问题):
|
|
197
198
|
用 `build_sample.mjs` 从 v6 + v7 全量池各抽代表,合并成 `training/datasets/blindset/train-set.jsonl`
|
|
198
199
|
(≥24 例,结构聚类去重后覆盖列表/表单/结果/媒体/空状态),晋级轮一律用它盲测,
|
|
199
|
-
并在 `training/
|
|
200
|
+
并在 `training/lessons/lessons.jsonl` 记录该盲测集的 provenance 与轮号。盲测集**不参与任何修复迭代**。
|
|
200
201
|
### 收敛检测与早停
|
|
201
202
|
|
|
202
203
|
当出现以下任一信号时,**停止脚本微调,转而修订 Skill 文档规则或范式**:
|
|
@@ -237,7 +238,7 @@ r198 记录的共享 40 例均值为 fidelity 96.4% / pixel 3.8% / position 95.7
|
|
|
237
238
|
|
|
238
239
|
- v5(转账二批次 17 代表)r199 = 14/17、avgFid 95.8%、avgPix 4.0%;
|
|
239
240
|
- v6(存款+理财 30 代表)r200 = 4/30、avgFid 81.7%——新布局族缺口,仍是泛化主战场
|
|
240
|
-
(见 `training/
|
|
241
|
+
(见 `training/datasets/v6/dataset-summary.json` 与本节指标水位);
|
|
241
242
|
- v7(焦点集)r243→r262 逐例收敛:`945b562caad1f0f4` 散点带/段落壳/评分仪表盘通道落地后
|
|
242
243
|
pix 0.1348→0.0681、pos 0.532→0.905、comp 0.852;`86ae6d41957935d8` 渐变壳绑定修正后 pix 0.055。
|
|
243
244
|
对应规则见 `dsl-card-generator/references/generation_pitfalls.md §21-24` 与 lessons `r255-l01..l05` / `r261-l01`。
|
|
@@ -271,7 +272,7 @@ r198 记录的共享 40 例均值为 fidelity 96.4% / pixel 3.8% / position 95.7
|
|
|
271
272
|
3. 修复优先级:可见媒体与重影 → 语义顺序与纵向节奏 → 背景/圆角/留白 → 未见样本泛化。
|
|
272
273
|
4. 缺失/状态错切图优先画板裁切(`cropSlice`,含宽条切图与纯矢量 logo 组;矩形内有文本叠加或背景级大矩形禁裁),裁切不可用才纯色近似并标记缺陷;弥散/光晕类纹理特事特办,计入局部还原度豁免。
|
|
273
274
|
5. 焦点样本通过后必须复跑冻结锚点全量;每 3 轮做不少于 24 例未见样本盲测(固定跨批次盲测集见上文)。
|
|
274
|
-
6. 晋级门、焦点 ID
|
|
275
|
+
6. 晋级门、焦点 ID 和产物路径以本轮 `reports/summary.json` 与 `training/anchors/<最新>.json` 为准。
|
|
275
276
|
7. 新布局通道(如散点带 §21、段落壳归属 §22、行锚点/尾随切片 §23)落盘生成器后,同步 lessons.jsonl + generation_pitfalls/quickref/previewer_contract,并复跑冻结锚点确认 0 回归;通道判据只认几何/样式结构,不认业务词。
|
|
276
277
|
|
|
277
278
|
训练清单可记录样本 ID 和画板名作为证据;运行时代码、Skill 判定和校验器不得引用业务词、画板名或样本专属文案。
|
|
@@ -290,7 +291,7 @@ r198 记录的共享 40 例均值为 fidelity 96.4% / pixel 3.8% / position 95.7
|
|
|
290
291
|
7. 通用规则写入 `training/lessons/lessons.jsonl`(新格式 `{lessonId, signature, failureClass, evidenceCases,
|
|
291
292
|
rule, targetFiles, status, round}`;`plan_next_round.mjs` 产的 `lesson-drafts.jsonl` 是待补 rule 的草稿),
|
|
292
293
|
并同步到对应 Skill 的 `SKILL.md` 或 `references/`;业务词、画板名和样本专属文案不得进入规则。
|
|
293
|
-
8.
|
|
294
|
+
8. 通过后以新轮目录作为下一冻结锚点候选,回灌 lessons 并重生成压缩锚点入库
|
|
294
295
|
(`training/anchors/<round>.json`);未通过的候选规则保持 `candidate`,不修改正式 Skill 行为。
|
|
295
296
|
9. `npm run ci` 全绿(9 硬门禁:card-check / smoke-preview / verify-event-model / verify-model-expose /
|
|
296
297
|
magic-values / regress-data / unit-tests / mcp-doc-sync / lint-ratchet;另有 regress-fidelity、lint 两道软门禁):评分尺子改动被
|
|
@@ -378,6 +379,9 @@ node skills/card-skill-trainer/scripts/run_round.mjs \
|
|
|
378
379
|
- `--resume`:续跑既有轮目录(已有报告的案例跳过,渲染会话懒启动);进程被 kill 或渲染假死后不必整轮重来。
|
|
379
380
|
- `--no-correct`:禁用布局校正器,裸测生成器真实水平(里程碑对照用)。
|
|
380
381
|
- `--no-summary`:跳过收尾自动汇总(默认自动调 `score_round.mjs --quiet`,直接打出 pass/avgFid/roundGate)。
|
|
382
|
+
- `--concurrency N`:并行渲染会话数(默认 2,上限 4;`1` 即旧串行行为)。每个 worker 持独立
|
|
383
|
+
Chrome + HTTP server(随机端口 / 唯一 userDataDir,多实例安全),worker 池按案例分派;
|
|
384
|
+
单案例异常隔离不变。吞吐敏感的全量轮保持默认,内存受限环境降为 1。
|
|
381
385
|
- `--splits train,dev`:限定分区(迭代纪律见上文)。
|
|
382
386
|
- `--only` / `--only-failed` / `--focus` 三者互斥,同时传会直接报错而不是静默取其一。
|
|
383
387
|
|
|
@@ -459,7 +463,7 @@ python3 training/tools/split_report.py <runDir> [topN] # 按 split 聚合指
|
|
|
459
463
|
|
|
460
464
|
> `training/datasets/`、`training/runs/` 仍被 gitignore(体积 + 源稿版权)。数据集可复现性靠
|
|
461
465
|
> `dataset-summary.json` 的 `provenance`(sourceRoot / sourceNames / toolchainRev / 解析器参数)
|
|
462
|
-
> 与 `training/
|
|
466
|
+
> 与 `training/lessons/lessons.jsonl` 的轮次记录;锚点回归靠冻结轮目录(`training/runs/r176`)**或入库压缩锚点**
|
|
463
467
|
> (`training/anchors/r176.json`,干净克隆唯一可用载体)。已入库的训练设施还有
|
|
464
468
|
> `training/tools/*`、`training/reports/font-metrics.json`、`training/fixtures/ui-drafts/`(保真度软门禁兜底稿)。
|
|
465
469
|
|
|
@@ -471,13 +475,18 @@ training/runs/<round>/
|
|
|
471
475
|
slices/
|
|
472
476
|
reports/
|
|
473
477
|
<caseId>.json ← 含 gateFailures / failureSignature / failureClass / exclusionCoverage
|
|
474
|
-
与 layoutCorrections / correctorRejected
|
|
478
|
+
与 layoutCorrections / correctorRejected 校正明细;
|
|
479
|
+
telemetry 字段:timings(阶段耗时 ms)/ stageErrors(阶段化错误)/
|
|
480
|
+
usage(生成链路 LLM usage,预留)/ reviewTokensEst(归因证据包 token 估算)
|
|
475
481
|
<caseId>.render.png ← 最终渲染截图
|
|
476
482
|
<caseId>.design.png ← 设计稿参考图
|
|
477
483
|
<caseId>.side.png ← diagnose_case 并排对比图(按需生成)
|
|
478
484
|
summary.json ← 含 roundGate / roundGatePassed / correctorCoverage /
|
|
479
|
-
gateFailureHistogram / failureClasses / highMaskCases
|
|
480
|
-
|
|
485
|
+
gateFailureHistogram / failureClasses / highMaskCases /
|
|
486
|
+
avgCaseMs / p50CaseMs / p95CaseMs / telemetry(速度·错误·token 汇总)
|
|
487
|
+
summary.md ← 含晋级门禁表格、归因直方图、「速度与消耗」段与截图对比图廊
|
|
488
|
+
telemetry.json ← 轮级 telemetry:墙钟 / 并发 / 阶段耗时聚合 / 渲染次数与会话重建 /
|
|
489
|
+
阶段错误直方图 / LLM usage / 归因证据包 token 估算(run_round 收尾落盘)
|
|
481
490
|
review-queue.jsonl
|
|
482
491
|
semantic-review.jsonl
|
|
483
492
|
inherited.json ← --inherit 轮:继承来源与案例清单
|
|
@@ -487,4 +496,50 @@ training/runs/<round>/
|
|
|
487
496
|
diagnosis.json ← diagnose_case --out:批量诊断的机器可读证据包
|
|
488
497
|
```
|
|
489
498
|
|
|
499
|
+
### 效率观测(telemetry,2026-09-25 起)
|
|
500
|
+
|
|
501
|
+
训练的三类效率信号由 `run_round.mjs` 自动采集、`score_round.mjs` 聚合进 summary(口径实现:
|
|
502
|
+
`lib/telemetry.mjs`,直测 `tools/tests/trainer-telemetry.test.mjs`):
|
|
503
|
+
|
|
504
|
+
1. **耗时**:每案例按阶段计时(parse / upstream / generate / autofix / slices / validate / lint /
|
|
505
|
+
render / reRender / pixel / score),轮级 `telemetry.json` 出阶段均值与墙钟吞吐
|
|
506
|
+
(`casesPerMinute`)。瓶颈定位:`render` 占比高 → 提 `--concurrency`;`reRender` 次数多 →
|
|
507
|
+
校正器覆盖率高,按「校正器纪律」沉淀规则;`pixel` 占比高 → 检查掩膜数量与图像尺寸。
|
|
508
|
+
2. **错误**:阶段化登记(`stageErrors`,validate/lint/render/case 分开计次),轮级直方图区分
|
|
509
|
+
「工具链坏了」与「生成质量差」——前者修环境,后者走归因闭环。
|
|
510
|
+
3. **token**:v3 生成器是纯规则引擎(0 LLM 调用),`usage` 字段为生成链路 LLM 化后预留(自动
|
|
511
|
+
累计 `meta.usage`);训练闭环中真实发生的消耗是**失败案例的 Agent 在环多模态归因**——
|
|
512
|
+
`reviewTokensEst` 按「双图面积/750 + spec 文本/4 chars」估算归因证据包 input token(预算
|
|
513
|
+
参考,不进门禁)。降 token 的正道 = 提高还原度减少失败案例,而不是压缩证据包。
|
|
514
|
+
|
|
515
|
+
> 口径只作效率观测,不参与 PASS_GATE / ROUND_GATE 任何判定;旧轮次报告缺字段时对应聚合自动跳过。
|
|
516
|
+
|
|
490
517
|
`summary.md` 与 `next-round-plan.json` 是需要周期性查看的两份产物;如果所有门禁通过,可以完全不看。
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
### Input component was invisible to text-component fidelity(r038n 训练回灌)
|
|
521
|
+
> lesson: `LESSON-20260826-043` status: `promoted`
|
|
522
|
+
|
|
523
|
+
Input 是可编辑文本承载组件,组件相似度应计入 text 与 controls 两个维度;不能只把 className=Text 当作文本组件,否则会反向奖励 display-text 而惩罚正确 Input 语义。
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
### ListLayout template reuse was scored as missing components(r039n 训练回灌)
|
|
528
|
+
> lesson: `LESSON-20260826-046` status: `promoted`
|
|
529
|
+
|
|
530
|
+
组件评分必须按 ListLayout 数据项数展开模板内 Text/Image/Input/Button 计数;模板复用是正确实现,不能因 schema 只保存一份模板而判为组件丢失。
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
### card focus needs visual, annotation and agent semantic cross(r039n 训练回灌)
|
|
535
|
+
> lesson: `LESSON-20260826-047` status: `promoted`
|
|
536
|
+
|
|
537
|
+
卡片候选由截图视觉、标注几何和 Agent 语义三方交叉验证;完整业务容器才可接受,对话气泡、单按钮、弹窗、toast 和底部抽屉必须拒绝或跳过。规则只使用通用结构信号,禁止业务词硬编码。
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
### pixel-diff-over-8%(seq4 训练回灌)
|
|
542
|
+
> lesson: `seq4-l06` status: `promoted`
|
|
543
|
+
|
|
544
|
+
像素掩膜门必须环境感知:探针实测「回退栈宽/标注宽」fontMaskWiden > 1.06 即该字体在本机渲染环境不可得(PingFang SC 在 Windows 同样回退合成粗体/换族),家族名豁免会让最大像素热带(文本字形带 16-36%/带)漏掩,把像素门顶在 8% 门外并误逼生成器改字重/字号扭曲 spec;探针缺失回退家族名判据。掩膜只进像素 ratio 通道(pixelOnlyRects),structure/color 剖面仍只用硬排除掩膜,否则大掩膜会把 structure 打穿(实测 87→67)。
|
|
545
|
+
|