cardbuddy 0.14.3 → 0.16.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.
Files changed (108) hide show
  1. package/dist/cli.js +0 -0
  2. package/dist/preview-editor/assets/index-BkbBxDTT.css +1 -0
  3. package/dist/preview-editor/assets/index-Bro07DTa.js +313 -0
  4. package/dist/preview-editor/index.html +2 -2
  5. package/dist/standalone.js +3 -0
  6. package/dist/vendored/card-editor/runtime/adapter.js +2 -2
  7. package/dist/vendored/mcp/client.mjs +9 -2
  8. package/dist/vendored/server/core/ai-chat.js +1702 -134
  9. package/dist/vendored/server/core/browser.js +12 -3
  10. package/dist/vendored/server/core/cardFiles.js +3 -0
  11. package/dist/vendored/server/core/cardHistory.js +1 -0
  12. package/dist/vendored/server/core/cardImportMeta.js +7 -6
  13. package/dist/vendored/server/core/cardOpenFolder.js +1 -0
  14. package/dist/vendored/server/core/cardSave.js +1 -0
  15. package/dist/vendored/server/core/cardSync.js +3 -0
  16. package/dist/vendored/server/core/cardValidate.js +16 -5
  17. package/dist/vendored/server/core/codeBridge.js +5 -1
  18. package/dist/vendored/server/core/logStream.js +1 -0
  19. package/dist/vendored/server/core/sliceStatic.js +24 -4
  20. package/dist/vendored/server/core/sliceUpload.js +1 -0
  21. package/dist/vendored/server/core/turnJournal.js +6 -0
  22. package/dist/vendored/skills/card-skill-trainer/SKILL.md +257 -201
  23. package/dist/vendored/skills/card-skill-trainer/scripts/diagnose_case.mjs +14 -82
  24. package/dist/vendored/skills/card-skill-trainer/scripts/lib/diagnosis.mjs +207 -0
  25. package/dist/vendored/skills/card-skill-trainer/scripts/lib/layout-correction-sim.mjs +62 -0
  26. package/dist/vendored/skills/card-skill-trainer/scripts/lib/paradigm-router.mjs +76 -0
  27. package/dist/vendored/skills/card-skill-trainer/scripts/lib/pixel-worker-runner.mjs +31 -0
  28. package/dist/vendored/skills/card-skill-trainer/scripts/lib/pixel-worker.mjs +138 -0
  29. package/dist/vendored/skills/card-skill-trainer/scripts/lib/scoring.mjs +24 -20
  30. package/dist/vendored/skills/card-skill-trainer/scripts/lib/telemetry.mjs +46 -0
  31. package/dist/vendored/skills/card-skill-trainer/scripts/plan_next_round.mjs +4 -1
  32. package/dist/vendored/skills/card-skill-trainer/scripts/run_round.mjs +279 -90
  33. package/dist/vendored/skills/card-skill-trainer/scripts/score_round.mjs +74 -2
  34. package/dist/vendored/skills/card-skill-trainer/scripts/sink_rules.mjs +127 -0
  35. package/dist/vendored/skills/dsl-card-ai/SKILL.md +1 -1
  36. package/dist/vendored/skills/dsl-card-generator/SKILL.md +208 -380
  37. package/dist/vendored/skills/dsl-card-generator/references/dsl_structure.md +1 -15
  38. package/dist/vendored/skills/dsl-card-generator/references/expression_engine.md +11 -20
  39. package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +233 -61
  40. package/dist/vendored/skills/dsl-card-generator/references/ir-format.md +202 -4
  41. package/dist/vendored/skills/dsl-card-generator/references/iteration_protocol.md +87 -0
  42. package/dist/vendored/skills/dsl-card-generator/references/previewer_contract.md +1 -1
  43. package/dist/vendored/skills/dsl-card-generator/references/quickref.md +42 -33
  44. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/title-underline-overlay.json +59 -11
  45. package/dist/vendored/skills/dsl-card-generator/references/recipes.md +44 -8
  46. package/dist/vendored/skills/dsl-card-generator/references/rules.md +26 -6
  47. package/dist/vendored/skills/dsl-card-generator/references/scripts_reference.md +54 -0
  48. package/dist/vendored/skills/dsl-card-generator/references/semantic_adjudication.md +23 -0
  49. package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +6 -6
  50. package/dist/vendored/skills/dsl-card-generator/references/spec_to_schema_patterns.md +33 -4
  51. package/dist/vendored/skills/dsl-card-generator/references/ui_fidelity_rules.md +13 -1
  52. package/dist/vendored/skills/dsl-card-generator/scripts/add_component.mjs +5 -14
  53. package/dist/vendored/skills/dsl-card-generator/scripts/autofix_schema.py +133 -2
  54. package/dist/vendored/skills/dsl-card-generator/scripts/check_fidelity.mjs +226 -19
  55. package/dist/vendored/skills/dsl-card-generator/scripts/check_magic_values.mjs +6 -1
  56. package/dist/vendored/skills/dsl-card-generator/scripts/check_topology.py +0 -35
  57. package/dist/vendored/skills/dsl-card-generator/scripts/crop_slices.mjs +92 -6
  58. package/dist/vendored/skills/dsl-card-generator/scripts/expand_ir.mjs +277 -27
  59. package/dist/vendored/skills/dsl-card-generator/scripts/gate.mjs +147 -0
  60. package/dist/vendored/skills/dsl-card-generator/scripts/generate_cards.mjs +12 -8
  61. package/dist/vendored/skills/dsl-card-generator/scripts/ir_merge.mjs +288 -0
  62. package/dist/vendored/skills/dsl-card-generator/scripts/ir_restore.mjs +175 -0
  63. package/dist/vendored/skills/dsl-card-generator/scripts/iterate.mjs +1015 -54
  64. package/dist/vendored/skills/dsl-card-generator/scripts/lib/component_contract.mjs +315 -0
  65. package/dist/vendored/skills/dsl-card-generator/scripts/lib/geometry_audit.mjs +131 -0
  66. package/dist/vendored/skills/dsl-card-generator/scripts/lib/layout-tokens.mjs +1 -1
  67. package/dist/vendored/skills/dsl-card-generator/scripts/lib/layout_rules.mjs +160 -0
  68. package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.mjs +23 -0
  69. package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.py +0 -37
  70. package/dist/vendored/skills/dsl-card-generator/scripts/lib/python.mjs +99 -0
  71. package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/adaptive-width.mjs +105 -0
  72. package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/bindings.mjs +260 -0
  73. package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/expr.mjs +52 -0
  74. package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/image.mjs +96 -0
  75. package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/registry.mjs +166 -0
  76. package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/structure.mjs +127 -0
  77. package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/walk.mjs +61 -0
  78. package/dist/vendored/skills/dsl-card-generator/scripts/lib/scope_alias.mjs +125 -0
  79. package/dist/vendored/skills/dsl-card-generator/scripts/lib/slicePath.mjs +17 -5
  80. package/dist/vendored/skills/dsl-card-generator/scripts/lib/slice_dedupe.mjs +102 -0
  81. package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_assemble.mjs +5727 -0
  82. package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_extract.mjs +4214 -0
  83. package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_shared.mjs +1110 -0
  84. package/dist/vendored/skills/dsl-card-generator/scripts/lint_card.mjs +322 -71
  85. package/dist/vendored/skills/dsl-card-generator/scripts/preflight.mjs +87 -43
  86. package/dist/vendored/skills/dsl-card-generator/scripts/readiness.mjs +634 -0
  87. package/dist/vendored/skills/dsl-card-generator/scripts/register_card_template.mjs +5 -8
  88. package/dist/vendored/skills/dsl-card-generator/scripts/render_card.mjs +91 -43
  89. package/dist/vendored/skills/dsl-card-generator/scripts/route_input.mjs +7 -11
  90. package/dist/vendored/skills/dsl-card-generator/scripts/spec_audit.mjs +272 -46
  91. package/dist/vendored/skills/dsl-card-generator/scripts/spec_to_schema_v3.mjs +43 -10499
  92. package/dist/vendored/skills/dsl-card-generator/scripts/v3_regression.mjs +188 -0
  93. package/dist/vendored/skills/dsl-card-generator/scripts/validate_schema.py +23 -15
  94. package/dist/vendored/skills/shared/exit-codes.md +79 -0
  95. package/dist/vendored/skills/shared/spec-contract.md +118 -0
  96. package/dist/vendored/skills/sketch-spec-to-card/SKILL.md +43 -20
  97. package/dist/vendored/skills/sketch-spec-to-card/references/spec-schema.md +4 -4
  98. package/dist/vendored/skills/sketch-spec-to-card/scripts/build_prompt.py +47 -9
  99. package/dist/vendored/skills/sketch-spec-to-card/scripts/cluster_states.py +14 -25
  100. package/dist/vendored/skills/sketch-spec-to-card/scripts/parse_meaxure.py +546 -91
  101. package/dist/vendored/skills/sketch-spec-to-card/scripts/png_utils.py +51 -5
  102. package/dist/vendored/skills/sketch-spec-to-card/scripts/test_text_only_artboard.py +217 -0
  103. package/dist/vendored/skills/sketch-spec-to-card/scripts/vision.py +64 -34
  104. package/package.json +1 -1
  105. package/dist/preview-editor/assets/index-Cm0USikc.js +0 -307
  106. package/dist/preview-editor/assets/index-cbTCHp8J.css +0 -1
  107. package/dist/vendored/skills/card-skill-trainer/scripts/test_missing_slice.py +0 -122
  108. package/dist/vendored/skills/dsl-card-generator/scripts/analyze_component_usage.mjs +0 -153
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * preflight.mjs — 生成前/后一键机械检查(替代 AI 手动过 Pre-flight Checklist)。
3
+ * preflight.mjs — 生成前/后一键机械检查(把 AI 需手动过的机械项一次性跑完)。
4
4
  *
