cardbuddy 0.15.0 → 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.
- package/dist/preview-editor/assets/index-BkbBxDTT.css +1 -0
- package/dist/preview-editor/assets/{index-CIIfMlwI.js → index-Bro07DTa.js} +91 -88
- 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/cardValidate.js +16 -5
- package/dist/vendored/skills/card-skill-trainer/SKILL.md +61 -6
- 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 +4 -1
- package/dist/vendored/skills/card-skill-trainer/scripts/run_round.mjs +279 -90
- 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/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 +169 -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/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/scope_alias.mjs +125 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_assemble.mjs +325 -41
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_extract.mjs +112 -6
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_shared.mjs +0 -6
- 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 +3 -1
- package/dist/vendored/skills/dsl-card-generator/scripts/validate_schema.py +23 -15
- package/dist/vendored/skills/shared/exit-codes.md +79 -0
- package/dist/vendored/skills/shared/spec-contract.md +118 -0
- package/dist/vendored/skills/sketch-spec-to-card/SKILL.md +26 -9
- 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 +91 -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 +75 -15
- package/dist/vendored/skills/sketch-spec-to-card/scripts/vision.py +40 -34
- 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
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
| 目的 | 命令 |
|
|
24
24
|
|---|---|
|
|
25
25
|
| **输入路由(建卡第一步)** | `node scripts/route_input.mjs <输入路径> [--json]`:判输入形态并直接给出下一步命令。`meaxure-export`(Sketch MeaXure 标注导出:index.html + preview/ + assets/)→ **必须先跑 sketch-spec-to-card 前处理**,工具会打印 `parse_meaxure.py --src --draft --build` + `cluster_states.py --out UI/<稿名>` 两条命令(若 `UI/*/manifest.json` 的 `export` 已匹配该导出则跳过前处理、直接给存量产物路径);`ui-bundle` → Workflow A 逐卡;`image` → Workflow A 纯视觉;`card-dir` → Workflow B 改卡;`unknown`(exit 3) → 先问用户别猜。见 SKILL.md §8 Workflow 0 |
|
|
26
|
-
|
|
|
26
|
+
| 秒出合法骨架(**仅 blank 调试 / 模板克隆**) | `node scripts/scaffold_card.mjs --intent <blank\|list\|detail\|form\|dialog\|tab\|canvas-head> --id <卡id> --name <名称>`。**常规建卡走 IR 主路径**(SKILL.md §8 Workflow A:整树 IR → `expand_ir.mjs`);`--from <模板卡id>` 整卡克隆仍可用 |
|
|
27
27
|
| **列画板 + 选卡门禁(多画板)** | 取清单:未前处理 `python3 skills/sketch-spec-to-card/scripts/parse_meaxure.py --src <导出目录> --list`(**展示 1 起**,传 `--indices` 时 -1);已前处理 `cat UI/<稿名>/manifest.json`(`canvases[].order` **1 起**/`name`/`artboardType`/`status`)+ `clusters.json`(`cards[].cardKey`/`pageId`/`stateNames`/`recommendedBase`)。交互:按 **`1..n` 列出 + 末尾 `全部`** → 一句纯文本提问 → **停止输出等用户回序号**(**没有** `request_user_input`,不要去找弹窗工具)。**清单逐行配图**(每画板行下 `图: <绝对路径>`,缺图标缺失,禁止堆末尾;**路径必须完整绝对路径**——禁止 `…`/`...` 缩写前缀、禁止反引号或代码块包裹);**机械卡点**:`parse_meaxure.py` 不带范围参数的生成运行直接 **exit 4** 并打印清单;用户回复序号 k → `--indices <k-1>`、「全部」→ `--scope-all`(「全部」只能是用户的回答,Agent 不得默认全量)。**UI 产物可回 `全部` 全量生成;产卡默认单画板 / 单 cardKey,回 `全部` 才算显式批量授权**;禁止自行挑画板。见 `rules.md §十三` |
|
|
28
28
|
| **设计稿层级树(AI 读图)** | `node scripts/spec_tree.mjs --spec UI/<画板>/spec.json [--card 1] [--max-depth 3] [--compact]`:把 `elements` 按矩形包含关系几何重建为 AI 可读的缩进树(类型+名称+尺寸+样式+文案)。Workflow A 步骤 1 用 |
|
|
29
|
-
| **设计稿 vs
|
|
29
|
+
| **设计稿 vs 渲染对比(可选,出并排图给人看)** | `node scripts/render_diff.mjs --card <卡id> --design UI/<画板>/card-1.png [--out diff.png] [--json]`:并排合成对比图(左=设计稿,右=渲染);省略 `--width` 自动读 `schema.view.width`。**视觉闭环主工具是 `iterate.mjs`(量化 match/hotspots/contributors)+ `diff_bands.mjs`(dy/dh/主色证据)**;render_diff 只在需要一张并排图给用户看时用 |
|
|
30
30
|
| **一条龙迭代(首选)** | `node scripts/iterate.mjs --ir <IR> --out src/cards/<卡id>/ --design <card-N.png> --width 343 [--spec <spec.json>] [--elements] [--slices-dir <要素包 slices 目录>]`:单进程 expand+lint+preflight+render+pixel-diff(含切图汇聚 + 同内容去重)。结果 JSON 带 `gate`(机器门禁判定)、`pixelDiff.hotspots`(>3% 差异热区,1× 设计 px)、**`contributors`(分项归因:每个热区 → 最内层具体节点 `cid/cls/text/rect/h/hSibling/designH/dH/flags/hotCover`;**一轮只改 `contributors[0].elements[0]` 的一个属性**,flags 见 SKILL.md §S5)**、`widthMismatch`(`--width` 与设计稿卡面宽不符 ⇒ 先改 width,否则 match 与 dy 全不可信)、`contentOffset1x`+`contentOffsetHint`(内容整体平移)、`renderImage`(渲染原图)、`lint.warningDetails`、`preflight`、**`fidelity`(要素线:`total/matched/missing/invented/slices` —— 设计稿有而 schema 里没有的文案、schema 里自造的文案;`elements.missing=0` 是门禁项)**、`stalled`(**连续 3 轮未刷新最好成绩,含震荡下滑形态 ⇒ 照 hint 先 `ir_restore.mjs` 回退 best、再单分项改,禁止继续盲调)。省略 `--width` 时自动取设计稿卡面宽。多卡连生成用 `--keep-alive`(stdin 逐行 JSON) |
|
|
31
31
|
| **收敛判定(收尾必跑)** | `node scripts/iterate.mjs --gate --out src/cards/<卡id>/`:读 `.cache/ai-scratch/<卡id>.iterate.json` 做机器判定(match≥95 ∧ heightDelta=0 ∧ widthDelta=0 ∧ \|contentOffset1x\|≤2 ∧ **elements.missing=0** ∧ 零 error,且 schema 未在报告后变更),未达标 exit 2(gate-fail,与「脚本报错 exit 1」区分开)。**`gate.stage` 给两段式节奏**:`structure`(要素缺失 / validate / lint / preflight 未过)⇒ 先补要素别调像素;`pixel` ⇒ 要素齐了才谈像素收敛。正文只引用它的 verdict,禁止自述「门禁全绿」 |
|
|
32
32
|
| **IR 微调(首选 `ir_patch`,别整份重写)** | MCP 工具 `ir_patch({file, ops:[{path, op, value}]})`:点路径最小写(数字段=数组下标)、`op ∈ set/merge/push/delete`、原子(任一条失败整份不写)、返回旧值→新值摘要、不吃引号转义。**改已存在的 IR 就用它**;`fs_write` 整份重写只在首次写时用 —— 同轮内第 2 次全量写会收到提醒、**第 3 次起被拒**(确实要重排结构才传 `rewrite:"true"`),与盘上逐字节相同则直接跳过不写。跑完仍要跑 iterate 才有门禁结论(ir_patch 不渲染、不校验) |
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
| **建骨架(MCP)** | `card_scaffold({intent,id,name,outBase,platform,force})`:等价于 scaffold_card.mjs,无需 dev server |
|
|
54
54
|
| **插组件(MCP)** | `card_add_component({id,parent,className,text,click})`:等价于 add_component.mjs,插入后自带离线硬校验 |
|
|
55
55
|
| **渲染回归基线** | `node scripts/render_baseline.mjs --snapshot`(建基线)/ `--diff`(对比基线像素差异,超阈值 exit 70)。已改为离线独立渲染器,不依赖 dev server |
|
|
56
|
-
| **批量产线(spec→多卡粗骨架)** | `node scripts/generate_cards.mjs --ui <UI目录> --out <输出目录> [--
|
|
57
|
-
| **手动单卡(批量产线工具)** | `node scripts/spec_to_schema_v3.mjs --spec <spec.json> --id <卡id> [--name <名称>] [--force]`:从 spec.json
|
|
56
|
+
| **批量产线(spec→多卡粗骨架)** | `node scripts/generate_cards.mjs --ui <UI目录> --out <输出目录> [--only <子串>] [--popups] [--force]`(走 v3 引擎,无 `--strategy` 开关)。生成粗骨架后逐卡走 `iterate.mjs` 视觉闭环精修(见 Workflow A 批量产线段) |
|
|
57
|
+
| **手动单卡(批量产线工具)** | `node scripts/spec_to_schema_v3.mjs --spec <spec.json> --id <卡id> [--name <名称>] [--force]`:从 spec.json 直组装组件树(v3 CLI),产出粗骨架后走 `iterate.mjs` 精修 |
|
|
58
58
|
|
|
59
59
|
## 1. 最常用组件最小骨架
|
|
60
60
|
|
|
@@ -183,37 +183,38 @@ $title.length * 16 // 按文字长度算宽度
|
|
|
183
183
|
|
|
184
184
|
**AI 必须主动判断的设计原则**:
|
|
185
185
|
|
|
186
|
-
-
|
|
187
|
-
- **元素间距用 margin 不用 padding**:
|
|
188
|
-
- **卡片根样式全量提取**:spec 卡片 bg shape
|
|
189
|
-
-
|
|
190
|
-
-
|
|
191
|
-
- **运行期数据必须绑定**:金额/日期/姓名/枚举 Tab/利率行等一律 inputParams;金额
|
|
192
|
-
- **水平 margin 不叠 width:100
|
|
193
|
-
- **space-between 三要素公式**:父 Row
|
|
194
|
-
-
|
|
195
|
-
- **语义组件由 AI 裁决**:脚本给信号,AI
|
|
196
|
-
- **垂直节奏用 padding 不用 lineHeight
|
|
197
|
-
- **background 必须含 backgroundType:"01"
|
|
198
|
-
-
|
|
199
|
-
-
|
|
200
|
-
- **borderRadius
|
|
201
|
-
- **分割线用 Divider 组件不用 Row
|
|
202
|
-
- **AbsoluteLayout height 必须显式设值**:
|
|
203
|
-
-
|
|
204
|
-
- **space-between Row
|
|
205
|
-
- **对比表行范式**:左值(强调色
|
|
206
|
-
- **分段标题范式(Divider 居中夹 Text
|
|
207
|
-
- **柱状图/网格线/轴标签 = Canvas
|
|
186
|
+
- **标注可信度分层**:属性值(圆角/阴影/字体/边框/背景色)读标注、禁抄存量卡;几何(rect/间距)按实图/容器边界分析(pitfalls §9-R1/R10)。
|
|
187
|
+
- **元素间距用 margin 不用 padding**:padding 污染 box 高(§9-R9)。
|
|
188
|
+
- **卡片根样式全量提取**:spec 卡片 bg shape 的背景+圆角+边框+阴影+透明度逐项提取到根容器,不可只取两项(§9-R33)。
|
|
189
|
+
- **根节点零间距**:聚焦裁切后卡片独立渲染,Root 与首层容器 padding/margin 归零,内部留白下沉到 content 容器 padding16(§9-R10)。
|
|
190
|
+
- **渲染器默认样式显式覆盖**:Row/Column 默认 padding:2px+gap:4px+居中、ListLayout 默认 edge-insets{2,2,2,2} 须归零/指定(§9-R11)。
|
|
191
|
+
- **运行期数据必须绑定**:金额/日期/姓名/枚举 Tab/利率行等一律 inputParams;金额 type:"number" + 千分号表达式(§9-R12 + amount_formatting.md)。
|
|
192
|
+
- **水平 margin 不叠 width:100%(R63)**:叶子设左右 margin 用 width:auto 靠 stretch 补宽;100%+marginLeft/Right 溢出。分栏:左 100%+shrink:1,右 auto+shrink:0。lint r63 只拦交叉轴父容器(Root/Column/SingleColumn/flexDirection:column/AbsoluteLayout abspos 子);Row/MultiColumn 主轴不报。补宽须显式 rect.width:"auto"(Text/MultiText 的 fit-content 顶掉 stretch)(rules.md §十六)。
|
|
193
|
+
- **space-between 三要素公式**:父 Row 100%+shrink:1+space-between;左 100%+shrink:1;右 auto+shrink:0(§9-R27)。
|
|
194
|
+
- **预览器行为契约**:容器默认 width:100%(自适应须显式 auto)、组件默认 overflow:hidden(有意溢出须 visible)、abspos 用 align+margin(position 组无效)、Canvas 禁 save/scale/transform/drawImage、fontWeight 只用 400/500/600、font 组无 fontFamily、预览默认 line-height:1.5(单行须显式行高)。完整见 previewer_contract.md。
|
|
195
|
+
- **语义组件由 AI 裁决**:脚本给信号,AI 结合截图+层级树定语义(§9-R36)。
|
|
196
|
+
- **垂直节奏用 padding 不用 lineHeight**:控制垂直位置用容器 paddingTop/Bottom,不设 lineHeight(§9-R51)。
|
|
197
|
+
- **background 必须含 backgroundType:"01"**:写纯色背景须同时写 backgroundType:"01",否则渲染器按默认 "00"(透明)静默丢弃 backgroundColor ——「设了背景但渲染透明」最常见原因。
|
|
198
|
+
- **文本水平居中需包裹层**:Column 中单 Text 居中仅设 textAlign:"center" 不生效,用 Row(width:100%, flexShrink:1, justifyContent:center)包裹。
|
|
199
|
+
- **券项/卡片项白色填充**:列表项即使无切图也须 background:{backgroundType:"01", backgroundColor:"rgba(255,255,255,1)"},否则与卡底融为一体失层次。
|
|
200
|
+
- **borderRadius 必须用对象形式**:borderRadius:"12" 标量展开器不生成 itemChildren、渲染器读不到;须写 {borderRadiusType:"0", borderRadius:"12"}。
|
|
201
|
+
- **分割线用 Divider 组件不用 Row+背景**:列表项间须 c:"Divider",禁 Row+backgroundColor 模拟;末项隐藏用 v-show 对象形式(showType:"01"+condition.expression,裸字符串被编译成 itemValueFrom:"1" 静默丢弃)+ condition.name 必填(缺名 validate ERROR、命名不合规 lint r52 WARN)。
|
|
202
|
+
- **AbsoluteLayout height 必须显式设值**:abspos 子元素脱标不撑高父容器,height:"auto" 导致容器塌缩为 0;须设 height=最高子元素已知高(如 "75")。
|
|
203
|
+
- **简单几何图形用样式实现不切图**:圆/方+文字(VS 徽章/数字角标)用 Column(borderRadius=宽/2 + backgroundColor)+ 内层 Text,无需切图。
|
|
204
|
+
- **space-between Row 子元素需显式像素宽**:两并列卡在 space-between 中须各设显式 width(从 spec 读像素如 "152"),不可用 width:"100%";至少一侧 flexShrink:"1" 满足 R35。
|
|
205
|
+
- **对比表行范式**:左值(强调色 textAlign:right width:100% flexShrink:1)+ 中间标签(灰底圆角徽章 width:"72" bg:rgba(245,245,245,1) radius:"6" flexShrink:0)+ 右值(对比色 textAlign:left width:100% flexShrink:1),父 Row justifyContent:space-between。
|
|
206
|
+
- **分段标题范式(Divider 居中夹 Text)**:Row(justifyContent:center alignItems:center)包 Divider(flexShrink:1)+Text(flexShrink:0, margin:左右8)+Divider(flexShrink:1),两线自适应居中。
|
|
207
|
+
- **柱状图/网格线/轴标签 = flex 原生优先、Canvas 仅兜底**:DSL 无图表组件,矢量柱体此前被静默丢弃。生成器已按纯几何识别柱状图簇(同基线窄柱 ≥4 + 网格线/标签佐证)——先出 flex 原生组件树(轴列 + 槽位柱列 + 硬停渐变网格线),表达不了才回退 Canvas 回放;手写 Canvas 兜底时记住 `event.canvas.getContext('2d')` 才是 ctx、白名单无 roundRect(四角 arc 拼路径)、簇高要登记语义边界。详见 `generation_pitfalls.md §16`。
|
|
208
208
|
- **列表行壳/行右图标认领**:行壳 = 圆角≥4 或渐变或宽≤卡宽-8(满宽无圆角平填充=分区背景板,禁认领);壳间有间隙 → 不发 Divider、row-spacing 承间隙、列表按壳宽内缩;逐行壳色不同绑 $rowBg;行右 32-72px 图标 = $rightIcon;icon 内嵌徽标不是 affordance(按 rightIcon 盒排除)。详见 `generation_pitfalls.md §17`。
|
|
209
209
|
- **图表绘制层序(铁律 R62)**:出码顺序 = 底 → 顶——坐标系/参考线(网格线、轴线、雷达环与辐射线、环形轨道)最底层 → 数据标记(柱/折线/多边形/扇区/高亮弧)→ 文字标签最上层;禁止「先填充后画网格」。半透明数据标记把 `opacity` 乘进颜色/渐变(代理 ctx 无 `globalAlpha`)。spec 元素顺序不可照抄。详见 `rules.md §十五`。
|
|
210
210
|
- **重复行并表前先比模板签名**:行图标/复合金额对/行右沿/行内操作不同的行不能进同一个 ListLayout(半宽对比面板混进全宽产品行 → 字段错位竖排换行 + 占位文案 + 双份渲染)。
|
|
211
211
|
- **卡底色看左右 margin**:采样卡底色只信左右边缘条带(内缩壳盖不到),别采底部中条(会采到最后一行彩色行壳)。
|
|
212
212
|
|
|
213
213
|
- 对比面板并排壳:预抽取每壳一行(壳内标签行+值行+圆点+tag),y 带重叠合成一条带;连接件末尾子节点+负 marginLeft;见 pitfalls §18。
|
|
214
|
-
- 柱状图走 flex 原生(轴列+槽位柱列+硬停渐变网格线),Canvas 仅兜底;值标签列宽=标签宽+4 防折行;见 pitfalls §
|
|
214
|
+
- 柱状图走 flex 原生(轴列+槽位柱列+硬停渐变网格线),Canvas 仅兜底;值标签列宽=标签宽+4 防折行;见 pitfalls §16。
|
|
215
215
|
- 渲染器契约:position 组被忽略(无绝对定位)/overflow 默认 hidden/负 margin 可用(**逐边负值必伴 marginType=1**,否则静默失效)/style 组同名子项唯一(绑定改写不追加);见 pitfalls §20/§23。
|
|
216
216
|
- 散点带(≥4 pill 分 ≥2 行 + 中心近方媒体):三列 Column + 列间负 margin 还原重叠,列内行 paddingTop=spec y 差;胶囊=Row 壳+文本+行尾箭头;金额/标签走变量绑定;见 pitfalls §21 / recipes §18。
|
|
217
|
+
- 顶点标签绝对定位(胶囊空间散布围绕多边形顶点,≥3 pill y 跨度>40px x 跨度>80px):胶囊+雷达图同一 AbsoluteLayout + 逐元素 margin 偏移到 spec 坐标(不嵌套容器),height 显式设值(auto 塌缩),overflow:visible;消费元素从 texts/shapes/slices/orphanSlices 统一过滤防重影;见 recipes §18a。
|
|
217
218
|
- 半环评分仪表盘(大号数字+「分」单位+下标签,均未被消费)→ Canvas recipe 出码,零流高叠放(marginTop+负 marginBottom,marginType=1);见 pitfalls §24 / recipes §14。
|
|
218
219
|
- 段落壳(灰底圆角含富文本碎片)归 richBlock:panelRows 不认领;尾部链接行(查看详细建议+箭头)随段落壳内补行;见 pitfalls §22。
|
|
219
220
|
- aux 行锚点认 spec x 几何(textAlign=right 但几何在左 1/3 → 左锚);行尾 8-32px 小切片随行消费不孤立成行;见 pitfalls §23。
|
|
@@ -185,13 +185,9 @@ this.postMessage('syncList');
|
|
|
185
185
|
|
|
186
186
|
## 5. 金额格式化(千分号/正负号/货币符号)
|
|
187
187
|
|
|
188
|
-
用途:金额显示带千分号、2
|
|
188
|
+
用途:金额显示带千分号、2 位小数、可选正负号与货币符号。完整分类与口径(纯数字 / 带前缀文案 / 列表项 fallback / 健壮版)见 `amount_formatting.md §3`。
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
```javascript
|
|
193
|
-
'剩余应还¥' + ($currentPeriodAmount * 1).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
|
194
|
-
```
|
|
190
|
+
内联表达式(带前缀文案,来源 creditCardPayment,适合单处使用):结构同 `amount_formatting.md §3.3`,本节聚焦默认口径的健壮版——
|
|
195
191
|
|
|
196
192
|
健壮版表达式(**默认口径**:显示金额一律在 view 表达式里直出,不在 onReady 预生成 `*_fmt`):
|
|
197
193
|
|
|
@@ -650,9 +646,9 @@ ynet.callJSAPI('alertDialog', {
|
|
|
650
646
|
- **不产出 schema.json**:上游 `prompt.md` 对该画板已是「交互提示文档」,明确声明不建卡。下游消费时,把本调用补到**父卡**(上游 `clusters.json.overlays[].parentKey` 或 `prompt.md §4` 指示的触发卡)的对应按钮 / 条件事件中。
|
|
651
647
|
- 参考存量:`budgetAnalysis`(温馨提示→alertDialog)、`creditCardPayment`(还款前二次确认→alertDialog)。片段见 `recipe_snippets/alertDialog-call.json`。
|
|
652
648
|
|
|
653
|
-
## 13. 样式生成铁律(生成器通用:
|
|
649
|
+
## 13. 样式生成铁律(生成器通用:v3 适用)
|
|
654
650
|
|
|
655
|
-
用途:
|
|
651
|
+
用途:v3 生成 schema.json 时,**必须遵守的样式格式约束**(geo 生成器已于 2026-08-31 整体删除)。违反任一条都会导致渲染器丢弃样式(表现为「UI 样式几乎全部丢失」——文本无色无大小、背景透明、TabBar 无底色)。
|
|
656
652
|
|
|
657
653
|
> **根因记录(2026-08-13)**:首轮生成 4 张校准卡全部「样式全丢」,排查发现渲染器对以下三条零容忍。
|
|
658
654
|
|
|
@@ -805,6 +801,25 @@ node scripts/apply_opacity_pattern.mjs --id <cardId> --node comp_8 --container C
|
|
|
805
801
|
- 三列 Column + 列间**负 marginLeft(marginType=1)**还原水平重叠;列宽=成员外接宽,溢出靠 overflow visible。
|
|
806
802
|
- 列内行 paddingTop=spec y 差、**列首行相对带顶**;胶囊=Row 壳+文本+行尾箭头(左锚不居中)。
|
|
807
803
|
- 金额/标签走变量绑定(千分表达式/字符串变量);检出即消费 pills/金额/标签/媒体/箭头防重影。
|
|
804
|
+
## 18a. 顶点标签绝对定位(vertex-absolute,train-proven:seq5 card 12 雷达五边形)
|
|
805
|
+
|
|
806
|
+
用途:胶囊**空间散布**(非横排),围绕雷达/环形图的多边形顶点分布。生成器自动检测条件:
|
|
807
|
+
≥3 个带填充 pill、y 跨度 >40px、x 跨度 >80px、分布在 ≥3 个 y 带 → 图表顶点标签而非按钮排。
|
|
808
|
+
|
|
809
|
+
关键纪律(踩坑总结):
|
|
810
|
+
- **胶囊和雷达图放在同一个 AbsoluteLayout**,共享 spec 绝对坐标系。中心媒体从 isolatedBlocks
|
|
811
|
+
提出到 vertexClusters.chartMedia,与胶囊统一 margin 偏移。分属不同路径 → 坐标系不一致 → 位置偏移。
|
|
812
|
+
- **逐元素绝对定位**:每个文本/形状用自身 margin 偏移到 spec 坐标,不嵌套容器(嵌套偏移内层元素 → IoU 极低)。
|
|
813
|
+
- **AbsoluteLayout 须设显式 height**(container() 不处理 opts.height,须手动 setStyleChild),
|
|
814
|
+
值 = 最底顶点 (y+height+35) − 最顶顶点 y。auto → 塌缩 → 下游间距计算错误。
|
|
815
|
+
- **overflow:visible** 保留防止子节点溢出被裁。
|
|
816
|
+
- 消费元素须从所有返回值统一过滤 _vertexConsumedEls:texts/shapes/slices/content/buttonShapes/
|
|
817
|
+
isolatedBlocks/orphanSlices(group 类型会被 push 进 slices,仅清 isolatedBlocks 不够 → 重影)。
|
|
818
|
+
- AbsoluteLayout 从 r66 FULLWIDTH_CONTAINERS 排除(叠层定位容器有意设固定宽)。
|
|
819
|
+
|
|
820
|
+
生成器已自动化:v3_extract.mjs 检测 vertexClusters + chartMedia,v3_assemble.mjs 统一 AbsoluteLayout 渲染。
|
|
821
|
+
|
|
822
|
+
|
|
808
823
|
|
|
809
824
|
## 19. 柱状图 Canvas 自绘回放(bar-chart replay,export-proven 同族:assetAllocRadar gauge/radar)
|
|
810
825
|
|
|
@@ -851,3 +866,10 @@ node scripts/apply_opacity_pattern.mjs --id <cardId> --node comp_8 --container C
|
|
|
851
866
|
**禁止**:用单条色带/硬停渐变近似(带底沿留矩形硬边,实测单带还原 32% 像素差);用纯色块伪装弥散。
|
|
852
867
|
|
|
853
868
|
**Workflow A 裁决**:此类装饰判 `decoration`,宽度/位置读标注 rect,透明度读标注 opacity(半透明合法,见铁律 §0-8);无导出切片时禁画板裁切(会带前景文字)。
|
|
869
|
+
|
|
870
|
+
---
|
|
871
|
+
### diffuse texture is bitmap not css gradient(r055 训练回灌)
|
|
872
|
+
> lesson: `LESSON-20260827-057` status: `promoted`
|
|
873
|
+
|
|
874
|
+
弥散、光晕、噪点和不规则纹理按位图处理:优先 slice,其次画板局部裁切,最后占位图;禁止退化为纯色或规则 CSS 渐变。
|
|
875
|
+
|
|
@@ -94,7 +94,8 @@
|
|
|
94
94
|
## 九、组件级硬约束
|
|
95
95
|
|
|
96
96
|
详见 `references/component_rules.md`(单表速查)。最易踩的几条:
|
|
97
|
-
- **根节点(Root/view)不设样式宽度**:根节点宽度由顶层 `view.width
|
|
97
|
+
- **根节点(Root/view)不设样式宽度**:根节点宽度由顶层 `view.width`(= **卡面宽**,非画板宽,见 `ir-format.md` §2f)承载;`view.style.properties` 里**不要**放 `rect` 组(含 `rect.width`)——放了是冗余/重复宽度(export-proven:49+ 张导出卡根 style 均无 rect,仅 `view.width` 定宽)。若手写时误加,`check_topology.py` 会以 `Root_style_rect_forbidden`(hard 级)提示删除。
|
|
98
|
+
- **宽度自适应(R66):占满语义禁写死 px**——纵向流父级(Root/Column/AbsoluteLayout)下,占满父内容盒(98%~103%)的容器子节点必须用 `width:"100%"` 或不写宽(容器 meta 默认就是 100%),px 只给真固定元素(图标/头像/徽章/图表画布/hug 宽标题壳)。写死 px 的内容层在非设计宽度的宿主容器里锁死不自适应。lint `r66-fullwidth-px` ERROR 拦截;契约全文见 `ir-format.md` §2f。
|
|
98
99
|
- **关键文本节点(金额/数字/标签/标题/占比/单位)必须显式 `flexShrink:"0"`**:`.ce_text` 默认 `width:fit-content; word-break:break-all`,父子链上任一层被压缩都会沿断行规则塌陷(字符竖排堆叠);关键文本节点 + 包裹它们的直接 Row/Column 容器都要 `flexShrink:"0"`(flex 组 itemId:"004")。详见 `generation_pitfalls.md §9-R17`。
|
|
99
100
|
- **嵌套需 space-between 的 Row 必须显式 `rect.width:"100%"`**:根节点级 Row 默认撑满,嵌套层不再默认撑满(收缩到内容宽 → space-between 失效);视觉上需要左右分布的行容器都要显式宽度 100%(同时按 R22 给该节点 `flexShrink:"1"`)。**例外**:父容器是纵向流且交叉轴 stretch(`Column`/`Root`/`SingleColumn`,CSS 强制 column + `align-items` 缺省 stretch)时子 Row 本就被拉满,或自身已给定宽(如 100px 胶囊)时 `space-between` 已生效——这两种情况 lint `r19` 不报,存量卡不要为消 WARN 去补 `width:"100%"`(会连带触发 R22/R35 两条 ERROR)。详见 `generation_pitfalls.md §9-R19`。
|
|
100
101
|
- **`rect.width:"100%"` 的节点必须保持可压缩(`flexShrink:"1"`),禁止同节点 `flexShrink:"0"`**:`width:100%` 在 flex 里语义是「占满主轴」,与兄弟节点并排时靠 `flexShrink` 协商让位;100% 节点若同时 `flexShrink:"0"`,协商被锁死,固定宽兄弟(图标/金额/按钮)会被挤出父容器或触发溢出。与 R17 按 `width` 分流:关键 Text(auto/fit-content/未设宽)走 R17 `flexShrink:"0"` 保护;`width:100%` 容器走本条 `flexShrink:"1"` 让位。详见 `generation_pitfalls.md §9-R22`。
|
|
@@ -260,7 +261,7 @@ Canvas 是**即时模式**绘制——后画的压先画的。所以「绘制层
|
|
|
260
261
|
|
|
261
262
|
- **禁止**把参考线画在数据标记之上(「先填充后画网格、网格才压得住」是反范式);spec 元素顺序**不可照抄**——标注导出常把网格线排在数据图元之后(assetAllocRadar 雷达:元素 34-39 是 `#DCDCDC` 网格/辐射线、40 才是渐变数据多边形)。
|
|
262
263
|
- 数据标记半透明(spec `opacity<1`)时把透明度**乘进颜色/渐变 stop**(代理 ctx 白名单**无 `globalAlpha`**),底层参考线才能透出来;设计稿「网格透过多边形可见」正是这个效果(assetAllocRadar 雷达 opacity 0.8)。
|
|
263
|
-
- 机械保证:`
|
|
264
|
+
- 机械保证:`lib/v3_shared.mjs` 的 `chartBarCanvasCode` 用 `chartPaintLayers` 强制分层(任一维 ≤2px 的细线 = 参考线;覆盖簇 bbox 一半以上的填充 = 绘图区底板;其余 = 数据标记),出码顺序 = 底板 → 参考线 → 数据标记 → 文字标签;回归护栏 `tools/tests/chart-layer-order.test.mjs`。手写图表(`recipes.md §15/§18`、`recipe_snippets/canvas-radar.json`)同口径。
|
|
264
265
|
- 数据取值导致的图形差异(多边形形状、柱高、扇区角度)不在本铁律管辖内——层序只约束「参考线 / 数据标记 / 文字标签」三类图层的先后。
|
|
265
266
|
|
|
266
267
|
## 十六、水平 margin 不叠加 width:100%(铁律 R63)
|
|
@@ -11,13 +11,16 @@ node scripts/spec_tree.mjs --spec <spec.json> [--card 1] [--compact] # spec 层
|
|
|
11
11
|
node scripts/scaffold_card.mjs --intent <…> --id <卡id> --name <名称> [--from <模板卡id>] # 骨架(blank/克隆;常规建卡走 IR)
|
|
12
12
|
node scripts/add_component.mjs --id <卡id> --parent <cid> --className <X> [--text "…"] [--click] [--set-prop k=v] # 插组件(改卡专用,只增不改)
|
|
13
13
|
node scripts/expand_ir.mjs --ir <IR.json> --out src/cards/<卡id>/ # 紧凑 IR→合法 schema/data(切图自动汇聚);契约见 ir-format.md
|
|
14
|
+
node scripts/expand_ir.mjs --to-ir <schema.json> # 逆向:schema→紧凑 IR(`--self-test` 的往返无损校验用;也可用于「改卡后回出 IR」)
|
|
15
|
+
node scripts/generate_cards.mjs --ui <UI目录> --out <目录> [--only <子串>] [--popups] [--force] # 批量产线:spec→多卡粗骨架(走 v3 引擎,只出粗骨架,逐卡再走 iterate 精修)
|
|
16
|
+
node scripts/spec_to_schema_v3.mjs --spec <spec.json> --id <卡id> [--name <名称>] [--force] # 单卡直组装 CLI(v3 引擎底;批量产线与训练/回归都调它)
|
|
14
17
|
node scripts/ir_merge.mjs --ir <IR.json> --part <片段.json> # IR 分片合并(幂等+原子;每片 +40-140s,小卡禁拆;支持 {parts:[…]} 捆绑包一次合并)
|
|
15
18
|
node scripts/iterate.mjs --ir <IR.json> --out <dir> --design <png> [--set 'path=value'] # 一条龙闭环:expand→lint→render→pixel-diff(JSON 摘要,比逐脚本快 5×)
|
|
16
19
|
node scripts/ir_restore.mjs --list|--best|--round … # 迭代快照回退(见 iteration_protocol.md)
|
|
17
|
-
node scripts/lint_card.mjs --id <卡id> | --all [--json] #
|
|
20
|
+
node scripts/lint_card.mjs --id <卡id> | --all [--json] # 行为规则体检(条数以实跑输出为准,文档不复制数字)
|
|
18
21
|
python3 scripts/validate_schema.py <schema.json> [--topology] # 结构合法性(CI 硬卡点)
|
|
19
22
|
python3 scripts/autofix_schema.py <schema.json> [--check] # 机械修复 itemValueFrom/重复 id/data 漂移
|
|
20
|
-
node scripts/preflight.mjs --spec <spec.json> --id <卡id> #
|
|
23
|
+
node scripts/preflight.mjs --spec <spec.json> --id <卡id> # 14 项机械 Pre-flight
|
|
21
24
|
node scripts/diff_card.mjs --id <卡id> [--baseline <path>] # 变更对比(铁律机械兜底,整体替换后必跑)
|
|
22
25
|
node scripts/check_fidelity.mjs --spec <spec.json> --schema <schema.json> # 保真度门禁(元素级对齐断言,npm run check:fidelity)
|
|
23
26
|
node scripts/render_card.mjs --id <卡id> [--width 375] [--out render.png] [--keep-alive] # 离线截图(vendor 产物+Chrome CDP,不占端口)
|
|
@@ -140,8 +140,7 @@ sketch-spec-to-card 产出的 spec 中,**slice 元素的 `name`(图层路径
|
|
|
140
140
|
| 职责 | 文件 / 函数 |
|
|
141
141
|
|---|---|
|
|
142
142
|
| 归一化 + 元素名↔导出匹配 + `/slices/` 路径生成 | `scripts/lib/slicePath.mjs`(`stripScale` / `resolveSliceExport` / `sliceFlatName` / `sliceRefPath`) |
|
|
143
|
-
|
|
|
144
|
-
| v3 变体(CLI 直组装)生成 Image 时引用 `/slices/` | `scripts/spec_to_schema_v3.mjs`(`imageFromSlice(sliceEl, sem.slicesExports)`,由 `extractSemantics` 透传 `spec.slices`) |
|
|
143
|
+
| v3 变体(CLI 直组装)生成 Image 时引用 `/slices/` | `scripts/spec_to_schema_v3.mjs`(`imageFromSlice(sliceEl, sem.slicesExports)`,由 `extractSemantics` 透传 `spec.slices`;geo 变体已随 geo 生成器删除) |
|
|
145
144
|
| 拷贝被引用切图到 `src/slices` | `scripts/generate_cards.mjs`(`collectSlicesToSrcSlices`,仅 referenced) |
|
|
146
145
|
| 本地 `/slices/*` 静态服务(自包含) | `scripts/render_card.mjs`(临时 HTTP 中间件,直接读 `slicesDir`) |
|
|
147
146
|
| ③ 阶段:上传切图 + 写映射 + 改写引用 | `skills/finmall-card-sync/scripts/slice_upload.mjs`(`uploadSlices`);HTTP 端点 `server/core/sliceUpload.js`(`POST /__slice-upload`);编辑器入口 `card-editor/App.vue`(`uploadCardSlices`);MCP `card_slice_upload` |
|
|
@@ -155,7 +154,7 @@ sketch-spec-to-card 产出的 spec 中,**slice 元素的 `name`(图层路径
|
|
|
155
154
|
| 存量切图归一化(语义化改名 + 同内容合并 + 引用/manifest 成套改写;**默认 dry-run**) | `scripts/slices_normalize.mjs`(`--write` 落盘;`--dedupe` / `--prune-unreferenced` / `--rename old=new` / `--map file.json` / `--no-legacy` / `--json`;内置 `LEGACY_RENAMES` = 2026-09-17 逐张核图确认的存量映射) |
|
|
156
155
|
| 上传阶段同内容复用线上地址(`reusedFrom`) | `skills/finmall-card-sync/scripts/slice_upload.mjs`(`shaToUploaded` 索引 + 批次内延后复用) |
|
|
157
156
|
|
|
158
|
-
>
|
|
157
|
+
> v3 生成器引用切片必须走 `sliceRefPath()`(产出 `/slices/<flat>`),与其 `collectSlicesToSrcSlices` 拷贝目标(扁平 `<flat>`)严格对应,禁止各自拼字符串。
|
|
159
158
|
|
|
160
159
|
## 7. 常见错误
|
|
161
160
|
|
|
@@ -376,9 +376,6 @@ node skills/dsl-card-generator/scripts/render_card.mjs --id <id> --width 375 --o
|
|
|
376
376
|
|
|
377
377
|
**纪律**:spec 的 textAlign 是权威信息,任何语义块提取/渲染都要**原样传递 align**;主金额必须是金额(不是任何大字号数字)。
|
|
378
378
|
|
|
379
|
-
## 8. 闭环验证(同 §6)
|
|
380
|
-
|
|
381
|
-
|
|
382
379
|
## 8. 环形倒计时(Canvas 自绘,v3 自动识别)
|
|
383
380
|
|
|
384
381
|
**识别信号**(`extractSemantics` 的 `countdown`,纯几何/格式,泛化):
|
|
@@ -473,7 +470,7 @@ node skills/dsl-card-generator/scripts/render_card.mjs --id <id> --width 375 --o
|
|
|
473
470
|
**配合关系**:拓扑生成不应只靠下游自算——上游(sketch-spec-to-card)在解析阶段就按**几何 y 聚类**产出每卡的视觉分段,下游直接消费,两端同一套哲学(设计稿节点结构不作拓扑依据)。
|
|
474
471
|
|
|
475
472
|
**上游产出(parse_meaxure.py::detect_segments + build_prompt.py)**:
|
|
476
|
-
- `spec.json` 每卡新增 `segments[]`:`[{y, height,
|
|
473
|
+
- `spec.json` 每卡新增 `segments[]`:`[{y, height, hint, items}]`(卡内相对坐标)。hint ∈ decor-bg(装饰背景层,强制独立段)/ button / content。段间距不落盘(旧字段 `gap_top` 已删——首段恒等于 y、后续段由相邻 y/height 推导,见 `skills/shared/spec-contract.md` removed 档)。
|
|
477
474
|
- 排除:卡片容器本身(symbol 尺寸≈整卡)、卡外装饰(全屏背景/蒙版,卡 y 轴交集<50%)、元素 gap 阈值=卡高 2%。
|
|
478
475
|
- `prompt.md` §3 新增「视觉分段(几何推断,供拓扑生成)」段,并写明「设计稿 group/编组结构不作卡片拓扑依据」。
|
|
479
476
|
|
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
/** 跨平台 Python 探测:DSL_CARD_PYTHON > WORKBUDDY_PYTHON > python3 > python */
|
|
5
|
-
function findPython() {
|
|
6
|
-
if (process.env.DSL_CARD_PYTHON) return process.env.DSL_CARD_PYTHON;
|
|
7
|
-
if (process.env.WORKBUDDY_PYTHON) return process.env.WORKBUDDY_PYTHON;
|
|
8
|
-
for (const bin of ['python3', 'python']) {
|
|
9
|
-
try {
|
|
10
|
-
if (spawnSync(bin, ['--version'], { stdio: 'ignore' }).status === 0) return bin;
|
|
11
|
-
} catch {}
|
|
12
|
-
}
|
|
13
|
-
return 'python3';
|
|
14
|
-
}
|
|
2
|
+
// Python 解释器解析集中在 lib/python.mjs(单一来源;此处取候选列表以获得真实回退链)。
|
|
3
|
+
import { pythonCandidates } from './lib/python.mjs'
|
|
15
4
|
/**
|
|
16
5
|
* add_component.mjs — 向已有卡片的某个容器节点插入一个「合法最小组件」,避免手写样板。
|
|
17
6
|
*
|
|
@@ -95,7 +84,9 @@ const setMsg = r.setProps.length ? ` set[${r.setProps.join(', ')}]` : ''
|
|
|
95
84
|
console.log(`[ok] 已在 ${r.parentId}(${r.parentClassName}) 插入 ${r.className} -> ${r.id}${setMsg}`)
|
|
96
85
|
|
|
97
86
|
// 复校验
|
|
98
|
-
|
|
87
|
+
// 用候选列表而非「最优单个」:首选解释器若跑不通(假别名/包装器),还能回退到下一个,
|
|
88
|
+
// 否则这里的 validate 会静默跳过(历史上正是这个原因让复校验形同不存在)。
|
|
89
|
+
const PY_CANDIDATES = pythonCandidates()
|
|
99
90
|
let validated = false
|
|
100
91
|
for (const py of PY_CANDIDATES) {
|
|
101
92
|
try {
|
|
@@ -7,9 +7,24 @@ autofix_schema.py — 机械修复「契约级」高频错误,把「校验-修
|
|
|
7
7
|
2. itemValueFrom == "2" 但值是 $ 表达式 → 翻回 "1"(R25:图片槽只放文件值,表达式在原样透传下渲染空白)。
|
|
8
8
|
3. 重复 componentId → 重编号为全局连续唯一 comp_N。
|
|
9
9
|
4. 缺失 data.json 或 key 与 inputParams 漂移 → 由 inputParams.defaultValue 重新派生并写回。
|
|
10
|
+
5. 列表作用域别名未归一化($item.<字段> / $item / $item._index)→ 改写为规范形(见下)。
|
|
10
11
|
|
|
11
12
|
不处理需要语义判断的 WARNING(未知样式子键、flex 枚举等)——那些留给人工/卡片作者。
|
|
12
13
|
|
|
14
|
+
── 关于规则 5(2026-09-24 复盘)────────────────────────────────────
|
|
15
|
+
`$item.xxx` 不是模型的问题,是**我们自己**在系统提示词 / ir-format.md 里手写了一条
|
|
16
|
+
不存在的契约(提交 56e9894),模型只是照做。运行时契约(card-editor/runtime/evaluate.js
|
|
17
|
+
的 makeSlotScope)是 `slotScope = 外层 ∪ item 字段逐个展开 ∪ {$$: item, _index: 索引}`
|
|
18
|
+
—— **没有 `item` 这个键**;全仓 50 张真卡的列表插槽绑定 100% 用扁平字段名。
|
|
19
|
+
|
|
20
|
+
处理方式是**编译器归一化**而不是「报错禁止」:IR / schema 都是本编译器的输入,
|
|
21
|
+
对公认别名做语义保持的改写即可,规范形(`$字段` / `$$` / `$_index`)由生成器保证。
|
|
22
|
+
唯一例外闸:卡片真的声明了名为 `item` 的根变量(inputParams[].key)时整体跳过。
|
|
23
|
+
|
|
24
|
+
⚠️ 实现与 JS 侧 skills/dsl-card-generator/scripts/lib/scope_alias.mjs **逐字对齐**,
|
|
25
|
+
三条正则显式写 [A-Za-z0-9_](Python 的 \w 默认是 Unicode 集,含中文,与 JS 的 ASCII \w
|
|
26
|
+
不同 —— 用 \w 会让 `$item名称` 在两侧得到不同结果)。改动请同步两侧。
|
|
27
|
+
|
|
13
28
|
用法:
|
|
14
29
|
python3 autofix_schema.py <schema.json> # 修复并写回
|
|
15
30
|
python3 autofix_schema.py <schema.json> --check # 只报告,不写回
|
|
@@ -157,6 +172,71 @@ def walk_fix_expr_slot(node, fixed):
|
|
|
157
172
|
return changed
|
|
158
173
|
|
|
159
174
|
|
|
175
|
+
_SCOPE_ALIAS_SKIP_KEYS = frozenset({
|
|
176
|
+
'id', 'name', 'itemId', 'itemKey', 'itemName', 'itemType', 'itemValueFrom',
|
|
177
|
+
'componentId', 'className', 'eventKey', 'eventName', 'actionType', 'slotKey',
|
|
178
|
+
'type', 'desc', 'remark',
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
# ⚠️ 与 skills/dsl-card-generator/scripts/lib/scope_alias.mjs 的三条正则逐字一致。
|
|
182
|
+
# Python 的 \w 默认匹配 Unicode(含中文),JS 的 \w 只匹配 ASCII —— 故此处显式写字符类。
|
|
183
|
+
_SCOPE_ALIAS_RE_INDEX = re.compile(r'\$item\._index(?![A-Za-z0-9_])')
|
|
184
|
+
_SCOPE_ALIAS_RE_FIELD = re.compile(r'\$item\.')
|
|
185
|
+
_SCOPE_ALIAS_RE_BARE = re.compile(r'\$item(?![A-Za-z0-9_$])')
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def rewrite_item_alias(s):
|
|
189
|
+
"""$item.<字段> → $<字段>;$item → $$;$item._index → $_index。纯函数。"""
|
|
190
|
+
if not isinstance(s, str) or '$item' not in s:
|
|
191
|
+
return s
|
|
192
|
+
out = _SCOPE_ALIAS_RE_INDEX.sub('$_index', s)
|
|
193
|
+
out = _SCOPE_ALIAS_RE_FIELD.sub('$', out)
|
|
194
|
+
out = _SCOPE_ALIAS_RE_BARE.sub('$$', out)
|
|
195
|
+
return out
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def walk_fix_scope_alias(node, fixed, known_roots, at='view'):
|
|
199
|
+
"""规则 5:列表作用域别名归一化(原地改写 schema 里的绑定表达式)。
|
|
200
|
+
|
|
201
|
+
规范形(schema 只存这三种):`$<字段名>` / `$$` / `$_index`。
|
|
202
|
+
可接受别名(作者侧随便写):`$item.<字段>` / `$item` / `$item._index`。
|
|
203
|
+
"""
|
|
204
|
+
if 'item' in known_roots:
|
|
205
|
+
# 卡片真的声明了名为 item 的根变量 ⇒ $item.x 是合法取值,一律不动
|
|
206
|
+
return False
|
|
207
|
+
changed = False
|
|
208
|
+
|
|
209
|
+
def visit(container, path):
|
|
210
|
+
nonlocal changed
|
|
211
|
+
if isinstance(container, list):
|
|
212
|
+
for i, v in enumerate(container):
|
|
213
|
+
if isinstance(v, str):
|
|
214
|
+
new = rewrite_item_alias(v)
|
|
215
|
+
if new != v:
|
|
216
|
+
container[i] = new
|
|
217
|
+
fixed.append(f'作用域别名归一化: {path}[{i}] {v[:60]} → {new[:60]}')
|
|
218
|
+
changed = True
|
|
219
|
+
else:
|
|
220
|
+
visit(v, f'{path}[{i}]')
|
|
221
|
+
return
|
|
222
|
+
if not isinstance(container, dict):
|
|
223
|
+
return
|
|
224
|
+
for k, v in list(container.items()):
|
|
225
|
+
if isinstance(v, str):
|
|
226
|
+
if k in _SCOPE_ALIAS_SKIP_KEYS:
|
|
227
|
+
continue
|
|
228
|
+
new = rewrite_item_alias(v)
|
|
229
|
+
if new != v:
|
|
230
|
+
container[k] = new
|
|
231
|
+
fixed.append(f'作用域别名归一化: {path}.{k} {v[:60]} → {new[:60]}')
|
|
232
|
+
changed = True
|
|
233
|
+
else:
|
|
234
|
+
visit(v, f'{path}.{k}')
|
|
235
|
+
|
|
236
|
+
visit(node, at)
|
|
237
|
+
return changed
|
|
238
|
+
|
|
239
|
+
|
|
160
240
|
def fix_duplicate_ids(node, fixed):
|
|
161
241
|
seen = set()
|
|
162
242
|
duplicated = False
|
|
@@ -240,6 +320,14 @@ def autofix(path, check=False, no_data=False):
|
|
|
240
320
|
walk_fix_value_from(schema.get('view', {}), fixed)
|
|
241
321
|
walk_fix_expr_slot(schema.get('view', {}), fixed)
|
|
242
322
|
walk_fix_defaults(schema.get('view', {}), fixed)
|
|
323
|
+
# 规则 5:作用域别名归一化。走整份 schema(与 JS 侧 walk 整个 IR 对齐),
|
|
324
|
+
# 早于 validate —— 归一化后绑定才可解析,不该让 validate 看见别名形态。
|
|
325
|
+
known_roots = {
|
|
326
|
+
p.get('key')
|
|
327
|
+
for p in (schema.get('dictionary', {}) or {}).get('inputParams', []) or []
|
|
328
|
+
if isinstance(p, dict) and p.get('key')
|
|
329
|
+
}
|
|
330
|
+
walk_fix_scope_alias(schema, fixed, known_roots)
|
|
243
331
|
fix_duplicate_ids(schema.get('view', {}), fixed)
|
|
244
332
|
fix_data_json(schema, schema_dir, fixed, skip=no_data)
|
|
245
333
|
|