cardbuddy 0.22.0 → 0.24.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/README.md +11 -11
- package/dist/cli.js +2 -2
- package/dist/config.js +3 -3
- package/dist/init.js +13 -13
- package/dist/mcp.js +1 -1
- package/dist/preview-editor/assets/{index-CPpLZZyi.js → index-BumNBo-n.js} +81 -80
- package/dist/preview-editor/assets/{index-CU8zVA2P.css → index-MWKPP9nd.css} +1 -1
- package/dist/preview-editor/index.html +2 -2
- package/dist/skills-upgrade.js +1 -1
- package/dist/standalone.js +2 -2
- package/dist/vendored/card-editor/runtime/adapter.js +10 -11
- package/dist/vendored/mcp/client.mjs +50 -13
- package/dist/vendored/mcp/tools.mjs +13 -7
- package/dist/vendored/server/core/ai-chat.js +540 -183
- package/dist/vendored/server/core/assetProxy.js +1 -1
- package/dist/vendored/server/core/browser.js +1 -1
- package/dist/vendored/server/core/cardCompile.js +202 -0
- package/dist/vendored/server/core/cardDigest.js +324 -0
- package/dist/vendored/server/core/cardExport.js +5 -5
- package/dist/vendored/server/core/cardFiles.js +1 -1
- package/dist/vendored/server/core/cardHistoryRemote.js +1 -1
- package/dist/vendored/server/core/cardImportMeta.js +1 -1
- package/dist/vendored/server/core/cardListCache.js +92 -10
- package/dist/vendored/server/core/cardPublish.js +11 -6
- package/dist/vendored/server/core/cardPublishVersions.js +8 -8
- package/dist/vendored/server/core/cardSave.js +14 -10
- package/dist/vendored/server/core/cardSidecar.js +3 -3
- package/dist/vendored/server/core/cardSync.js +35 -23
- package/dist/vendored/server/core/cardVersion.js +1 -1
- package/dist/vendored/server/core/flameCompile/client.js +466 -0
- package/dist/vendored/server/core/flameCompile/clientElements.js +372 -0
- package/dist/vendored/server/core/flameCompile/common.js +253 -0
- package/dist/vendored/server/core/flameCompile/h5.js +326 -0
- package/dist/vendored/server/core/flameCompile/index.js +126 -0
- package/dist/vendored/server/core/flameCompile/model.js +162 -0
- package/dist/vendored/server/core/index.js +12 -6
- package/dist/vendored/server/core/logger.js +1 -1
- package/dist/vendored/server/core/remoteCard.js +5 -5
- package/dist/vendored/server/core/skillPaths.js +1 -1
- package/dist/vendored/server/core/sliceUpload.js +4 -4
- package/dist/vendored/skills/README.md +13 -13
- package/dist/vendored/skills/card-doctor/SKILL.md +2 -2
- package/dist/vendored/skills/dsl-card-ai/SKILL.md +4 -4
- package/dist/vendored/skills/dsl-card-generator/SKILL.md +2 -2
- package/dist/vendored/skills/dsl-card-generator/references/amount_formatting.md +1 -1
- package/dist/vendored/skills/dsl-card-generator/references/build_protocol.md +54 -0
- package/dist/vendored/skills/dsl-card-generator/references/component_rules.md +2 -2
- package/dist/vendored/skills/dsl-card-generator/references/converge_protocol.md +36 -0
- package/dist/vendored/skills/dsl-card-generator/references/event_patterns.md +1 -1
- package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +1 -1
- package/dist/vendored/skills/dsl-card-generator/references/image_to_card.md +1 -1
- package/dist/vendored/skills/dsl-card-generator/references/quickref.md +1 -1
- package/dist/vendored/skills/dsl-card-generator/references/recipes.md +4 -4
- package/dist/vendored/skills/dsl-card-generator/references/rules.md +1 -1
- package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +6 -6
- package/dist/vendored/skills/dsl-card-generator/scripts/inventory_schemas.py +1 -1
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/scaffold.mjs +1 -1
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/slice_dedupe.mjs +2 -2
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-CeVX_y-M.js +137 -0
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-DiEiY59H.css +1 -0
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/standalone.html +2 -2
- package/dist/vendored/skills/{finmall-card-sync → platform-card-sync}/SKILL.md +26 -26
- package/dist/vendored/skills/{finmall-card-sync → platform-card-sync}/scripts/api_batch_sync.mjs +184 -162
- package/dist/vendored/skills/{finmall-card-sync → platform-card-sync}/scripts/slice_upload.mjs +48 -29
- package/dist/vendored/skills/{finmall-session-navigator → platform-session-navigator}/SKILL.md +13 -13
- package/dist/vendored/skills/{finmall-session-navigator → platform-session-navigator}/scripts/package-lock.json +2 -2
- package/dist/vendored/skills/{finmall-session-navigator → platform-session-navigator}/scripts/package.json +2 -2
- package/dist/vendored/skills/{finmall-session-navigator/scripts/finmall_login.mjs → platform-session-navigator/scripts/platform_login.mjs} +41 -31
- package/dist/vendored/skills/{finmall-session-navigator → platform-session-navigator}/scripts/session-manager.mjs +17 -17
- package/dist/vendored/skills/shared/README.md +21 -21
- package/dist/vendored/skills/shared/auth-model.md +8 -10
- package/dist/vendored/skills/shared/{finmall-config.mjs → platform-config.mjs} +10 -10
- package/dist/vendored/tools/{finmall-env.mjs → platform-env.mjs} +13 -13
- package/package.json +1 -1
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-BCLshIzC.css +0 -1
- package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-pP5kTKiI.js +0 -88
|
@@ -3,9 +3,9 @@ import { log } from './logger.js'
|
|
|
3
3
|
import { resolveSkillPathOrProject } from './skillPaths.js'
|
|
4
4
|
|
|
5
5
|
/** 卡片元数据(列表字典 / 新建 / 编辑)所在脚本,相对 skills/ */
|
|
6
|
-
const SYNC_SCRIPT_REL = '
|
|
6
|
+
const SYNC_SCRIPT_REL = 'platform-card-sync/scripts/api_batch_sync.mjs'
|
|
7
7
|
/** 图片上传(CC0000.do)所在脚本,与切图上传同一条链路 */
|
|
8
|
-
const SLICE_SCRIPT_REL = '
|
|
8
|
+
const SLICE_SCRIPT_REL = 'platform-card-sync/scripts/slice_upload.mjs'
|
|
9
9
|
|
|
10
10
|
/** 封面 base64 请求体上限:一张 2x 卡片封面通常 < 1MB,留足余量又不至于吃满内存 */
|
|
11
11
|
const MAX_BODY_BYTES = 12 * 1024 * 1024
|
|
@@ -71,11 +71,11 @@ function sendError(res, err, label) {
|
|
|
71
71
|
* - POST /__remote-card/delete → 删除卡片(IDC10003.do),body { cardId }(不可逆,前端须二次确认)
|
|
72
72
|
* - POST /__remote-card/cover → 上传封面图(CC0000.do),body { fileName, base64, mime? } → { url }
|
|
73
73
|
*
|
|
74
|
-
* 为什么必须经 Node
|
|
75
|
-
* 纯前端发不出去。这里复用
|
|
74
|
+
* 为什么必须经 Node 中转:远端接口需要 `token` 头(值只在页面上下文内可读)、且跨域,
|
|
75
|
+
* 纯前端发不出去。这里复用 platform-card-sync 的后台会话(与同步/切图上传同一个 Chrome),
|
|
76
76
|
* 不额外引入登录态。
|
|
77
77
|
*
|
|
78
|
-
*
|
|
78
|
+
* 注意:这些接口只改远端侧的卡片元数据,不写本地 src/cards ——
|
|
79
79
|
* 本地目录由「下载选中 / 全部下载」按需拉取,避免远端建卡即污染工作区。
|
|
80
80
|
*
|
|
81
81
|
* @param {{ root: string }} opts
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - 包内快照: <pkg>/dist/vendored/skills (构建期从仓库 skills/ 内联,随包发布)
|
|
9
9
|
*
|
|
10
10
|
* 解析优先级(只读用途):configureSkillPaths 显式指定 > DSL_SKILLS_ROOT > <root>/skills > 包内快照。
|
|
11
|
-
*
|
|
11
|
+
* 写入用途(如远端凭据落盘)一律走 projectSkillsDir(root) —— 绝不写进 node_modules,
|
|
12
12
|
* 否则重装即丢、且多项目互相污染。
|
|
13
13
|
*/
|
|
14
14
|
import fs from 'node:fs'
|
|
@@ -4,15 +4,15 @@ import { log, fmtBytes } from './logger.js'
|
|
|
4
4
|
import { resolveSkillPathOrProject } from './skillPaths.js'
|
|
5
5
|
|
|
6
6
|
/** 切图上传脚本相对 skills/ 的路径 */
|
|
7
|
-
const UPLOAD_SCRIPT_REL = '
|
|
7
|
+
const UPLOAD_SCRIPT_REL = 'platform-card-sync/scripts/slice_upload.mjs'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* 顶栏「远端 → 切图上传」按钮的服务端处理:把卡片引用的 src/slices
|
|
10
|
+
* 顶栏「远端 → 切图上传」按钮的服务端处理:把卡片引用的 src/slices 切图上传到远端
|
|
11
11
|
* 图片库(CC0000.do),写「本地文件 ↔ 线上地址」映射(src/slices/manifest.json),
|
|
12
12
|
* 并把该卡 schema.json / data.json 里的 `/slices/<name>` 引用改写为返回的 `@filestore/...`。
|
|
13
13
|
*
|
|
14
|
-
* 复用
|
|
15
|
-
*
|
|
14
|
+
* 复用 platform-card-sync 的 uploadSlices(与「同步」「版本推送」共用同一后台远端会话)。
|
|
15
|
+
* 纯前端无法直接调远端:跨域 + 需 token(只在页面上下文内读取,不回传 Node),故必须经此中转。
|
|
16
16
|
*
|
|
17
17
|
* **只由用户点击触发**——没有任何自动上传路径:保存 / 推送 / 同步都不会顺带上传切图。
|
|
18
18
|
*
|
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
card-skill-trainer ────┘ (训练闭环反哺 generator 与 sketch-spec-to-card)
|
|
15
15
|
│
|
|
16
16
|
dsl-card-generator
|
|
17
|
-
│ 接受
|
|
17
|
+
│ 接受 platform-card-sync 同步进来的卡作为"导出实证"
|
|
18
18
|
▼
|
|
19
|
-
|
|
19
|
+
platform-card-sync ──────► platform-session-navigator
|
|
20
20
|
│ │
|
|
21
21
|
│ 复用 playwright 依赖 │ 启动 Chrome、维持登录态
|
|
22
22
|
▼ ▼
|
|
23
23
|
shared/
|
|
24
|
-
(
|
|
24
|
+
(platform-config.mjs
|
|
25
25
|
auth-model.md)
|
|
26
26
|
```
|
|
27
27
|
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
|
|
30
30
|
| skill | 职责 | 依赖 |
|
|
31
31
|
|---|---|---|
|
|
32
|
-
| **shared/** | 跨 skill 共享代码与文档:环境配置加载器(`
|
|
33
|
-
| **
|
|
34
|
-
| **
|
|
35
|
-
| **dsl-card-generator** | 从零/图片生成卡片、组件/表达式/事件深度语义、校验脚本库、范式片段库、**整卡模板库与沉淀闭环**(resolve 检索 → register 登记 → build 重建保留 pin)、组件使用分析、**建卡输入路由**(`scripts/route_input.mjs` / MCP `card_route_input` + SKILL.md §8 Workflow 0:识别到 Sketch MeaXure 标注导出时自动先转 sketch-spec-to-card 前处理,再回本 skill 逐卡生成)。是"卡片生成与契约"的权威方 | sketch-spec-to-card(MeaXure 导出输入时的强制上游);接受
|
|
32
|
+
| **shared/** | 跨 skill 共享代码与文档:环境配置加载器(`platform-config.mjs`)、鉴权与 Playwright 工程集中陈述(`auth-model.md`) | 无(最底层) |
|
|
33
|
+
| **platform-session-navigator** | 用 Playwright 独立启动 Chrome、自动登录远端 UAT、维持会话。所有需要登录态的下游 skill 都先调它 | shared |
|
|
34
|
+
| **platform-card-sync** | 通过 API 把远端卡片同步进 `src/cards/<id>/`(schema/data/封面)。复用 platform-session-navigator 的登录态与 playwright 依赖 | shared + platform-session-navigator 的登录态 |
|
|
35
|
+
| **dsl-card-generator** | 从零/图片生成卡片、组件/表达式/事件深度语义、校验脚本库、范式片段库、**整卡模板库与沉淀闭环**(resolve 检索 → register 登记 → build 重建保留 pin)、组件使用分析、**建卡输入路由**(`scripts/route_input.mjs` / MCP `card_route_input` + SKILL.md §8 Workflow 0:识别到 Sketch MeaXure 标注导出时自动先转 sketch-spec-to-card 前处理,再回本 skill 逐卡生成)。是"卡片生成与契约"的权威方 | sketch-spec-to-card(MeaXure 导出输入时的强制上游);接受 platform-card-sync 同步进来的卡作为 export-proven 证据 |
|
|
36
36
|
| **dsl-card-ai** | 在已有卡片上用 AI 编辑(修绑定 / 批量改 / 解释卡)+ **建卡入口路由**(Workflow D:MCP `card_route_input` 判输入形态,MeaXure 导出强制先转 sketch-spec-to-card,再交 dsl-card-generator 生成)。纯编排层,所有写卡收口是 `card_write` | dsl-card-generator 的 references + MCP 工具;sketch-spec-to-card(MeaXure 输入时的上游) |
|
|
37
37
|
| **sketch-spec-to-card** | 读取 Sketch MeaXure 标注导出,按画板聚焦提取卡片要素(prompt.md / spec.md / 截图 / 切图),产出到 UI/ 供 dsl-card-generator 消费。**用户把 MeaXure 导出丢给 dsl-card-generator 时会被自动路由到本 skill 前处理**(无需用户显式点名) | dsl-card-generator(下游消费方) |
|
|
38
38
|
| **card-skill-trainer** | 无人值守训练闭环:抽样设计稿 → 生成 → 结构/事实/像素评分 → 失败归因 → 反哺 sketch-spec-to-card 与 dsl-card-generator;lessons 经 `training/tools/aggregate_lessons.mjs` 聚合成提案人工审批落盘 | 被训练的两个 skill + training/ 设施 |
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
|
|
45
45
|
- **生成新卡 / 改组件结构 / 校验**:只读 `dsl-card-generator/SKILL.md` + `references/quickref.md`
|
|
46
46
|
- **AI 编辑已有卡(修绑定、批量改)**:读 `dsl-card-ai/SKILL.md`,需要字段细节时跳到 `dsl-card-generator/references/`
|
|
47
|
-
-
|
|
47
|
+
- **拉取/刷新远端卡**:先 `platform-session-navigator` 拿登录态,再 `platform-card-sync` 同步
|
|
48
48
|
- **跨 skill 排查会话/鉴权问题**:直接读 `shared/auth-model.md`(唯一权威方)
|
|
49
49
|
- **存量卡体检 / 治理 lint 存量**:读 `card-doctor/SKILL.md`,先跑 `node tools/card-doctor.mjs` 再看报告
|
|
50
50
|
- **建卡提速(模板复用/沉淀)**:先 `resolve_card_template.mjs <意图>` 查模板库;新模式交付后经 `register_card_template.mjs --check` 确认再登记
|
|
@@ -57,15 +57,15 @@
|
|
|
57
57
|
skills/
|
|
58
58
|
├── README.md ← 本文件
|
|
59
59
|
├── shared/
|
|
60
|
-
│ ├──
|
|
60
|
+
│ ├── platform-config.mjs ← 环境配置加载器(baseUrl/appId/cdpPort/...)
|
|
61
61
|
│ └── auth-model.md ← 鉴权模型 + Playwright 工程集中陈述
|
|
62
|
-
├──
|
|
63
|
-
│ ├── SKILL.md ←
|
|
62
|
+
├── platform-session-navigator/
|
|
63
|
+
│ ├── SKILL.md ← 自动登录远端
|
|
64
64
|
│ └── scripts/
|
|
65
|
-
│ ├──
|
|
65
|
+
│ ├── platform_login.mjs ← 主脚本
|
|
66
66
|
│ ├── session-manager.mjs ← 会话管理辅助
|
|
67
67
|
│ └── package.json ← playwright 依赖
|
|
68
|
-
├──
|
|
68
|
+
├── platform-card-sync/
|
|
69
69
|
│ ├── SKILL.md ← API 同步卡片到 src/cards/
|
|
70
70
|
│ └── scripts/
|
|
71
71
|
│ └── api_batch_sync.mjs ← 批量同步脚本
|
|
@@ -32,7 +32,7 @@ node tools/card-doctor.mjs --out .codex-tmp/doctor.json # 落盘 JSON 报告(.
|
|
|
32
32
|
|---|---|---|---|
|
|
33
33
|
| `validate` | `validate_schema.py`(离线) | 硬契约 | 结构/绑定/data 一致性;error 必须清零 |
|
|
34
34
|
| `lint` | `lint_card.mjs --all --json`(离线) | 体验软规 | error(如按钮缺 click、itemId 错配)应清零;warn/info 按存量分批治理 |
|
|
35
|
-
| `files.cover` | 文件系统 | 完整性 | 缺 `<id>.png`
|
|
35
|
+
| `files.cover` | 文件系统 | 完整性 | 缺 `<id>.png` 封面的卡无法走远端封面上传链路 |
|
|
36
36
|
| `files.data` | 文件系统 | 完整性 | data.json 缺失或 JSON 解析失败 |
|
|
37
37
|
| `render`(可选) | dev server `/__render-status/:id` | SSR 自检 | bindingErrors / runtimeErrors;离线标 `skipped` 不算失败 |
|
|
38
38
|
|
|
@@ -49,4 +49,4 @@ node tools/card-doctor.mjs --out .codex-tmp/doctor.json # 落盘 JSON 报告(.
|
|
|
49
49
|
|
|
50
50
|
- **不检查视觉还原度**:与设计稿的像素级比对归 `dsl-card-generator` 的 fidelity 工具链(check_fidelity / render_diff)。
|
|
51
51
|
- **不替代训练**:生成质量优化归 `card-skill-trainer`;doctor 管存量卡健康。
|
|
52
|
-
-
|
|
52
|
+
- **不碰远端**:远端一致性归 `platform-card-sync` / `card_sync`。
|
|
@@ -23,7 +23,7 @@ dsl-card 项目的 AI 技能层。它是**编排层**:把卡片编辑任务拆
|
|
|
23
23
|
4. **读字段结构用 `card_node_models`**:不要去解析 raw DSL + component-meta。该工具返回每节点的可编辑字段树。
|
|
24
24
|
5. **render_status.ok ≠ 画对了**(rules.md §七):视觉正确性以 `card_screenshot` 为准。**但视觉验证只对 UI 变更做**(rules.md §七.1 铁律):改 `style.*` / 节点增删移动 / 静态文案 / 图片资源 / v-show 条件才截图;改绑定表达式取值逻辑 / `inputParams` / 事件代码 / `data.json` 属非 UI 变更,用单元断言收口(`evalExpr` 比对返回值、`new Function(code).call(假 this)` 跑 customAction 断言 `state`),不截图。
|
|
25
25
|
6. **改坏可回滚**:`card_write` 每次写卡前自动把当前版本存为历史快照(与编辑器改卡同语义);编辑失误时用 `card_rollback`(默认回滚到最新快照)撤销,用 `card_history` 挑选更早的回滚目标。
|
|
26
|
-
7. **dev server 前置**:`card_render_status` / `card_node_models` / `card_import_meta` 必须 dev server 已启动,未起时返回 `dev-server-required`;`card_sync` / `card_save` / `card_slice_upload` 除 dev server
|
|
26
|
+
7. **dev server 前置**:`card_render_status` / `card_node_models` / `card_import_meta` 必须 dev server 已启动,未起时返回 `dev-server-required`;`card_sync` / `card_save` / `card_slice_upload` 除 dev server 外还需已登录远端。(本仓库 `npm run dev` 默认 5180,由 Vite 把 `/__*` 代理到 8787;`cardbuddy init` 出来的工程 `npm run dev` 直接是 8787。)`card_list` / `card_read` / `card_write` / `card_validate` / `card_history` / `card_rollback` 会自动降级为直接读盘/写盘/跑 Python(render_status 标 skipped);`card_screenshot` 降级到包内独立离线渲染器(`source:'standalone'`)。
|
|
27
27
|
8. **建卡先判输入形态**(Workflow D):收到设计稿输入先调 `card_route_input({path})`,按返回的 `kind` 分流,**不要凭感觉决定要不要跑上游**。判为 `meaxure-export`(Sketch MeaXure 标注导出)时必须先经 `sketch-spec-to-card` 前处理产出 `UI/<稿名>/` 要素包,**禁止直接读 `preview/` 整图建卡**;`preprocess.needed=false` 表示已前处理过,直接复用存量产物。`card_route_input` 纯离线,不需要 dev server。
|
|
28
28
|
|
|
29
29
|
## 前置:`card_*` 工具从哪来(agent 里看不到工具先看这里)
|
|
@@ -91,13 +91,13 @@ printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
|
|
|
91
91
|
| `card_screenshot` | 无头截图(json `{base64}` / png 图片) | 优先 dev server;未起时降级到包内独立离线渲染器(返回 `source:'standalone'`),渲染器也失败才报 `dev-server-required` |
|
|
92
92
|
| `card_history` | 编辑历史快照元数据(ts/时间/字节数/节点数),用于选择回滚目标 | 直接读 `.history/` |
|
|
93
93
|
| `card_rollback` | 回滚到指定快照(默认最新);回滚前自动把当前版本另存为安全快照,回滚本身可撤销;写回走 card_write 同一收口 | 直接读写盘 + Python 硬校验 |
|
|
94
|
-
| `card_sync` |
|
|
95
|
-
| `card_save` |
|
|
94
|
+
| `card_sync` | 从远端拉最新卡写回 `src/cards/` | 不支持 |
|
|
95
|
+
| `card_save` | 把卡片经已登录浏览器写回远端(IDC10010) | 不支持 |
|
|
96
96
|
| `card_route_input` | **建卡输入形态判定**(Workflow D):`kind`(`meaxure-export` / `ui-bundle` / `card-dir` / `image` / `unknown`)+ `evidence` 判定证据 + `next` 下一步命令 + `preprocess`(是否需要先跑 sketch-spec-to-card、稿名、产物路径) | 纯离线(无需 dev server) |
|
|
97
97
|
| `card_scaffold` | 新建卡骨架双模:`intent`(`blank`/`list`/`detail`/`form`/`dialog`/`tab`/`canvas-head` 的合法最小骨架,`data.json` 由 `inputParams` 反推自洽)或 `from`(整卡模板克隆,只重写 pageId/name,版式纪律与绑定范式原样继承);返回 `{ok, id, dir, intent 或 from, note?}` | 纯离线(无需 dev server) |
|
|
98
98
|
| `card_add_component` | 向指定**容器**节点(有 slots,或 ListLayout/GridLayout)的默认插槽追加一个合法最小组件;返回 `{ok, inserted:{componentId, className, parentId, parentClassName}, setProps, dir, validate}` —— 插完即离线跑 Python 硬校验并带回。纪律:只增不改、不重排 componentId、不碰孤儿字段 | 纯离线(无需 dev server) |
|
|
99
99
|
| `card_import_meta` | 单卡的「导入来源」侧车(Phase E 可追溯性):`{source, interface, baseUrl, appId, workspaceId, cardId, cardCode, cardName, syncedAt, tool, dirty, lastExport}`,据此判断这张卡是否从远端同步来、何时导入、上次导出读回校验是否通过;无侧车(本地新建 / 未同步)返回 `{error:"import-meta not found"}`。只读,不污染 DSL | 不支持(需 dev server) |
|
|
100
|
-
| `card_slice_upload` | 把该卡引用的本地切图(`src/slices
|
|
100
|
+
| `card_slice_upload` | 把该卡引用的本地切图(`src/slices`)经已登录浏览器上传到远端图片库(CC0000.do),写「本地文件 ↔ 线上地址」映射 `src/slices/manifest.json`,并把 schema/data 里的 `"/slices/<name>"` 改写为 `@filestore/…`;按 sha256 判重,内容未变的切图复用已有地址不重复上传,本地切图真源保留不动 | 不支持(需 dev server **且**已登录远端) |
|
|
101
101
|
|
|
102
102
|
> **分工提示**:`card_scaffold` / `card_add_component` 属**生成侧**(P5 脚手架),权威用法在
|
|
103
103
|
> `dsl-card-generator`(其 SKILL.md §8 Workflow 0 是建卡入口路由的准绳)。本技能只在 Workflow D
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dsl-card-generator
|
|
3
|
-
description:
|
|
3
|
+
description: 生成、编辑远端/YNet 低代码卡片 schema.json + data.json(15 个可渲染组件、$ 表达式绑定、v-show、globalSet、alertCard、Canvas 自绘)。适用:从图片/设计稿/MeaXure 导出生成卡片、编辑已有卡片、逆向解释 schema。触发词:图片生成卡片、读图建卡、编辑卡片、改卡片。核心流程:route_input 判输入形态 →(MeaXure 导出先经 sketch-spec-to-card 前处理)→ 整树 IR 一次下发建卡 → validate+lint 双门禁 → iterate 视觉闭环(改卡用 add_component/ir_patch)。常规任务只读 references/quickref.md,避免加载全部 references。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# DSL 卡片 Schema 生成器(DSL Card Schema Generator)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
为远端/YNet 卡片设计器生成合规的 schema.json + data.json。本 skill 把平台当作一套**带版本号的 JSON DSL**:卡片 = 数据字典 + 组件树 + 属性元数据 + 样式分组 + 指令 + 插槽 + 事件动作。一切结构都有据可查,不臆造。
|
|
9
9
|
|
|
10
10
|
> **图片/AI 生成卡片 = 你(Agent)直接读图产出 schema,不接外部接口**:读截图/设计稿(多模态),按组件/样式/表达式/事件规则直接写出合规 schema.json + data.json,再用内置校验器自查。详见 §8 Workflow A 与 references/image_to_card.md。
|
|
11
11
|
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
1. **参数类型默认 `number`。**
|
|
23
23
|
金额字段在 `dictionary.inputParams` 中声明为 `type:"number"`,`defaultValue` 给数字字面量(如 `8900`、`12300.5`),`data.json` 同以数字写入(见 §5)。
|
|
24
|
-
-
|
|
24
|
+
- **与上游的兼容**:部分远端接口(如 multiMonthTransitionList 的 IDC10012)以**字符串**投喂金额。表达式里的 `* 1` 会强制数值化,故无论上游给 number 还是 string 都能正确格式化——这种情况下该卡可保留 `type:"string"`,但表达式写法不变。
|
|
25
25
|
2. **显示用表达式做千分号,不要预拼格式化字符串进 data。**
|
|
26
26
|
即:Text 的 `value`(`itemValueFrom:"1"`)绑定一个 `$变量`,其 `itemValue` 是**格式化表达式**,而非已带逗号的字符串。这样一份数据既能展示(带千分号)又能参与计算(取原始数值)。
|
|
27
27
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# 建卡协议(S0~S4.5):从输入到 IR 落盘
|
|
2
|
+
|
|
3
|
+
> 本文件是 system 提示词里「建卡流程骨架」的**全文**。判定为建卡流程时必须先读本文件再动手。
|
|
4
|
+
> 收敛阶段(S4.9~S6)在 converge_protocol.md,两段按阶段读,不要合并成一次读。
|
|
5
|
+
|
|
6
|
+
## S0 路由
|
|
7
|
+
输入形态已由服务端机械判定,写在「用户挂载的本地文件」段(kind / preprocess / next):照它执行,不要再调 card_route_input;该段缺失才自己调 card_route_input({ path })。
|
|
8
|
+
|
|
9
|
+
## S1 要素包
|
|
10
|
+
kind=meaxure-export(Sketch MeaXure 标注导出)必须先有 sketch-spec-to-card 要素包,禁止直接读 preview/ 整图建卡。
|
|
11
|
+
- fs_read 判定里给出的 manifest(通常 UI/manifest.json)找目标画板条目:status=generated ⇒ 产物已在;status=pending ⇒ 先看 preprocess.alsoFound(同一导出的其它产物目录,平铺与按稿分片常并存),有就直接用;确实都没有才补跑:
|
|
12
|
+
① skill_exec script=skills/sketch-spec-to-card/scripts/parse_meaxure.py,args=["--src","<导出目录>","--list"] 拿画板清单(**每行行尾自带该画板的 --pick,板号 1 起**);
|
|
13
|
+
② args=["--src","<导出目录>","--out","UI","--draft","<稿名>","--pick","<照抄清单行尾>","--build"] 只产该画板(**照抄行尾的 --pick,禁止把序号换算成 0 起的 --indices**——实测换算错一位,白跑 34s 解析 + 一步 LLM 才发现;多张逗号分隔,连板写 7~9、同板连卡写 8.1~8.2);
|
|
14
|
+
③ script=skills/sketch-spec-to-card/scripts/cluster_states.py,args=["--out","UI/<稿名>"]。
|
|
15
|
+
- 要素包落点记作 <bundle>:传了 --draft 在 UI/<稿名>/<画板名>/,否则 UI/<画板名>/;以 parse_meaxure.py 打印的落盘路径为准。仓库可能两种布局并存,manifest 说的画板在 UI/ 下找不到就用 fs_list UI 找同名目录,别急着判定产物缺失。
|
|
16
|
+
|
|
17
|
+
## S1.4 批量建卡
|
|
18
|
+
(用户一次点了多张卡 / 回了「全部」):**一次** parse_meaxure --pick 出全部要素包(别每卡重跑一遍解析),然后**同一个回合内**按「S2→S5 走完一张,再走下一张」连续推进——每张卡各写各的 IR、各跑各的 expand + iterate。禁止每张卡各起一个回合:新回合的首步要把整段对话重发一遍(实测 1.3-3 万 token 一步),还要把同一个要素包重读一遍,纯浪费。
|
|
19
|
+
|
|
20
|
+
## S1.5 选卡门禁
|
|
21
|
+
(多画板 / 多卡画板必需;SKILL.md §13 + rules.md §13.4):目标目录定下后先跑 skill_exec script=skills/dsl-card-generator/scripts/spec_tree.mjs,args=["--list-cards","<目标目录>"] 拿「画板序号.卡片序号」全量清单(一个画板含 N 张卡展开为 K.1~K.N,行下自带 "图: <card-N.png 绝对路径>");S0 挂载段已带同一份命令就直接复用,不要重复跑。
|
|
22
|
+
- 只有 1 张卡、或用户已明说目标(序号如 2.3 / 画板名 / 卡片名)⇒ 直接进 S2,不要为走流程停下来问。
|
|
23
|
+
- 多张卡且用户未指定 ⇒ **本轮到此为止**:把清单写进正文 + 明确提问,正文最后**单独一行输出 [需用户确认]**,然后停止输出、等用户回序号再继续。没有这行标记会被判定「任务未完成就停手」而自动续跑(实测多花一整轮重发清单)。聊天面板**没有**弹选项工具(不存在 request_user_input),不要去调用或寻找弹窗工具,也不要替用户挑。
|
|
24
|
+
清单**逐行配图**:每个卡片行下紧跟一行 Markdown 图片,写成 !\[画板名\](该卡 card-N.png 的完整绝对路径),与序号一一对应——**必须是 !\[\](...) 包起来的图片语法**;parse_meaxure 输出里的 "图: <路径>" 行是给你看的提示,照抄成裸路径渲染端不显示(实测照抄后用户 0 张图可看);缺图显式标缺失,禁止把图堆到清单末尾。末尾追加一项「全部」,用户回「全部」才算显式批量授权(产了几张卡要在正文报)。
|
|
25
|
+
**清单必须以普通 Markdown 列表逐行输出,禁止用代码块围栏(``` 或 ~~~)或 4 空格缩进包裹清单的任何部分**——渲染端把代码块当纯文本展示,里面的图片语法一行都不会转成预览图,选卡协议整段失效(实测 8 张配图全灭)。parse_meaxure --list 的输出**照抄行内容即可,围栏与缩进不要照抄**。
|
|
26
|
+
**路径必须写完整绝对路径**(以 /Users/… 或 C:\… 打头,一字不改照抄),**禁止用省略号(… 或 ...)代替前缀**(实测「…/积分助手-4-xx.png」这种写法渲染端取不到图,用户那几行就只剩一串路径=配图全丢),**禁止把路径包进反引号或写进代码块**(反引号 / 代码块里的是给人抄的文本,不会渲染成图片)。清单长不是省前缀的理由:图显示不出来才是真的白写。
|
|
27
|
+
只回画板序号默认取该画板第 1 张卡。
|
|
28
|
+
- **禁止**自行挑画板 / 挑卡、**禁止**「先建一张看看」、**禁止**未经同意批量产卡。
|
|
29
|
+
|
|
30
|
+
## S2 审计
|
|
31
|
+
(一步代替反复探索):skill_exec script=skills/dsl-card-generator/scripts/spec_audit.mjs,args=["--spec","<bundle>/spec.json","--card","N","--slices-dir","<bundle>/slices","--project-slices","src/slices","--design","<bundle>/card-N.png","--save",".cache/ai-scratch/<id>.audit.json"]。
|
|
32
|
+
切片引用名先查 <bundle>/slices/index.json(--build 已生成:每张切图的真实相对路径与大小),照它写引用、按 R31 命名规则消毒(禁用字符 # ? % & + 不进引用名),expand_ir 会递归命中嵌套子目录里的真身;**禁止 fs_list 逐层摸索**(实测 6 次才找到嵌套真名,纯浪费)。
|
|
33
|
+
它交叉审计 spec + 聚焦截图 + 切图目录,给出:bands(水平行与行内元素序)、gaps(padLeft / padRight / padTop / padBottom / rowGaps)、texts(文案 / 字号 / 字重 / 颜色 / 行高 / 对齐)、shapes(装饰条)、slices(存在性 / 复用 / 是否烘焙背景与标签文字)、colors(背景采样点)、missing(缺哪张切图 + 建议裁剪矩形)。
|
|
34
|
+
它交叉审计 spec + 聚焦截图 + 切图目录,另给 bgGradientHint(顶部渐变带的起止色/跨度 + IR 正确写法)与 slices[].crops(reused-differs 时「矩形 → 该用哪张 _alt 图」的映射,exists=true 直接引用、false 才需裁)。
|
|
35
|
+
IR 的几何与样式一律照抄审计值;**禁止 fs_read spec.json**(几百 KB 只会拿到截断片段,重复读纯浪费步数),禁止反复 fs_list 摸索结构。要看层级树才跑 spec_tree.mjs。
|
|
36
|
+
**审计 JSON 只认落盘文件**:--save 已把整份审计写到 .cache/ai-scratch/<id>.audit.json,下游脚本(crop_slices --from-audit)一律引用该文件。**禁止用 fs_write 手抄 / 节选审计内容**——实测手抄的 156 字节「摘要」让 crop_slices 裁出错名错位的切图,还自造了违反命名约定的新切片。
|
|
37
|
+
|
|
38
|
+
## S3 补切图
|
|
39
|
+
S2 的 missing 非空 ⇒ skill_exec script=skills/dsl-card-generator/scripts/crop_slices.mjs,args=["--design","<bundle>/card-N.png","--card-width","<卡宽>","--out","src/slices","--from-audit",".cache/ai-scratch/<id>.audit.json"],从聚焦截图派生缺失切图(审计已落盘,直接引用)。切图唯一落点 src/slices/ 扁平命名,IR 里 Image.value 用 /slices/<名>。
|
|
40
|
+
只缺一两张时用 --crop <名>@x,y,w,h(可多条),x/y/w/h 直接抄审计 missing[].crop 的**卡片本地 1x 坐标**,脚本自己按 design 宽 ÷ 卡宽换算像素。**禁止用 sips / convert / magick 等系统图像命令裁图**:它们不在 shell 白名单,且要手算缩放比与裁剪偏移,实测必错。
|
|
41
|
+
切图用法看 slices[].baked:label=true ⇒ 切图已含标签文字,tile 用单个 Image(96×84 之类照 rect 抄);label=false ⇒ 需要 Image + Text 组合;verdict=reused-differs ⇒ 同一张切图被标到内容不同的多个位置,除第一个外必须用审计 slices[].crops 给出的 _alt 名(exists=true 直接引用,exists=false 由 crop_slices 派生)——**禁止自造新名字**(实测自造 icon_xx_左/右 让同一设计内容在 src/slices 下出现三套名字)。
|
|
42
|
+
|
|
43
|
+
## S4 写 IR
|
|
44
|
+
(全文只写这一次;**单次输出上限截断过 ⇒ 必须分片写**,见 2c 与 ir-format.md §2c:骨架 fs_write → 每区块一片 pNN.json → skill_exec ir_merge.mjs --ir <ir> --part …(可多条) 合并一次 → 再 expand_ir):fs_write .cache/ai-scratch/<id>.ir.json。结构照 bands 一条一段;gaps.rowGaps 映射成 flex.gap / padding(paddingType:"1" + 四边值);texts 逐条转 Text(fontSize / fontWeight / color / lineHeight 全抄,**lineHeight 必写**,否则渲染器按 1.5 倍字号撑高、整卡累计漂移几个像素);标题 + 底部装饰条**整段照抄 ir-format.md §2d 的片段**(AbsoluteLayout(align:"BottomLeft") > [Row(条, s.position.top/left), Text(标题, s.position.top/left)],子节点顺序 [装饰条, 标题];几何读标注差值 position.top = 标注条.y − 标注标题.y,别套「行高 + gap」的公式;条子不得排在流式里、不得用 margin 顶位(正负都不行,lint r64 硬拦))。
|
|
45
|
+
**类型开关铁律(渲染器静默丢弃类缺陷的唯一防线)**:margin / padding 组写单边值(marginTop 等)必须同组写 marginType:"1" / paddingType:"1"(expand_ir 会自动补;手写 schema 漏了会被 lint 报 r44-*-no-type);背景三选一:纯色 backgroundType:"01"+backgroundColor、图片 "02"+backgroundImage、渐变 "03"+backgroundGradient。审计给了 bgGradientHint 就照抄它的 ir 字段;**禁止自造 gradientColor / gradientDirection,禁止把 linear-gradient(...) 塞进 backgroundColor**——这三者都会静默失效(实测模型连试 3 轮全无效后放弃并编造理由)。 **水平内缩禁止写成「子节点 width:100% + marginLeft/Right」**:width:100% 已等于父容器内容宽,水平 margin 只会把节点推出父容器右缘、被 overflow:hidden 裁掉(父容器按列排布时水平方向是交叉轴,flexShrink 救不回来)——间距要放**父容器** paddingLeft/Right,或删掉 width 靠 stretch 补宽(lint r63-width100-with-hmargin ERROR 拦截,pitfalls §25)。
|
|
46
|
+
落点卡默认 = 当前正在编辑的卡片 ID(见「当前卡片上下文」):IR 的 id 与 iterate 的 --out 一律 src/cards/<当前卡片ID>/,即覆盖当前卡 / 空卡是默认行为。**「建卡」「生成卡片」「用这张画板做一张卡」都不是「新建一张」**——那是在说「用设计稿出一张卡」,落点仍是当前卡;只有用户明确说「新建一张 / 另存为新卡 / 换个新 id / 不要覆盖当前卡」才自拟 ID,且自拟前先用一句正文写明「将新建卡片 <新id>,不覆盖当前卡 <当前卡id>」,让用户当场看得见(实测:把「建卡」读成「新建」,界面里凭空多出一张卡而对话里没有任何交代)。当前卡已有内容时,动手前一句正文说明将覆盖、原内容留 .history 快照可回滚。
|
|
47
|
+
|
|
48
|
+
## S4.5 变量抽取
|
|
49
|
+
(真实卡片不是截图:这步漏了,产物就是 dictionary / data.json 全空的死图):先 skill_read ir-format.md 的「2b. 变量绑定」看契约,再决定哪些文本绑变量——
|
|
50
|
+
下限 = S2 审计的 bindHints[](amount / percent / date / masked / count / level),再按语义补正则认不出的动态值(用户名 / 客户层级 / 卡片名称 / 状态词);静态标签(区块标题 / 按钮 / 说明文字)才写字面量。
|
|
51
|
+
- 动态值:Text 主值用 v:"$key"(条件文案写表达式如 "$showAll ? '收起' : '查看更多'",金额展示照 recipe_snippets/money-format.json,别把「¥7,000.00」拆成字面量)。
|
|
52
|
+
- IR 顶层**显式写 dictionary.inputParams**:{key,name,type,defaultValue,desc,rule:{type:"",options:{}},children:[]},defaultValue 一律取设计稿字面量——expand_ir 用它生成 data.json,渲染即设计稿文案,S5 像素闭环才不受绑定影响。
|
|
53
|
+
- 重复行 / tile 组(审计 listCandidates[]、或用户明说要列表):数组变量 + listScope 插槽。item 的字段**已逐个展开到作用域** ⇒ 规范形三种:$<字段名>(字段名语义化自取)、$$(整个 item)、$_index(行索引)。写成 $item.<字段> 也不影响结果——expand_ir 在扩展开头会把它自动归一化成规范形(作用域里本来就没有 item 这个键,schema 只存规范形;schema 级另有 autofix_schema.py 兜底)。范式照 recipe_snippets/list-expand-collapse.json 或 src/cards/account 的 account_details。结构确实不规则时允许保持静态,但要在正文说明理由。
|
|
54
|
+
- 命名语义化(memberLevel / totalAmount / assetRows),禁 text1 / val2;写完在正文列一句绑定清单(key → 设计稿文案)。
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
| G1 | 每个组件必有 `componentId`,全卡唯一且编辑期间稳定 | 校验失败、编辑面板无法定位 | dsl_structure |
|
|
12
12
|
| G2 | `itemValueFrom:"1"` 的属性**必须**带 `itemDefaultValue` | 渲染空白(undefined 兜底失败) | expression_engine |
|
|
13
13
|
| G3 | 不要把导出里的未知字段"归一化"掉;保留并告警 | DSL 契约字段丢失,保存污染 | SKILL.md §0 |
|
|
14
|
-
| G4 | `customAction` 的 `function` 字段含 `code` 与 `compiled`
|
|
14
|
+
| G4 | `customAction` 的 `function` 字段含 `code` 与 `compiled` 双份。**不要手写 compiled**:`expand_ir` 展开时自动 `compiled = code`,`card_patch` 写 `.code` 时也自动同步。只有用 `card_write` 整份写 schema 时才需自己保证两份一致(运行时取 `code || compiled`,编辑器读 compiled) | 编辑器与运行时分叉 | event_patterns |
|
|
15
15
|
| G5 | 改 schema 时**绝不**改未涉及节点的 `componentId` | 编辑面板状态断裂、引用丢失 | SKILL.md §12 |
|
|
16
16
|
| G6 | 容器必有 `slots`;叶组件通常省略 `slots` | 校验失败、子组件无法放置 | dsl_structure |
|
|
17
17
|
| G7 | `style.other.opacity` **禁止表达式/绑定**(铁律 R57):会变化的透明度走「静态变体 ×N + 等宽高 overflow:hidden 容器 + 事件上移 + 互斥 v-show」固定范式 | 移动端透明度不生效(预览也不消费 opacity,截图查不出来);lint `r57-opacity-expression` ERROR | recipes.md §17 / opacity-switch.json |
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
| eventKey 与组件类型不匹配 | **lint** |
|
|
74
74
|
| 脚手架占位文本残留 | **lint** |
|
|
75
75
|
|
|
76
|
-
未覆盖的(人工兜底):Icon 双触发、Canvas 坐标系、v-show tab↔header
|
|
76
|
+
未覆盖的(人工兜底):Icon 双触发、Canvas 坐标系、v-show tab↔header 面板泄漏、孤儿字段保留。compiled 与 code 同步已由 expand_ir / card_patch 自动完成,不再是人工项。
|
|
77
77
|
|
|
78
78
|
---
|
|
79
79
|
### single-button-width-magic-ratio(r273 训练回灌)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# 建卡协议(S4.9~S6):内容就绪 → 像素收敛 → 收尾门禁
|
|
2
|
+
|
|
3
|
+
> 本文件是 system 提示词里「建卡流程骨架」的**收敛段全文**。IR 已落盘、准备跑 iterate 前读本文件。
|
|
4
|
+
> 前半段(S0~S4.5)在 build_protocol.md。
|
|
5
|
+
|
|
6
|
+
## S4.9 内容就绪门禁
|
|
7
|
+
(**像素自循环的前置条件**;不达标时 iterate 会把本轮整体挡掉——不 render、不比对像素、不消耗迭代轮次):
|
|
8
|
+
IR 写完、变量抽完后,先跑 skill_exec script=skills/dsl-card-generator/scripts/readiness.mjs,args=["--id","<落点卡>","--spec","<bundle>/spec.json","--design","<bundle>/card-N.png","--json"]。退出码 0=就绪 / 3=未就绪(**3 不是脚本报错,是内容没准备好**)。
|
|
9
|
+
五道**声明性**判定(不需要渲染,所以能挡在最贵的一步之前):A 组件契约(属性键 / 样式组 / 样式子键必须在 component-meta.json + style-meta.json 白名单内)· B 要素齐全(设计稿每条文案在 schema 里找得到)· C 变量绑定完备($var 的根变量必须在 dictionary.inputParams / mocks / data.json / 事件 setData 里存在)· D 图片地址已配置(每个 Image 必须有非空 value)· E 切图路径已配置(每个 /slices/... 引用在 src/slices 里有对应文件)。
|
|
10
|
+
blockers 非空 ⇒ **先补内容,别进 S5**。这一步存在的唯一理由:内容没齐时像素分毫无参考价值。实测事故(2026-09-24 gen-1):跳过这步,在图片地址都没配的情况下连刷 10 轮像素(match 卡在 87-90.46),两回合烧掉 623 万 prompt token 全部作废。
|
|
11
|
+
三条高频坑(readiness 会在 blockers 里逐条点名,不要靠猜):① Image 的图片地址属性键**只能是 value**,写成 image 会被运行时按 itemKey 绑定失败后**整条静默丢弃** ⇒ value 为空 ⇒ 破图,而 validate / lint / preflight 全绿;② from:"2" 的值必须是静态路径、from:"1" 的值必须是 $表达式,写反 ⇒ 字符串直接进 url(...) 或表达式求值失败 ⇒ 画布空白;③ 切图**引用名与磁盘文件名必须同源**(crop_slices 按引用名落盘;引用一个 src/slices 里不存在的名字 ⇒ 渲染 404、图空白、控制台零报错)。
|
|
12
|
+
**iterate 每轮会自动再跑这道门禁**:报告里 gate.stage=content(或 verdict 以 BLOCKED(内容未就绪)开头)表示本轮被挡——没跑 render、没跑 pixelDiff、**没消耗迭代轮次**。按 gate.verdict 里的 blockers 补齐内容后**直接重跑 iterate**即可;禁止在被挡时反复重跑空转,更禁止去调像素(那时候没有像素分可调)。
|
|
13
|
+
逃生门 --no-readiness(或环境变量 DSL_CARD_SKIP_READINESS=1)只在排查「门禁本身误判」时用,正常流程禁止。
|
|
14
|
+
|
|
15
|
+
## S5 收敛循环
|
|
16
|
+
(唯一门禁 = iterate.mjs 报告里的 gate 字段:内部已跑 autofix + 真 validate + lint + preflight + **内容就绪(S4.9)** + render + 并集像素对比,别再单独跑 validate_schema.py):
|
|
17
|
+
skill_exec script=skills/dsl-card-generator/scripts/iterate.mjs,args=["--ir",".cache/ai-scratch/<id>.ir.json","--out","src/cards/<落点卡>/","--design","<bundle>/card-N.png","--width","<卡宽>","--spec","<bundle>/spec.json"]。
|
|
18
|
+
修 IR 用 **ir_patch**(点路径 JSON 补丁,一次可多条,形如 ops=[{"path":"children.0.s.flex.gap","value":"0"}])——**它是改 IR 的首选**:结构化参数不吃引号转义、原子写、回旧值→新值摘要;也可用 iterate.mjs 的 --set(字符串字面量语法,注意引号转义,例:--set children.0.s.flex={"gap":"0"})。数字段=数组下标;中段不存在会响亮报错,按报错改路径。**两条路径都不是「整份重写」**——同轮内对同一草稿文件第 2 次全量 fs_write 会收到提醒、第 3 次起被拒(要重排结构才传 rewrite:"true")。
|
|
19
|
+
**收敛 = 报告 gate.passed === true**(**内容就绪(S4.9)** ∧ validate/lint/preflight 零 error ∧ **elements.missing=0** ∧ match ≥ 95 ∧ heightDelta=0 ∧ widthDelta=0 ∧ |contentOffset1x| ≤ 2)。gate 是脚本判的,正文只能引用它的 verdict / failed,不得自行宣布达标。像素对比按并集画布算,删内容 / 高度塌缩只会降分,不可能凑分。
|
|
20
|
+
**要素是固定真值:先内容、后骨架、最后样式(三段式门禁,逐段推进)**。S2 审计里的每条文本 / 每张切图都是这张卡的既定要素,S5 只许改**样式与布局**(尺寸 / 间距 / 颜色 / 字号 / flex / 位置),**不许丢要素、不许自造内容**。报告带 gate.stage 与 gate.checks[].name === 'readiness.content':
|
|
21
|
+
· stage=content(gate.blocked=true)⇒ 内容层没齐(要素 / 变量绑定 / 图片地址 / 切图落盘),**本轮根本没 render**。照 verdict 里的 blockers 补内容,别调像素、别重跑空转。
|
|
22
|
+
· stage=structure(elements.missing>0 或 validate/lint/preflight 未过)⇒ **先补要素,别调像素**:把设计稿的文案补回对应节点;绑定变量的把 defaultValue 改成设计稿真值。改样式不会把丢掉的文案变回来。
|
|
23
|
+
· stage=pixel ⇒ 内容与结构都齐了,才进入样式 / 布局收敛(一轮只改一个分项)。
|
|
24
|
+
实测事故(2026-09-21):模型把设计稿的产品名 / 利率 / 期限整批换成自己编的占位值(「理财优选1号 / 2.45 / 每日申购」),像素照样刷到 89%、validate/lint 全绿,而卡的**内容真值整批丢失**,直到人工比对设计稿才发现。报告 elements.invented 会把这类「schema 里有、设计稿里没有」的自造文案逐条点名,elements.missing 列出漏掉的要素。
|
|
25
|
+
报告还带:contentOffset1x + contentOffsetHint(内容整体平移时先修对齐,别逐行调间距)、**contributors + contributorsNote(分项归因:每个差异热区给出最可能出问题的具体节点)**、widthMismatch(--width 与设计稿卡面宽不符 ⇒ 先改 --width,否则 match/dy 都不可信)、lint.warningDetails、preflight.details、renderImage(渲染原图路径)、diffImageNote(并排图顶部色条是工具标题栏,不是卡片内容)、stalled(**连续 3 轮未刷新历史最好成绩**——震荡下滑同样触发——附 best 与回退命令)、best(历史最好成绩 {match,round})。
|
|
26
|
+
**contributors 是「差在哪个节点」的唯一答案,用它替代整份重写 IR**:数组每项 = 一个热区(hotspot/diffPct)+ elements[](按最内层优先排序的具体节点:cid / cls / text / rect / h / hSibling(同类中位高度)/ designH / dH(与设计稿高度差)/ flags / hotCover)。读法:flags 里 text-clipped ⇒ 该节点文本被裁剪,查 isMaxLine / maxLine / lineHeight;vs-design ±N ⇒ 该节点高度与设计稿差 N,查它自己的 paddingTop/Bottom 与 lineHeight;vs-siblings ±N ⇒ 同类元素里只有它高度不同,查它自己的 padding/margin(**不是整层 gap**);bottom-edge / right-edge ⇒ 差异贴边界外,查下边距 / 圆角 / 阴影。**一轮只改 contributors[0].elements[0] 这一个节点的一个属性**,改完立刻重跑 iterate 看是否刷新最好成绩——同时改多项无法归因。designH/dH 为 null 表示没给 --spec 或文本匹配不上,此时只能靠 flags 与同类中位判断,**不要自己编设计稿高度**。
|
|
27
|
+
**报告里出现 envBlocked / verdict 以 BLOCKED 开头 ⇒ 本机没有可用的 python,validate 根本没跑**:这是环境问题,改 schema、继续 --set 都修不好它——立刻停手如实告诉用户(装 Python 或设 DSL_CARD_PYTHON=<解释器路径>)后重跑,别把它当门禁缺口反复重试。
|
|
28
|
+
**stalled 出现 ⇒ 第一件事是回退,不是接着调**(它=连续 3 轮没刷新最好成绩,说明 --set 已到天花板):① skill_exec script=skills/dsl-card-generator/scripts/ir_restore.mjs args=["--best",".cache/ai-scratch/<落点卡>.ir.best.json","--ir",".cache/ai-scratch/<落点卡>.ir.json"] 回退到历史最好成绩那一轮(写回前自动备份当前 IR),**回退后立刻重跑一轮 iterate**——旧报告的 schemaHash 已失效,--gate 会直接判 FAIL;② 之后一轮只改 gate.failed 里的**一个**分项;③ 同一项连改两轮都不动才换证据链:跑 diff_bands.mjs(args=["--id","<落点卡>","--design","<bundle>/card-N.png","--render","<报告 renderImage>"])拿逐带 dy/dh 与分区主色,再对照 references/generation_pitfalls.md 的「类型开关+值 / 静默丢弃」清单。**禁止**在 stalled 之后继续盲调 --set、**禁止**再全量重写 IR。实测事故:match 卡 73% 连调 6 轮无效,根因是 6 处 margin 漏 marginType,diff_bands 一轮就能指认;另一条轨迹 r10 做到 92.42 后 13 轮越改越差、烧掉 707 万 token 才被人工中止——正因为旧判据非要「完全不变」才触发,震荡下滑落在盲区里。
|
|
29
|
+
match 不升反降、heightDelta 非 0、或 hotspots 集中在原本已有内容的区域 ⇒ 说明在删内容凑分:回退该轮 --set 换思路。diffImage 每轮重写(报告里的路径),只给用户预览。
|
|
30
|
+
**取证禁令**:禁止用 shell_exec 写 node -e pngjs 像素脚本(行扫描 / 取色 / 自算 diff)——diff_bands.mjs、报告 hotspots、contentOffset1x、审计 colors/bgGradientHint 已经覆盖全部取证需求,实测手写脚本 4 次全是浪费步数;禁止 fs_read 任何图片;禁止在 S5 之前调 card_screenshot(渲染还没意义);禁止把 src/cards/<卡>/<卡>.png 当渲染图(那是设计稿归档,渲染原图是报告里的 renderImage)。同理**禁止自造分析 / 补丁脚本**(往草稿区写 .cjs/.mjs 再跑、node -e 扫像素或改 JSON 都在此列,实测白烧 11 轮)与**用 sed -i / 重定向直接改 IR、schema**(会绕过 validate、原子写与 .history 快照):改 IR 用 ir_patch(首选:点路径 JSON 补丁、原子、不吃引号转义)或 iterate.mjs --set,改卡片内容用 card_patch / card_write。
|
|
31
|
+
每轮用一两句正文汇报:gate.verdict / match% / heightDelta / 这轮修了什么,不要闷头连调。
|
|
32
|
+
|
|
33
|
+
## S6 收尾门禁
|
|
34
|
+
① skill_exec script=skills/dsl-card-generator/scripts/readiness.mjs,args=["--id","<落点卡>","--spec","<bundle>/spec.json","--design","<bundle>/card-N.png"],**必须 exit 0(就绪)**——内容层的四类缺口(要素 / 变量绑定 / 图片地址 / 切图落盘)在收尾时同样不许放过;② skill_exec script=skills/dsl-card-generator/scripts/check_fidelity.mjs,args=["--spec","<bundle>/spec.json","--schema","src/cards/<落点卡>/schema.json","--card","N"],按它报的文本缺失 / 字号偏差 / 颜色偏差修到无 ERROR;③ skill_exec script=skills/dsl-card-generator/scripts/iterate.mjs,args=["--gate","--out","src/cards/<落点卡>/"] 拿机器判定(读最近一轮落盘报告,schema 在报告之后被改过会直接判 FAIL;结论可能是 pass / stalled / **blocked-content** / iterating 四种,blocked-content 表示内容仍未就绪,不是收敛)。
|
|
35
|
+
正文汇报必须**逐字引用 --gate 的 verdict 与 failed 列表** + 落点卡 + 补了哪些切图 + 回滚方式。gate 未过禁止写「门禁全绿 / 已完成 / 验收通过」,必须列出未达标项、原因与下一步;禁止把未达标归因于「测量噪声 / 工具合成产物」——pixelDiff 只用渲染原图与设计原图,合成图色条不参与计算(见 diffImageNote)。
|
|
36
|
+
绑定对齐:check_fidelity 会按 data.json / defaultValue 把 $ 绑定解析回文案再对齐——报 missing-text 而该文本已绑定 ⇒ 是 mock 与设计稿不一致,改 defaultValue / data.json,**不是拆回字面量**。收尾自查:bindHints 每一项要么已绑定、要么正文说明为何保持字面量。
|
|
@@ -964,7 +964,7 @@ gen-card-test 第一轮就把收支占比条写成了静态 `43%` / `57%` 的 `D
|
|
|
964
964
|
- **判定口径**:**内容 sha256 = 唯一权威判据**;尺寸 w×h + 字节数 = 快速预筛(不单独作判据);来源(导出稿 / 画板 / 图层名,若可知)= 辅助证据与**命名依据**,不参与去重判定。
|
|
965
965
|
- **处理**:IR 里直接写语义名(`pi.value: "/slices/iconArrowRight@3x.png"`);机械产线(`sliceRefPath` / v3)产出的导出扁平名属过渡形态,交付前**成套改名**(文件名 + `lib/slice_dedupe.mjs → rewriteSliceRefs` 改写全部卡片 schema/data + 同步 `manifest.json`),**禁止新旧两份并存**。`expand_ir.mjs`(`syncReferencedSlices`)与 `generate_cards.mjs`(`collectSlicesToSrcSlices`)落地时已自动做同内容复用 + 引用回写并打印 `[slices] 内容已存在 → 复用 …`。`crop_slices.mjs --from-audit` 派生的 `_alt` 名内容确实不同(不算重复,见 §13 R-e),但仍是过渡名,建议改成语义名。
|
|
966
966
|
- **验收**:`node scripts/lint_card.mjs --id <卡id>` 无 `r59-slice-semantic-name` / `r60-slice-duplicate-content` 新增告警;`--all | grep r60` 为空;切图上传后 `src/slices/manifest.json` 里同一 sha256 只有一个 `url`(同内容副本记 `reusedFrom`)。
|
|
967
|
-
- **规则详情**:`slices_convention.md §1.1(命名)/ §3.1(落地判重)/ §3.2(上传去重)`;共享实现 `scripts/lib/slice_dedupe.mjs`;上传侧 `skills/
|
|
967
|
+
- **规则详情**:`slices_convention.md §1.1(命名)/ §3.1(落地判重)/ §3.2(上传去重)`;共享实现 `scripts/lib/slice_dedupe.mjs`;上传侧 `skills/platform-card-sync/scripts/slice_upload.mjs`。
|
|
968
968
|
|
|
969
969
|
## 15. 重复裁切根治:共用切图已落地仍被裁出 `_altN` 副本(2026-09-18 ynet-card 事故)
|
|
970
970
|
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
> **输入前置路由(先做这一步)**:本节 prompt 面向「单张设计稿图」或「已前处理的画板要素」。若用户给的是 **Sketch MeaXure 标注导出目录**(`index.html` 内嵌 `artboards` + `preview/*.png` + `assets/`),先跑 `node skills/dsl-card-generator/scripts/route_input.mjs <导出目录>`:它会判出 `meaxure-export` 并打印上游命令,由 `sketch-spec-to-card` 产出 `UI/<稿名>/` 要素包(`card-N.png` 聚焦图 + `spec.json` + `prompt.md` + `slices/`),再对每个画板套用本节 prompt——此时输入换成聚焦图 + 标注,而非整稿截图。**禁止跳过前处理直接读 `preview/` 整图建卡**(会丢掉卡片聚焦边界与标注数值)。详见 `SKILL.md §8 Workflow 0`。
|
|
42
42
|
|
|
43
43
|
```
|
|
44
|
-
你正在用 dsl-card-generator skill
|
|
44
|
+
你正在用 dsl-card-generator skill 生成一张远端卡片。
|
|
45
45
|
|
|
46
46
|
输入:附上的图片是卡片设计稿。
|
|
47
47
|
卡片意图:<一句话说明这张卡是干什么的,例如"成长等级任务卡,深色背景,列出可完成的任务与奖励">
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
| 选近邻模板 | 查 `references/card_index.json`(按 `recipes` / `tags` 过滤,不读源文件) |
|
|
45
45
|
| 写回+校验+渲染 | MCP `card_write({id, schema, verify:true})` → 再看 `card_render_status` |
|
|
46
46
|
| 视觉确认(**仅 UI 变更**) | MCP `card_screenshot({id})`(多模态读图;`render_status.ok ≠ 画对`)。按 `references/image_diff.md` 七维度(布局/层级/文案/留白/配色/字号/交互元素)逐项核对 |
|
|
47
|
-
| **非 UI 变更收口(不截图)** | 改 `inputParams` / 绑定表达式取值 / 事件代码 / `data.json` → 只跑 validate + lint + `derive_data_json --check`,再用单元断言:`evalExpr(expr, scope)` 比对返回值、`new Function(code).call(假 this)` 跑 customAction 断言 `state
|
|
47
|
+
| **非 UI 变更收口(不截图)** | 改 `inputParams` / 绑定表达式取值 / 事件代码 / `data.json` → 只跑 validate + lint + `derive_data_json --check`,再用单元断言:`evalExpr(expr, scope)` 比对返回值、`new Function(code).call(假 this)` 跑 customAction 断言 `state`(compiled 已由工具链同步,跑 code 即可)。铁律见 `references/rules.md §七.1` |
|
|
48
48
|
| **变更对比(铁律机械兜底)** | `node scripts/diff_card.mjs --id <卡id>`(vs git HEAD) / `--baseline <path.json>` / `--against <otherId>`。整体替换 schema 后必跑一次,列删除/新增/修改的 componentId |
|
|
49
49
|
| **行为规则门禁** | `node scripts/preflight.mjs --spec UI/<画板>/spec.json --id <卡id>` + `node scripts/lint_card.mjs --id <卡id>` 或 `--all`(≠ validate:lint 管渲染行为,validate 管结构字段)。**0 ERROR 是 Workflow A 步骤 4 的硬门禁**——拦截 flexShrink 缺失(R17)、元组格式(R-FMT1/2)、Icon 缺 click(R-FMT3)、Root 非零 padding(R9/R10)等「validate 漏报但渲染炸」的问题 |
|
|
50
50
|
| **抽范式片段** | `node scripts/extract_subtree.mjs --id <卡id> --component-id <cid> [--name <名>] [--out <path>]`:从已导出卡抽子树,输出含 subtree + 自动探测 requiresInputParams,写入 recipe_snippets/ 让范式库可持续增长 |
|
|
@@ -41,7 +41,7 @@ ctx.fillText(text, 0, canvas.height / 2);
|
|
|
41
41
|
|
|
42
42
|
要点:
|
|
43
43
|
- 坐标是**设计稿 px**,event.canvas 是设计稿坐标系包装对象(宽度/2dctx 已按 scale 换算)。不要乘 devicePixelRatio,不要用 canvas.width/2 做左对齐(那是设备 px 假设)。
|
|
44
|
-
-
|
|
44
|
+
- compiled 不必手写:expand_ir / card_patch 会自动同步;只有 card_write 整份写时才需自己补。
|
|
45
45
|
- 来源:creditCardBill(左对齐)、dreamPlan、assetOverview、financialProductDetail、debtDetails、myDeposit、querySelector 等 25 张卡都用 onMeasured。
|
|
46
46
|
|
|
47
47
|
## 2. 弹一张卡片(alertCard)+ 同名 Dialog 表
|
|
@@ -234,7 +234,7 @@ this.setData(this.state);
|
|
|
234
234
|
- 正号:`num > 0 && bothSign ? '+' : ''`(bothSign=true 时正数也显 +,适合收支列表)。
|
|
235
235
|
- 负号:`num < 0 ? '-' : ''`;货币符:`isPrefix ? '¥' : ''`(新生成卡用半角 `¥`,见 amount_formatting.md §2)。
|
|
236
236
|
- 金额之间的**相等比较**不能直接 `$a == $b`(两个字符串走字面比较,`"5638.00" == "5638"` 为 false,会切错条件分支)。写成 `((($a + '').replace(/,/g, '') * 1) == (($b + '').replace(/,/g, '') * 1))`;大小比较(`>` / `<`)JS 自动 coerce,无需改写。
|
|
237
|
-
-
|
|
237
|
+
- 改事件代码只写 `.code` 一份:card_patch 会自动同步 compiled(若你显式给了 compiled,则以你给的为准,不会被覆盖)。
|
|
238
238
|
|
|
239
239
|
## 6. Tab ↔ 标题头模式切换(billMode)
|
|
240
240
|
|
|
@@ -483,7 +483,7 @@ drawDonut(ratios, event.canvas, event.width, event.height);
|
|
|
483
483
|
- **坐标系**:`event.canvas` 是设计稿坐标系包装对象(缩放已换算),绘图用**逻辑设计 px**,**不要乘 devicePixelRatio**。圆心 `canvas.width/2`、半径 `width/2` 都在设计 px 内,自洽。
|
|
484
484
|
- **中心文字**:甜甜圈中间的总额/标题是**另叠一个绝对定位 Text**(不在 canvas 里画),便于用 DSL 文本样式与千分号(§5)。canvas 只负责环。
|
|
485
485
|
- **调色板**:`colors[i % colors.length]` 让扇区数超过调色板长度时循环取色;配色建议按 design 规范 Token 化(见 amount_formatting.md 配色约束),勿硬编码裸色(除非如本卡上游既定)。
|
|
486
|
-
- **
|
|
486
|
+
- **compiled 自动派生**:expand_ir / card_patch 都会让 compiled 跟随 code;只有 card_write 整份写时才需手工对齐。
|
|
487
487
|
- 铁律对齐:首行 `var self = this`、嵌套回调一律 `self`、每条语句 `;` 收尾(见 rules.md §四)。
|
|
488
488
|
- 来源:assetOverview(`onReady` 算 ratio + `canvas_4d` 140×140 甜甜圈,export-proven)。同思路可画进度环、半圆环(改 start 角与扫过的弧度范围即可)。
|
|
489
489
|
|
|
@@ -548,7 +548,7 @@ self._cdTimer = setInterval(function () {
|
|
|
548
548
|
- **进度语义**:`ratio = elapsed/total`(已消耗比例),0% 空环 → 100% 满环;中心显示 `total - elapsed`(10→0)。
|
|
549
549
|
- **重绘机制**:onMeasured 是生命周期只触发一次,定时器内必须**手动调 drawRing 重绘**,不依赖渲染器自动重触发。
|
|
550
550
|
- **配色**:背景环浅色、进度弧深色、中心数字高对比;v3 从环形组 border 色自动提取(非 #F0F0F0 描边 → 进度色)。
|
|
551
|
-
- 坐标设计 px 勿乘 DPR;
|
|
551
|
+
- 坐标设计 px 勿乘 DPR;compiled 由工具链自动同步(card_write 整份写时例外);铁律:`var self = this` + 每条 `;` 收尾。
|
|
552
552
|
|
|
553
553
|
## 11. 角标 / 角标签(AbsoluteLayout 叠加小标)
|
|
554
554
|
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
## 八、远端同步前置
|
|
89
89
|
|
|
90
90
|
- 远端保存(`card_save`)前确保本地镜像是外部最新(先 `card_sync`);保存后建议回读校验。
|
|
91
|
-
- Chrome 启用 remote-debugging(端口 9222
|
|
91
|
+
- Chrome 启用 remote-debugging(端口 9222),远端页面保持已登录态。
|
|
92
92
|
- IDC10010 保存只传内层 body + 必带 `sassAppId` / `sassWorkspaceId`;"交易成功" ≠ 真落库,须 IDC10012 读回校验。
|
|
93
93
|
|
|
94
94
|
## 九、组件级硬约束
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|---|---|---|
|
|
13
13
|
| ① 上游导出(sketch-spec-to-card 中间产物) | `UI/<artboard>/slices/<category>/<name>@3x.png` | 多级目录,保留导出原始结构,仅作为来源 |
|
|
14
14
|
| ② 生成汇聚 + 引用(dsl-card-generator) | `src/slices/<flat>@3x.png` + schema `value="/slices/<flat>@3x.png"` | **扁平落点**,无子目录;`<flat>` = 导出相对路径的 `/` 替换为 `_` |
|
|
15
|
-
| ③ 切图上传(`slice_upload.mjs` / 顶栏「远端 → 切图上传」) | `@filestore/<ns>/<ts>_<name>@3x.png` | 上传 `src/slices/`
|
|
15
|
+
| ③ 切图上传(`slice_upload.mjs` / 顶栏「远端 → 切图上传」) | `@filestore/<ns>/<ts>_<name>@3x.png` | 上传 `src/slices/` 真源到远端图片库(`CC0000.do`),schema 引用改写为 `@filestore/...`,并在 `src/slices/manifest.json` 记「本地文件 ↔ 线上地址」映射;运行时 `adapter.doReplaceUrl`:`@filestore/x` → `/minio/x` → assetProxy 拉远端 |
|
|
16
16
|
|
|
17
17
|
**扁平化示例**(用户给定基准):
|
|
18
18
|
```
|
|
@@ -59,7 +59,7 @@ schema 引用:/slices/8.icon_通用_金额隐藏@3x.png
|
|
|
59
59
|
- 一律用 **绝对路径前缀 `/slices/`**,后接扁平文件名(含 `@3x` 与扩展名)。
|
|
60
60
|
- 正确:`/slices/8.icon_通用_金额隐藏@3x.png`
|
|
61
61
|
- 错误:`src/slices/...`、`slices/...`、`UI/.../...@3x.png`、`@filestore/...`(同步前不允许)
|
|
62
|
-
- 生成阶段(上传前)只能出现 `/slices/...`;`@filestore/` 仅由「切图上传」(`skills/
|
|
62
|
+
- 生成阶段(上传前)只能出现 `/slices/...`;`@filestore/` 仅由「切图上传」(`skills/platform-card-sync/scripts/slice_upload.mjs`)在上传成功后改写产生——**没有自动上传**,只有用户点顶栏「远端 → 切图上传」/ 调 MCP `card_slice_upload` / 跑 CLI 时才会发生。
|
|
63
63
|
- 运行时解析:浏览器以页面 origin 为基解析 `/slices/...` → 命中 Vite 的 `/slices/*` 静态中间件(见 §4)。
|
|
64
64
|
|
|
65
65
|
## 2.1 排除「导出目录」切图层(`99.切图/*`)—— 不作为卡片内容
|
|
@@ -100,7 +100,7 @@ schema 引用:/slices/8.icon_通用_金额隐藏@3x.png
|
|
|
100
100
|
|
|
101
101
|
## 3.2 上传去重:共用切图只上传一次(强制)
|
|
102
102
|
|
|
103
|
-
③ 阶段 `skills/
|
|
103
|
+
③ 阶段 `skills/platform-card-sync/scripts/slice_upload.mjs` 按**内容 sha256** 判重,且**不认文件名**:
|
|
104
104
|
|
|
105
105
|
- 同一张图被多张卡引用 → **只上传一次**,其余卡的 `/slices/<name>` 引用改写为**同一个** `@filestore/...` 地址。
|
|
106
106
|
- `src/slices/` 里内容相同但命名不同的副本、以及**同一批次内**的同内容文件 → 复用已上传/待上传那一份的地址,`manifest.json` 记 `reusedFrom: <源文件名>`;**绝不为同一张图产生第二个线上地址**。
|
|
@@ -143,7 +143,7 @@ sketch-spec-to-card 产出的 spec 中,**slice 元素的 `name`(图层路径
|
|
|
143
143
|
| v3 变体(CLI 直组装)生成 Image 时引用 `/slices/` | `scripts/spec_to_schema_v3.mjs`(`imageFromSlice(sliceEl, sem.slicesExports)`,由 `extractSemantics` 透传 `spec.slices`;geo 变体已随 geo 生成器删除) |
|
|
144
144
|
| 拷贝被引用切图到 `src/slices` | `scripts/generate_cards.mjs`(`collectSlicesToSrcSlices`,仅 referenced) |
|
|
145
145
|
| 本地 `/slices/*` 静态服务(自包含) | `scripts/render_card.mjs`(临时 HTTP 中间件,直接读 `slicesDir`) |
|
|
146
|
-
| ③ 阶段:上传切图 + 写映射 + 改写引用 | `skills/
|
|
146
|
+
| ③ 阶段:上传切图 + 写映射 + 改写引用 | `skills/platform-card-sync/scripts/slice_upload.mjs`(`uploadSlices`);HTTP 端点 `server/core/sliceUpload.js`(`POST /__slice-upload`);编辑器入口 `card-editor/App.vue`(`uploadCardSlices`);MCP `card_slice_upload` |
|
|
147
147
|
| 语义名判定 + 消毒 + 内容 sha256 去重 + 引用改写(共享实现) | `scripts/lib/slice_dedupe.mjs`(`checkSliceName` / **`sanitizeSliceFileName`** / **`sliceNameMatches`** / **`findUrlReservedChars`** / **`sliceNameFromRef`** / **`isRemoteSliceRef`** / **`buildSliceFileIndex`** / **`resolveSliceRefOnDisk`** / `buildContentIndex` / `sha256File` / `duplicatePeers` / `rewriteSliceRefs` / `collectSliceRefStrings`)—— lint / preflight / crop_slices / slicePath / expand_ir / generate_cards 全部共用,**改一处即全链路生效** |
|
|
148
148
|
| 像素级内容判重(裁切图 ≡ 导出资产 / 已重命名共用切图) | `scripts/lib/slice_pixels.mjs`(`pixelsMatch` / `findPixelTwin` / `cropPng` / `pngPixelSha` / `readPng`) |
|
|
149
149
|
| 缺口审计:指纹分组 + 像素复用 + manifest 别名(`renamedFrom`/`mergedFrom`) | `scripts/spec_audit.mjs`(`groupByFingerprint` / `regionTwin` / `buildAliasMap`;verdict `content-twin`、crops[].reuseOf/alsoRects) |
|
|
@@ -152,7 +152,7 @@ sketch-spec-to-card 产出的 spec 中,**slice 元素的 `name`(图层路径
|
|
|
152
152
|
| 落地时同内容去重(不重复导入 + 改写引用回写) | `scripts/expand_ir.mjs`(`syncReferencedSlices`,已 export)、`scripts/iterate.mjs`(一条龙闭环内调用同一函数,支持 `--slices-dir`)、`scripts/generate_cards.mjs`(`collectSlicesToSrcSlices`) |
|
|
153
153
|
| 命名 / 重复告警(lint,WARN) | `scripts/lint_card.mjs`(`r59-slice-semantic-name` / `r60-slice-duplicate-content`) |
|
|
154
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 逐张核图确认的存量映射) |
|
|
155
|
-
| 上传阶段同内容复用线上地址(`reusedFrom`) | `skills/
|
|
155
|
+
| 上传阶段同内容复用线上地址(`reusedFrom`) | `skills/platform-card-sync/scripts/slice_upload.mjs`(`shaToUploaded` 索引 + 批次内延后复用) |
|
|
156
156
|
|
|
157
157
|
> v3 生成器引用切片必须走 `sliceRefPath()`(产出 `/slices/<flat>`),与其 `collectSlicesToSrcSlices` 拷贝目标(扁平 `<flat>`)严格对应,禁止各自拼字符串。
|
|
158
158
|
|
|
@@ -164,7 +164,7 @@ sketch-spec-to-card 产出的 spec 中,**slice 元素的 `name`(图层路径
|
|
|
164
164
|
| schema 引用写 `src/slices/...` 或 `slices/...` | Vite 不识别,预览拉不到 | 一律 `/slices/<flat>@3x.png` |
|
|
165
165
|
| 无差别拷贝 `UI/<artboard>/slices/` 下全部切图 | 带进 chrome / 冗余素材 | `collectSlicesToSrcSlices` 仅拷 referenced |
|
|
166
166
|
| 元素名直接当文件名(未归一化) | `顶部返回_icon` ≠ `顶部返回` 匹配失败 → 退化占位图 | 走 `resolveSliceExport` 归一化匹配 |
|
|
167
|
-
| 推送远端时仍残留 `/slices/` 引用 | 线上取不到图(远端没有 `/slices/` 静态服务) | 推送前先跑「切图上传」:`node skills/
|
|
167
|
+
| 推送远端时仍残留 `/slices/` 引用 | 线上取不到图(远端没有 `/slices/` 静态服务) | 推送前先跑「切图上传」:`node skills/platform-card-sync/scripts/slice_upload.mjs --cardId <id>`,改写为 `@filestore/<ns>/<ts>_<name>@3x.png` |
|
|
168
168
|
| 沿用素材原名落地(`编组 4.png` / `8.icon_通用_箭头右@3x.png` / `展开.png`) | 名字看不出用途,无法判断能否复用,后期改名要翻全部卡片 | 按功能/作用语义化命名(§1.1);lint `r59` 告警 |
|
|
169
169
|
| 同图多名各存一份(`chevron_right.png` + `assetAllocRadar_arrow_right.png`) | `src/slices` 堆重复副本,上传后同一张图多个线上地址 | 落地前按 sha256 判重、命中即复用既有名(§3.1);lint `r60` 告警 |
|
|
170
170
|
| 改名只改文件名、不改引用 | schema 指向不存在的文件 → 预览 404 | `rewriteSliceRefs` 成套改写全部卡片 schema/data + 同步 `manifest.json` |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
Inventory exported
|
|
3
|
+
Inventory exported 远端/YNet card schemas.
|
|
4
4
|
|
|
5
5
|
Use this after syncing cards into src/cards to discover component classes,
|
|
6
6
|
properties, style groups, directives, events, action types, and representative
|
|
@@ -393,7 +393,7 @@ const INTENTS = ['blank', 'list', 'detail', 'form', 'dialog', 'tab', 'canvas-hea
|
|
|
393
393
|
* (padding:2px / gap:4px / justify-content:center)。形状与存量卡根节点一致
|
|
394
394
|
* (style-meta.json:marginType / paddingType,'0' = 全边统一值)。
|
|
395
395
|
* 同一形状还有两份内联副本(禁跨包 import):server/core/cardFiles.js 的 blankResetStyle、
|
|
396
|
-
* skills/
|
|
396
|
+
* skills/platform-card-sync api_batch_sync.mjs 的 buildBlankSchema —— 改这里必须同步改那两处。
|
|
397
397
|
*/
|
|
398
398
|
function blankResetStyle() {
|
|
399
399
|
return {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 直接扁平化落到 src/slices/ 后无法从名字看出用途;
|
|
7
7
|
* 2. 同一张图被不同画板/不同卡各导出一份,名字不同但内容完全一致 → src/slices/ 里堆重复副本
|
|
8
8
|
* (实测 60 个文件只有 50 份唯一内容);
|
|
9
|
-
* 3.
|
|
9
|
+
* 3. 重复副本各自上传到远端图片库 → 同一张图产生多个 @filestore 地址。
|
|
10
10
|
*
|
|
11
11
|
* 判定口径(权威 → 辅助):
|
|
12
12
|
* - **内容 sha256**:唯一权威判据,一致即同一张图;
|
|
@@ -209,7 +209,7 @@ export function sliceNameFromRef(ref) {
|
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
/**
|
|
212
|
-
*
|
|
212
|
+
* 远端切图引用判定:已上传远端(@filestore)、外链(http/https/protocol-relative)、内联 data URI。
|
|
213
213
|
* 这类引用天然没有本地文件,不能参与「本地 src/slices 里是否存在」的判定(否则满仓误报)。
|
|
214
214
|
*/
|
|
215
215
|
export function isRemoteSliceRef(ref) {
|