5
- * 检查项(对应 quickref.md Pre-flight Checklist 12 条中可机械化的 10 条):
5
+ * 检查项(共 14 项;语义清单见 SKILL.md §8 Workflow A 步骤 4):
6
6
  * 1. 元素清单:spec text/slice/shape 数 vs schema 节点数(R29)
7
7
  * 2. 组件白名单:className ∈ component-meta.json(R28)
8
- * 3. Image itemValueFrom:静态路径必须 "2",变量 "1"(R25)
8
+ * 3. Image itemValueFrom:静态路径必须 "2",变量 "1"(双向都拦:`"0"` 常量放路径、
9
+ * `"1"` 变量放非 `$` 值、`"2"` 图片槽放 `$` 表达式——末者会被原样透传成 url($icon))(R25)
9
10
  * 4. Image rect.height:必须有显式高度(R32,否则被 aspect-ratio:16/9 压缩)
10
11
  * 5. 切图文件名:含空格/特殊字符必须重命名(R31)
11
12
  * 6. Icon eventaction:嵌套格式 event.eventKey(R30)
@@ -13,6 +14,12 @@
13
14
  * 8. 分割线用 Divider:禁止 Row 模拟分割线(R28)
14
15
  * 9. 卡片根样式全量提取:bg shape 有 border 但 schema 根容器未设(R33)
15
16
  * 10. 空样式 itemId:style 子项 itemId 为空串 → 运行时静默丢弃
17
+ * 11. 切图引用含 URL 保留字符(`# ? % & +`)→ 浏览器截断请求、图片静默不显示(R31b)
18
+ * 12. 横向流父容器里的容器子节点未写 rect.width + flexShrink:0 → 默认 100% 吃掉整行(R22b)
19
+ * 13. 切图引用在 src/slices 里没有对应文件 → 渲染 404、图空白零报错(R31c)
20
+ * 14. 组件契约:属性键 / 样式组 / 样式子键必须在 component-meta.json + style-meta.json 白名单内(R28b)
21
+ * —— 不存在的键被运行时**整条静默丢弃**(Image 键写错 ⇒ 破图),历史四道闸全放行。
22
+ * —— 11/12/13 与 lint_card.mjs 共用 lib/layout_rules.mjs 与 lib/slice_dedupe.mjs;14 共用 lib/component_contract.mjs,避免双写漂移。
16
23
  *
17
24
  * 用法:
18
25
  * node preflight.mjs --spec UI/<画板>/spec.json --id <卡id>
@@ -24,6 +31,11 @@
24
31
  import fs from 'node:fs'
25
32
  import path from 'node:path'
26
33
  import { metaDir, cardsDir, slicesDir } from './lib/paths.mjs'
34
+ import { buildSliceFileIndex, collectSliceRefStrings, findUrlReservedChars, isRemoteSliceRef, resolveSliceRefOnDisk, sliceNameFromRef } from './lib/slice_dedupe.mjs'
35
+ import { collectRowChildNoWidth } from './lib/layout_rules.mjs'
36
+ import { loadContract, collectContractViolations, describeViolation } from './lib/component_contract.mjs'
37
+ import { collectImageValueFrom, collectImageNoHeight } from './lib/rules/image.mjs'
38
+ import { collectExprNoMathRound } from './lib/rules/expr.mjs'
27
39
 
28
40
  /* ── args ── */
29
41
  const args = {}
@@ -53,10 +65,12 @@ if (!schema) {
53
65
  const spec = args.spec ? loadJSONSafe(args.spec) : null
54
66
  const specCards = spec?.cards || []
55
67
 
56
- /* ── Load meta ── */
57
- const CM = loadJSONSafe(path.join(metaDir, 'component-meta.json'))
58
- const SM = loadJSONSafe(path.join(metaDir, 'style-meta.json'))
59
- const knownClasses = new Set(Object.keys(CM?.components || {}))
68
+ /* ── Load meta ──
69
+ * 组件/样式白名单唯一来源:lib/component_contract.mjs(内部编译 component-meta.json + style-meta.json)。
70
+ * 原先这里自建 `new Set(Object.keys(CM.components))`,与 lint 侧经 loadContract 得到的
71
+ * `contract.classes` **并不相等**——后者含内置类 `Root`(CM 里没有),且元数据读不到时
72
+ * 前者会把全部组件报成「不在白名单」。统一到一处,消除这条口径差。 */
73
+ const contract = loadContract({ metaDir })
60
74
 
61
75
  /* ── helpers ── */
62
76
  function getStyleChild(node, groupKey, childKey) {
@@ -106,33 +120,28 @@ if (specCards.length > 0) {
106
120
  }
107
121
  }
108
122
 
109
- /* ══ Check 2: Component whitelist (R28) ══ */
110
- for (const n of allNodes) {
111
- if (n.className && !knownClasses.has(n.className)) {
112
- check('r28-unknown-class', 'error', `${n.className} "${n.componentId}" 不在 component-meta.json 中 → 渲染空白(R28)`)
123
+ /* ══ Check 2: Component whitelist (R28) ══
124
+ * 白名单 = contract.classes(component-meta.json 的 components 键 ∪ 内置类 Root)。
125
+ * 元数据读不到时**不报**——满屏「不在白名单」只会淹没真正的问题(与 lint 同口径)。 */
126
+ if (contract.loaded.components) {
127
+ for (const n of allNodes) {
128
+ if (n.className && !contract.classes.has(n.className)) {
129
+ check('r28-unknown-class', 'error', `${n.className} "${n.componentId}" 不在 component-meta.json 中 → 渲染空白(R28)`)
130
+ }
113
131
  }
114
132
  }
115
133
 
116
- /* ══ Check 3: Image itemValueFrom (R25) ══ */
117
- for (const n of allNodes.filter(n => n.className === 'Image')) {
118
- const valProp = getProp(n, 'value')
119
- if (!valProp) continue
120
- const from = String(valProp.itemValueFrom || '')
121
- const val = String(valProp.itemValue || '')
122
- if (from === '0' && !val.startsWith('$')) {
123
- check('r25-image-from-0', 'error', `Image "${n.componentId}" itemValueFrom="0"(常量) 但值是路径 → 渲染器不走图片逻辑,空白(R25)。静态路径用 "2"`)
124
- }
125
- if (from === '1' && !val.startsWith('$')) {
126
- check('r25-image-from-1', 'error', `Image "${n.componentId}" itemValueFrom="1"(变量) 但值不以 $ 开头 → 表达式求值失败,空白(R25)`)
127
- }
134
+ /* ══ Check 3: Image itemValueFrom (R25) ══
135
+ * 判定与文案共享 lib/rules/image.mjs(与 lint_card.mjs 同一实现)。
136
+ * reportMissingProp=true —— 键名写错(如 itemKey:"image")会让本项与 Check 5 **双双短路跳过**,
137
+ * 四道闸全绿而图片死掉,故缺失 value 本身必须是 ERROR。 */
138
+ for (const f of collectImageValueFrom(schema.view, { reportMissingProp: true })) {
139
+ check(f.code, f.severity, f.message, f.detail)
128
140
  }
129
141
 
130
- /* ══ Check 4: Image rect.height (R32) ══ */
131
- for (const n of allNodes.filter(n => n.className === 'Image')) {
132
- const h = getStyleChild(n, 'rect', 'height')
133
- if (!h || !String(h.itemValue).trim()) {
134
- check('r32-image-no-height', 'error', `Image "${n.componentId}" 缺 rect.height → CSS aspect-ratio:16/9 会压缩图片高度(R32)。设显式高度`)
135
- }
142
+ /* ══ Check 4: Image rect.height (R32) ══(共享 lib/rules/image.mjs) */
143
+ for (const f of collectImageNoHeight(schema.view)) {
144
+ check(f.code, f.severity, f.message, f.detail)
136
145
  }
137
146
 
138
147
  /* ══ Check 5: Slice filename spaces (R31) ══ */
@@ -155,21 +164,10 @@ for (const n of allNodes.filter(n => n.className === 'Icon')) {
155
164
  }
156
165
  }
157
166
 
158
- /* ══ Check 7: Percentage expression Math.round (R23) ══ */
159
- function walkExprs(node) {
160
- const props = [...(node.properties || [])]
161
- for (const sp of node.style?.properties || []) {
162
- for (const c of sp.itemChildren?.properties || []) props.push(c)
163
- }
164
- for (const p of props) {
165
- if (String(p.itemValueFrom) === '1' && typeof p.itemValue === 'string') {
166
- if (/\*\s*100/.test(p.itemValue) && !/Math\.round/.test(p.itemValue)) {
167
- check('r23-no-math-round', 'error', `"${node.componentId}" 表达式含 *100 但无 Math.round → JS 浮点精度问题(R23): ${p.itemValue.slice(0, 50)}`)
168
- }
169
- }
170
- }
167
+ /* ══ Check 7: Percentage expression Math.round (R23) ══(共享 lib/rules/expr.mjs) */
168
+ for (const f of collectExprNoMathRound(schema.view)) {
169
+ check(f.code, f.severity, f.message, f.detail)
171
170
  }
172
- for (const n of allNodes) walkExprs(n)
173
171
 
174
172
  /* ══ Check 8: Divider for separators (R28) ══ */
175
173
  // Heuristic: Row with height ≤2 and background color but no children = likely a divider
@@ -227,6 +225,52 @@ for (const n of allNodes) {
227
225
  }
228
226
  }
229
227
 
228
+ /* ══ Check 11: Slice refs with URL reserved chars (R31b) ══
229
+ * `#` 之后被当 fragment,浏览器根本不发这部分(实测 /slices/警示-#999.png → 请求 /slices/警示-)。
230
+ * 未命中时静态中间件返回 HTML 兜底(旧行为 200)⇒ 图空白、网络面板 200、零报错。 */
231
+ for (const ref of collectSliceRefStrings(schema)) {
232
+ const hit = findUrlReservedChars(sliceNameFromRef(ref))
233
+ if (hit) {
234
+ check('r31b-slice-url-reserved-char', 'error', `切图引用含 URL 保留字符 ${hit.map((c) => `"${c}"`).join(' ')}: "${ref}" → 浏览器会截断请求,图片静默不显示(R31b)。重命名切图为纯 ASCII 名并同步改写引用`)
235
+ }
236
+ }
237
+
238
+ /* ══ Check 12: Row-child container without rect.width + flexShrink:0 (R22b) ══ */
239
+ for (const hit of collectRowChildNoWidth(schema.view)) {
240
+ check('r22b-row-child-container-no-width', 'error', hit.message, `${hit.className} ${hit.componentId} ← ${hit.parentClass} ${hit.parentId}(${hit.siblingCount} 个并列子节点)`)
241
+ }
242
+
243
+ /* ══ Check 13: Slice ref target missing on disk (R31c) ══
244
+ * 审计给了正确的名字、但文件还在 bundle 里没落进 src/slices ⇒ 引用 404、图空白、零报错。
245
+ * 这一条把「引用名 ↔ 磁盘名同源」变成每轮迭代都能看到的硬约束。 */
246
+ {
247
+ const sliceIndex = buildSliceFileIndex(slicesDir)
248
+ for (const ref of collectSliceRefStrings(schema)) {
249
+ if (isRemoteSliceRef(ref)) continue
250
+ const { resolved, name } = resolveSliceRefOnDisk(sliceIndex, ref)
251
+ if (resolved) continue
252
+ check(
253
+ 'r31c-slice-ref-missing-file',
254
+ 'error',
255
+ `切图引用 "${ref}" 在 src/slices 里不存在(期望文件名 ${name})→ 渲染时 404,图片位置一片空白且控制台不报错(R31c)。把切图按引用名落盘(crop_slices.mjs --from-audit 会收养审计判定存在的资产),或把引用改成磁盘上真实存在的文件名 —— 两者必须同源`,
256
+ )
257
+ }
258
+ }
259
+
260
+ /* ══ Check 14: Component contract — 属性键 / 样式组 / 样式子键白名单 ══
261
+ * 2026-09-24 事故:模型给 Image 写 itemKey:"image"(合法键只有 "value"),
262
+ * 运行时按 itemKey 绑定、不存在的键被整条静默丢弃 ⇒ value 为空、图片渲染成破图;
263
+ * 而 validate / lint / preflight / elements.missing **全部放行**。
264
+ * 全仓 82 处 Image 有 81 处写 value,唯一写 image 的正是这张卡 ⇒ 必须机械兜底。
265
+ * 白名单唯一来源 card-editor/meta/component-meta.json(经 lib/component_contract.mjs 编译)。 */
266
+ {
267
+ for (const v of collectContractViolations(schema, contract)) {
268
+ // unknown-class 由 Check 2 报(r28-unknown-class),此处只报属性键 / 样式组 / 样式子键
269
+ if (v.code === 'unknown-class') continue
270
+ check(`r28b-${v.code}`, v.severity, describeViolation(v), v.where ? `节点路径 ${v.where}` : '')
271
+ }
272
+ }
273
+
230
274
  /* ── Output ── */
231
275
  const errors = results.filter(r => r.severity === 'error')
232
276
  const warns = results.filter(r => r.severity === 'warn')