cardbuddy 0.11.0 → 0.12.1

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 (313) hide show
  1. package/README.md +46 -5
  2. package/dist/cli.js +51 -1
  3. package/dist/config.js +51 -16
  4. package/dist/core.js +2 -2
  5. package/dist/init.js +425 -0
  6. package/dist/preview-editor/assets/index-CHuGGIR3.js +276 -0
  7. package/dist/preview-editor/assets/{index-B4VZgNSK.css → index-CQScMhyg.css} +1 -1
  8. package/dist/preview-editor/index.html +2 -2
  9. package/dist/standalone.js +8 -0
  10. package/dist/vendored/mcp/client.mjs +537 -0
  11. package/dist/vendored/mcp/tools.mjs +268 -0
  12. package/dist/vendored/preview/assets/default-image.png +0 -0
  13. package/dist/{server-core → vendored/server/core}/ai-chat.js +82 -38
  14. package/dist/{server-core → vendored/server/core}/browser.js +1 -1
  15. package/dist/{server-core → vendored/server/core}/cardSave.js +8 -3
  16. package/dist/{server-core → vendored/server/core}/cardSync.js +6 -14
  17. package/dist/{server-core → vendored/server/core}/cardValidate.js +11 -8
  18. package/dist/{server-core → vendored/server/core}/index.js +5 -3
  19. package/dist/vendored/server/core/skillPaths.js +124 -0
  20. package/dist/{server-core → vendored/server/core}/sliceUpload.js +5 -8
  21. package/dist/vendored/skills/README.md +93 -0
  22. package/dist/vendored/skills/card-doctor/SKILL.md +52 -0
  23. package/dist/vendored/skills/card-skill-trainer/SKILL.md +312 -0
  24. package/dist/vendored/skills/card-skill-trainer/baselines/v3-r038n.json +43 -0
  25. package/dist/vendored/skills/card-skill-trainer/baselines/v3-r039n.json +47 -0
  26. package/dist/vendored/skills/card-skill-trainer/baselines/v3-r050n.json +48 -0
  27. package/dist/vendored/skills/card-skill-trainer/scripts/build_dataset.py +472 -0
  28. package/dist/vendored/skills/card-skill-trainer/scripts/build_sample.mjs +162 -0
  29. package/dist/vendored/skills/card-skill-trainer/scripts/diagnose_case.mjs +241 -0
  30. package/dist/vendored/skills/card-skill-trainer/scripts/lib/scoring.mjs +525 -0
  31. package/dist/vendored/skills/card-skill-trainer/scripts/round_diff.mjs +133 -0
  32. package/dist/vendored/skills/card-skill-trainer/scripts/run_round.mjs +1043 -0
  33. package/dist/vendored/skills/card-skill-trainer/scripts/score_round.mjs +195 -0
  34. package/dist/vendored/skills/card-skill-trainer/scripts/test_missing_slice.py +122 -0
  35. package/dist/vendored/skills/dsl-card-ai/SKILL.md +191 -0
  36. package/dist/vendored/skills/dsl-card-generator/SKILL.md +480 -0
  37. package/dist/vendored/skills/dsl-card-generator/config/render-contract.json +11 -0
  38. package/dist/vendored/skills/dsl-card-generator/references/amount_formatting.md +138 -0
  39. package/dist/vendored/skills/dsl-card-generator/references/card_index.json +1597 -0
  40. package/dist/vendored/skills/dsl-card-generator/references/card_templates.json +384 -0
  41. package/dist/vendored/skills/dsl-card-generator/references/card_to_summary.md +56 -0
  42. package/dist/vendored/skills/dsl-card-generator/references/component_rules.md +76 -0
  43. package/dist/vendored/skills/dsl-card-generator/references/component_specs.md +393 -0
  44. package/dist/vendored/skills/dsl-card-generator/references/dsl_structure.md +208 -0
  45. package/dist/vendored/skills/dsl-card-generator/references/event_patterns.md +287 -0
  46. package/dist/vendored/skills/dsl-card-generator/references/expression_engine.md +121 -0
  47. package/dist/vendored/skills/dsl-card-generator/references/field_name_dict.json +34 -0
  48. package/dist/vendored/skills/dsl-card-generator/references/field_naming.md +101 -0
  49. package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +928 -0
  50. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/README.md +69 -0
  51. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/examples_index.json +2967 -0
  52. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/patterns.json +2835 -0
  53. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/recipes_v2.json +648 -0
  54. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/recipes_v2_schema.md +295 -0
  55. package/dist/vendored/skills/dsl-card-generator/references/image_diff.md +48 -0
  56. package/dist/vendored/skills/dsl-card-generator/references/image_to_card.md +129 -0
  57. package/dist/vendored/skills/dsl-card-generator/references/ir-format.md +212 -0
  58. package/dist/vendored/skills/dsl-card-generator/references/message-center-pattern.md +149 -0
  59. package/dist/vendored/skills/dsl-card-generator/references/platform_jsapi.md +196 -0
  60. package/dist/vendored/skills/dsl-card-generator/references/previewer_contract.md +54 -0
  61. package/dist/vendored/skills/dsl-card-generator/references/quickref.md +197 -0
  62. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/absolute-badge.json +84 -0
  63. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/accordion-group-toggle.json +18 -0
  64. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/alertCard-pair.json +12 -0
  65. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/alertDialog-call.json +12 -0
  66. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-countdown.json +15 -0
  67. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-donut.json +15 -0
  68. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-gauge-score.json +11 -0
  69. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-radar.json +10 -0
  70. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-title-head.json +13 -0
  71. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/globalSet.json +12 -0
  72. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/label-col-modes.json +21 -0
  73. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/list-expand-collapse.json +17 -0
  74. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/money-format.json +11 -0
  75. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/opacity-switch.json +130 -0
  76. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/progress-bar.json +25 -0
  77. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/tab-billMode.json +15 -0
  78. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/title-underline-overlay.json +21 -0
  79. package/dist/vendored/skills/dsl-card-generator/references/recipes.md +797 -0
  80. package/dist/vendored/skills/dsl-card-generator/references/rules.md +235 -0
  81. package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +177 -0
  82. package/dist/vendored/skills/dsl-card-generator/references/spec_to_schema_patterns.md +609 -0
  83. package/dist/vendored/skills/dsl-card-generator/references/ui_fidelity_rules.md +131 -0
  84. package/dist/vendored/skills/dsl-card-generator/scripts/add_component.mjs +118 -0
  85. package/dist/vendored/skills/dsl-card-generator/scripts/analyze_component_usage.mjs +153 -0
  86. package/dist/vendored/skills/dsl-card-generator/scripts/apply_opacity_pattern.mjs +343 -0
  87. package/dist/vendored/skills/dsl-card-generator/scripts/build_card_index.mjs +108 -0
  88. package/dist/vendored/skills/dsl-card-generator/scripts/build_card_templates.mjs +190 -0
  89. package/dist/vendored/skills/dsl-card-generator/scripts/check_fidelity.mjs +257 -0
  90. package/dist/vendored/skills/dsl-card-generator/scripts/check_magic_values.mjs +79 -0
  91. package/dist/vendored/skills/dsl-card-generator/scripts/crop_slices.mjs +98 -0
  92. package/dist/vendored/skills/dsl-card-generator/scripts/describe_card.mjs +265 -0
  93. package/dist/vendored/skills/dsl-card-generator/scripts/diff_bands.mjs +257 -0
  94. package/dist/vendored/skills/dsl-card-generator/scripts/diff_card.mjs +316 -0
  95. package/dist/vendored/skills/dsl-card-generator/scripts/expand_ir.mjs +873 -0
  96. package/dist/vendored/skills/dsl-card-generator/scripts/extract_handcrafted.py +1199 -0
  97. package/dist/vendored/skills/dsl-card-generator/scripts/extract_subtree.mjs +191 -0
  98. package/dist/vendored/skills/dsl-card-generator/scripts/generate_cards.mjs +389 -0
  99. package/dist/vendored/skills/dsl-card-generator/scripts/inventory_schemas.py +160 -0
  100. package/dist/vendored/skills/dsl-card-generator/scripts/iterate.mjs +700 -0
  101. package/dist/vendored/skills/dsl-card-generator/scripts/lib/design_image.mjs +56 -0
  102. package/dist/vendored/skills/dsl-card-generator/scripts/lib/design_sample.mjs +328 -0
  103. package/dist/vendored/skills/dsl-card-generator/scripts/lib/layout-tokens.mjs +79 -0
  104. package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.mjs +103 -0
  105. package/dist/vendored/skills/dsl-card-generator/scripts/lib/scaffold.mjs +575 -0
  106. package/dist/vendored/skills/dsl-card-generator/scripts/lib/slicePath.mjs +81 -0
  107. package/dist/vendored/skills/dsl-card-generator/scripts/lib/slice_dedupe.mjs +147 -0
  108. package/dist/vendored/skills/dsl-card-generator/scripts/lint_autofix.mjs +363 -0
  109. package/dist/vendored/skills/dsl-card-generator/scripts/lint_card.mjs +1399 -0
  110. package/dist/vendored/skills/dsl-card-generator/scripts/preflight.mjs +249 -0
  111. package/dist/vendored/skills/dsl-card-generator/scripts/register_card_template.mjs +208 -0
  112. package/dist/vendored/skills/dsl-card-generator/scripts/render_baseline.mjs +231 -0
  113. package/dist/vendored/skills/dsl-card-generator/scripts/render_card.mjs +801 -0
  114. package/dist/vendored/skills/dsl-card-generator/scripts/render_diff.mjs +239 -0
  115. package/dist/vendored/skills/dsl-card-generator/scripts/resolve_card_template.mjs +73 -0
  116. package/dist/vendored/skills/dsl-card-generator/scripts/route_input.mjs +447 -0
  117. package/dist/vendored/skills/dsl-card-generator/scripts/scaffold_card.mjs +81 -0
  118. package/dist/vendored/skills/dsl-card-generator/scripts/slices_normalize.mjs +316 -0
  119. package/dist/vendored/skills/dsl-card-generator/scripts/spec_audit.mjs +462 -0
  120. package/dist/vendored/skills/dsl-card-generator/scripts/spec_to_schema_v3.mjs +7735 -0
  121. package/dist/vendored/skills/dsl-card-generator/scripts/spec_tree.mjs +355 -0
  122. package/dist/vendored/skills/dsl-card-generator/scripts/validate_all_schemas.py +71 -0
  123. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/default-image-wzZYQ86K.png +0 -0
  124. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-C9k-07y7.css +1 -0
  125. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-oFRM4f4V.js +55 -0
  126. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/svg-symbols-B2exBrAr.svg +118 -0
  127. package/dist/vendored/skills/dsl-card-generator/vendor/preview/meta/component-meta.json +2714 -0
  128. package/dist/vendored/skills/dsl-card-generator/vendor/preview/meta/directive-meta.json +40 -0
  129. package/dist/vendored/skills/dsl-card-generator/vendor/preview/meta/style-meta.json +1086 -0
  130. package/dist/vendored/{standalone.html → skills/dsl-card-generator/vendor/preview/standalone.html} +2 -1
  131. package/dist/vendored/skills/finmall-card-sync/SKILL.md +262 -0
  132. package/dist/vendored/skills/finmall-session-navigator/SKILL.md +169 -0
  133. package/dist/vendored/skills/finmall-session-navigator/scripts/finmall_login.mjs +882 -0
  134. package/dist/vendored/skills/finmall-session-navigator/scripts/package-lock.json +59 -0
  135. package/dist/vendored/skills/finmall-session-navigator/scripts/package.json +10 -0
  136. package/dist/vendored/skills/finmall-session-navigator/scripts/session-manager.mjs +96 -0
  137. package/dist/vendored/skills/shared/README.md +55 -0
  138. package/dist/vendored/skills/shared/auth-model.md +79 -0
  139. package/dist/vendored/skills/sketch-spec-to-card/SKILL.md +329 -0
  140. package/dist/vendored/skills/sketch-spec-to-card/references/boundary.json +14 -0
  141. package/dist/vendored/skills/sketch-spec-to-card/references/card_refs.json +13 -0
  142. package/dist/vendored/skills/sketch-spec-to-card/references/spec-schema.md +103 -0
  143. package/dist/vendored/skills/sketch-spec-to-card/scripts/build_prompt.py +797 -0
  144. package/dist/vendored/skills/sketch-spec-to-card/scripts/card_refs.py +94 -0
  145. package/dist/vendored/skills/sketch-spec-to-card/scripts/cluster_states.py +706 -0
  146. package/dist/vendored/skills/sketch-spec-to-card/scripts/parse_meaxure.py +1781 -0
  147. package/dist/vendored/skills/sketch-spec-to-card/scripts/png_utils.py +318 -0
  148. package/dist/vendored/skills/sketch-spec-to-card/scripts/vision.py +1118 -0
  149. package/dist/vendored/templates/helloCard/data.json +10 -0
  150. package/dist/vendored/templates/helloCard/schema.json +238 -0
  151. package/dist/vendored/tools/link-skills.mjs +485 -0
  152. package/package.json +2 -1
  153. package/dist/preview-editor/assets/index-jl4YJzKv.js +0 -180
  154. package/dist/vendored/App.vue +0 -2858
  155. package/dist/vendored/ansi.js +0 -185
  156. package/dist/vendored/assets/apple-touch-icon.png +0 -0
  157. package/dist/vendored/assets/default-image.png +0 -0
  158. package/dist/vendored/assets/favicon-16.png +0 -0
  159. package/dist/vendored/assets/favicon-32.png +0 -0
  160. package/dist/vendored/assets/site-icon.png +0 -0
  161. package/dist/vendored/assets/svgIcons/icons.svg +0 -87
  162. package/dist/vendored/assets/svgIcons/index.js +0 -39
  163. package/dist/vendored/components/AiSettings.vue +0 -392
  164. package/dist/vendored/components/AlertCardInstance.vue +0 -58
  165. package/dist/vendored/components/AlertCardLayer.vue +0 -59
  166. package/dist/vendored/components/ApiCallPanel.vue +0 -127
  167. package/dist/vendored/components/ApiPayloadTree.vue +0 -201
  168. package/dist/vendored/components/AppToast.vue +0 -37
  169. package/dist/vendored/components/CardGallery.vue +0 -291
  170. package/dist/vendored/components/CardPreview.vue +0 -72
  171. package/dist/vendored/components/CardSelect.vue +0 -206
  172. package/dist/vendored/components/ChatComposer.vue +0 -413
  173. package/dist/vendored/components/ChatMessage.vue +0 -383
  174. package/dist/vendored/components/ChatPanel.vue +0 -937
  175. package/dist/vendored/components/ComponentPalette.vue +0 -301
  176. package/dist/vendored/components/ConfirmDialog.vue +0 -54
  177. package/dist/vendored/components/DatePickerOverlay.vue +0 -345
  178. package/dist/vendored/components/DebugPanel.vue +0 -1734
  179. package/dist/vendored/components/DeviceOverlay.vue +0 -70
  180. package/dist/vendored/components/Dropdown.vue +0 -193
  181. package/dist/vendored/components/EventEditor.vue +0 -273
  182. package/dist/vendored/components/HoverGuides.vue +0 -122
  183. package/dist/vendored/components/JsonTree.vue +0 -547
  184. package/dist/vendored/components/LogValue.vue +0 -52
  185. package/dist/vendored/components/MarkdownBody.vue +0 -64
  186. package/dist/vendored/components/NewCardDialog.vue +0 -132
  187. package/dist/vendored/components/NodeInspector.vue +0 -295
  188. package/dist/vendored/components/NumberStepper.vue +0 -125
  189. package/dist/vendored/components/PickerOverlay.vue +0 -131
  190. package/dist/vendored/components/PropControl.vue +0 -245
  191. package/dist/vendored/components/PropTree.vue +0 -128
  192. package/dist/vendored/components/RemotePreview.vue +0 -173
  193. package/dist/vendored/components/SelectionOverlay.vue +0 -105
  194. package/dist/vendored/components/StructureTree.vue +0 -1002
  195. package/dist/vendored/components/Switch.vue +0 -38
  196. package/dist/vendored/components/setters/SetBgPosition.vue +0 -64
  197. package/dist/vendored/components/setters/SetBgSize.vue +0 -90
  198. package/dist/vendored/components/setters/SetCode.vue +0 -198
  199. package/dist/vendored/components/setters/SetColor.vue +0 -244
  200. package/dist/vendored/components/setters/SetCustomSelect.vue +0 -58
  201. package/dist/vendored/components/setters/SetExpr.vue +0 -107
  202. package/dist/vendored/components/setters/SetGradient.vue +0 -125
  203. package/dist/vendored/components/setters/SetGrid9.vue +0 -68
  204. package/dist/vendored/components/setters/SetIconSelect.vue +0 -67
  205. package/dist/vendored/components/setters/SetIfForm.vue +0 -61
  206. package/dist/vendored/components/setters/SetImage.vue +0 -43
  207. package/dist/vendored/components/setters/SetList.vue +0 -139
  208. package/dist/vendored/components/setters/SetNumber.vue +0 -118
  209. package/dist/vendored/components/setters/SetNumberStyle.vue +0 -113
  210. package/dist/vendored/components/setters/SetPadding.vue +0 -77
  211. package/dist/vendored/components/setters/SetSegment.vue +0 -160
  212. package/dist/vendored/components/setters/SetSelect.vue +0 -51
  213. package/dist/vendored/components/setters/SetSwitch.vue +0 -49
  214. package/dist/vendored/components/setters/SetText.vue +0 -52
  215. package/dist/vendored/components/setters/props.js +0 -23
  216. package/dist/vendored/fidelity/baseline-messageOverview.json +0 -87
  217. package/dist/vendored/fidelity/calibration.json +0 -29
  218. package/dist/vendored/fidelity/fidelity-messageOverview-after.html +0 -114
  219. package/dist/vendored/fidelity/fidelity-messageOverview-after.json +0 -272
  220. package/dist/vendored/fidelity/fidelity-messageOverview.html +0 -185
  221. package/dist/vendored/fidelity/fidelity-messageOverview.json +0 -336
  222. package/dist/vendored/fidelity/fidelity-messageOverview_v2.html +0 -262
  223. package/dist/vendored/fidelity/fidelity-messageOverview_v2.json +0 -502
  224. package/dist/vendored/fidelity/fidelity-messageOverview_v3.html +0 -201
  225. package/dist/vendored/fidelity/fidelity-messageOverview_v3.json +0 -417
  226. package/dist/vendored/fidelity/fidelity-messageOverview_visual.html +0 -125
  227. package/dist/vendored/fidelity/fidelity-messageOverview_visual.json +0 -338
  228. package/dist/vendored/index.html +0 -16
  229. package/dist/vendored/main.js +0 -37
  230. package/dist/vendored/runtime/ai-api.js +0 -165
  231. package/dist/vendored/runtime/alert-card.js +0 -125
  232. package/dist/vendored/runtime/app-toast.js +0 -33
  233. package/dist/vendored/runtime/clipboard-node.js +0 -78
  234. package/dist/vendored/runtime/clipboard.js +0 -29
  235. package/dist/vendored/runtime/event-model.js +0 -300
  236. package/dist/vendored/runtime/hoverGuide.js +0 -31
  237. package/dist/vendored/runtime/jsapi-mock.js +0 -256
  238. package/dist/vendored/runtime/markdown.js +0 -140
  239. package/dist/vendored/runtime/node-model.js +0 -217
  240. package/dist/vendored/skills/.manifest.json +0 -1101
  241. package/dist/vendored/standalone-main.js +0 -36
  242. package/dist/vendored/styles/ai-chat.css +0 -1850
  243. package/dist/vendored/styles/app.css +0 -3648
  244. package/dist/vendored/styles/card-gallery.css +0 -285
  245. package/dist/vendored/styles/card-select.css +0 -288
  246. package/dist/vendored/styles/components.css +0 -383
  247. package/dist/vendored/styles/dropdown.css +0 -324
  248. package/dist/vendored/styles/json-tree.css +0 -308
  249. package/dist/vendored/styles/node-inspector.css +0 -800
  250. package/dist/vendored/styles/setters.css +0 -1050
  251. package/dist/vendored/styles/standalone.css +0 -27
  252. package/dist/vendored/styles/structure-tree.css +0 -460
  253. /package/dist/vendored/{api.js → preview/api.js} +0 -0
  254. /package/dist/vendored/{assets → preview/assets}/svg-symbols.svg +0 -0
  255. /package/dist/vendored/{components → preview/components}/AbsoluteLayout.vue +0 -0
  256. /package/dist/vendored/{components → preview/components}/Button.vue +0 -0
  257. /package/dist/vendored/{components → preview/components}/Canvas.vue +0 -0
  258. /package/dist/vendored/{components → preview/components}/CheckGroup.vue +0 -0
  259. /package/dist/vendored/{components → preview/components}/Column.vue +0 -0
  260. /package/dist/vendored/{components → preview/components}/CustomPlaceholder.vue +0 -0
  261. /package/dist/vendored/{components → preview/components}/Divider.vue +0 -0
  262. /package/dist/vendored/{components → preview/components}/GridLayout.vue +0 -0
  263. /package/dist/vendored/{components → preview/components}/Icon.vue +0 -0
  264. /package/dist/vendored/{components → preview/components}/Image.vue +0 -0
  265. /package/dist/vendored/{components → preview/components}/Input.vue +0 -0
  266. /package/dist/vendored/{components → preview/components}/ListLayout.vue +0 -0
  267. /package/dist/vendored/{components → preview/components}/Map.vue +0 -0
  268. /package/dist/vendored/{components → preview/components}/MultiColumn.vue +0 -0
  269. /package/dist/vendored/{components → preview/components}/MultiText.vue +0 -0
  270. /package/dist/vendored/{components → preview/components}/ProgressSlider.vue +0 -0
  271. /package/dist/vendored/{components → preview/components}/RadioButton.vue +0 -0
  272. /package/dist/vendored/{components → preview/components}/RenderTree.vue +0 -0
  273. /package/dist/vendored/{components → preview/components}/Root.vue +0 -0
  274. /package/dist/vendored/{components → preview/components}/Row.vue +0 -0
  275. /package/dist/vendored/{components → preview/components}/SvgCode.vue +0 -0
  276. /package/dist/vendored/{components → preview/components}/SvgSymbols.vue +0 -0
  277. /package/dist/vendored/{components → preview/components}/Text.vue +0 -0
  278. /package/dist/vendored/{components → preview/components}/Video.vue +0 -0
  279. /package/dist/vendored/{components → preview/components}/ViewContainer.vue +0 -0
  280. /package/dist/vendored/{components → preview/components}/base.js +0 -0
  281. /package/dist/vendored/{components → preview/components}/icons.js +0 -0
  282. /package/dist/vendored/{components → preview/components}/index.js +0 -0
  283. /package/dist/vendored/{meta → preview/meta}/component-meta.json +0 -0
  284. /package/dist/vendored/{meta → preview/meta}/directive-meta.json +0 -0
  285. /package/dist/vendored/{meta → preview/meta}/style-meta.json +0 -0
  286. /package/dist/vendored/{runtime → preview/runtime}/StyleParser.js +0 -0
  287. /package/dist/vendored/{runtime → preview/runtime}/adapter.js +0 -0
  288. /package/dist/vendored/{runtime → preview/runtime}/assets.js +0 -0
  289. /package/dist/vendored/{runtime → preview/runtime}/canvas-context.js +0 -0
  290. /package/dist/vendored/{runtime → preview/runtime}/card-runtime.js +0 -0
  291. /package/dist/vendored/{runtime → preview/runtime}/evaluate.js +0 -0
  292. /package/dist/vendored/{runtime → preview/runtime}/property.js +0 -0
  293. /package/dist/vendored/{runtime → preview/runtime}/selectionOverlay.js +0 -0
  294. /package/dist/vendored/{runtime → preview/runtime}/toPreview.js +0 -0
  295. /package/dist/{server-core → vendored/server/core}/assetProxy.js +0 -0
  296. /package/dist/{server-core → vendored/server/core}/bbox.js +0 -0
  297. /package/dist/{server-core → vendored/server/core}/buildDataFromSchema.js +0 -0
  298. /package/dist/{server-core → vendored/server/core}/cardFiles.js +0 -0
  299. /package/dist/{server-core → vendored/server/core}/cardHistory.js +0 -0
  300. /package/dist/{server-core → vendored/server/core}/cardImage.js +0 -0
  301. /package/dist/{server-core → vendored/server/core}/cardImportMeta.js +0 -0
  302. /package/dist/{server-core → vendored/server/core}/cardListCache.js +0 -0
  303. /package/dist/{server-core → vendored/server/core}/cardOpenFolder.js +0 -0
  304. /package/dist/{server-core → vendored/server/core}/cardSidecar.js +0 -0
  305. /package/dist/{server-core → vendored/server/core}/codeBridge.js +0 -0
  306. /package/dist/{server-core → vendored/server/core}/logStream.js +0 -0
  307. /package/dist/{server-core → vendored/server/core}/logger.js +0 -0
  308. /package/dist/{server-core → vendored/server/core}/nodeModels.js +0 -0
  309. /package/dist/{server-core → vendored/server/core}/renderStatus.js +0 -0
  310. /package/dist/{server-core → vendored/server/core}/screenshot.js +0 -0
  311. /package/dist/{server-core → vendored/server/core}/sliceStatic.js +0 -0
  312. /package/dist/vendored/{meta → skills/dsl-card-generator/vendor/preview/meta}/action-meta.json +0 -0
  313. /package/dist/{server-core/_vendor → vendored/tools}/finmall-env.mjs +0 -0
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * preflight.mjs — 生成前/后一键机械检查(替代 AI 手动过 Pre-flight Checklist)。
4
+ *
5
+ * 检查项(对应 quickref.md Pre-flight Checklist 12 条中可机械化的 10 条):
6
+ * 1. 元素清单:spec text/slice/shape 数 vs schema 节点数(R29)
7
+ * 2. 组件白名单:className ∈ component-meta.json(R28)
8
+ * 3. Image itemValueFrom:静态路径必须 "2",变量 "1"(R25)
9
+ * 4. Image rect.height:必须有显式高度(R32,否则被 aspect-ratio:16/9 压缩)
10
+ * 5. 切图文件名:含空格/特殊字符必须重命名(R31)
11
+ * 6. Icon eventaction:嵌套格式 event.eventKey(R30)
12
+ * 7. 百分比表达式:必须 Math.round(R23)
13
+ * 8. 分割线用 Divider:禁止 Row 模拟分割线(R28)
14
+ * 9. 卡片根样式全量提取:bg shape 有 border 但 schema 根容器未设(R33)
15
+ * 10. 空样式 itemId:style 子项 itemId 为空串 → 运行时静默丢弃
16
+ *
17
+ * 用法:
18
+ * node preflight.mjs --spec UI/<画板>/spec.json --id <卡id>
19
+ * node preflight.mjs --id <卡id> # 跳过 spec 比对(改卡时)
20
+ * node preflight.mjs --id <卡id> --json # 机器可读输出
21
+ *
22
+ * 退出码:0 = 通过,1 = 有 ERROR
23
+ */
24
+ import fs from 'node:fs'
25
+ import path from 'node:path'
26
+ import { metaDir, cardsDir, slicesDir } from './lib/paths.mjs'
27
+
28
+ /* ── args ── */
29
+ const args = {}
30
+ for (let i = 2; i < process.argv.length; i++) {
31
+ if (process.argv[i] === '--spec') args.spec = process.argv[++i]
32
+ else if (process.argv[i] === '--id') args.id = process.argv[++i]
33
+ else if (process.argv[i] === '--json') args.json = true
34
+ }
35
+
36
+ function loadJSONSafe(p) { try { return JSON.parse(fs.readFileSync(p, 'utf8')) } catch { return null } }
37
+
38
+ const results = []
39
+ function check(code, severity, message, detail) {
40
+ results.push({ code, severity, message, detail: detail || '' })
41
+ }
42
+
43
+ /* ── Load schema ── */
44
+ const schemaDir = path.join(cardsDir, args.id || '')
45
+ const schemaPath = path.join(schemaDir, 'schema.json')
46
+ const schema = loadJSONSafe(schemaPath)
47
+ if (!schema) {
48
+ console.error(`[preflight] 找不到 schema: ${schemaPath}`)
49
+ process.exit(1)
50
+ }
51
+
52
+ /* ── Load spec (optional) ── */
53
+ const spec = args.spec ? loadJSONSafe(args.spec) : null
54
+ const specCards = spec?.cards || []
55
+
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 || {}))
60
+
61
+ /* ── helpers ── */
62
+ function getStyleChild(node, groupKey, childKey) {
63
+ for (const sp of node?.style?.properties || []) {
64
+ if (sp.itemKey === groupKey) {
65
+ for (const c of sp.itemChildren?.properties || []) {
66
+ if (c.itemKey === childKey) return c
67
+ }
68
+ }
69
+ }
70
+ return null
71
+ }
72
+ function getProp(node, key) {
73
+ for (const p of node?.properties || []) {
74
+ if (p.itemKey === key) return p
75
+ }
76
+ return null
77
+ }
78
+
79
+ /* ── Walk all nodes ── */
80
+ const allNodes = []
81
+ function walkNode(node) {
82
+ if (!node || typeof node !== 'object') return
83
+ allNodes.push(node)
84
+ for (const c of node.components || []) walkNode(c)
85
+ for (const s of node.slots || []) for (const c of s.components || []) walkNode(c)
86
+ }
87
+ for (const c of schema.view?.components || []) walkNode(c)
88
+ const viewNode = schema.view
89
+
90
+ /* ══ Check 1: Element count vs schema nodes (R29) ══ */
91
+ if (specCards.length > 0) {
92
+ const cardIdx = Math.min(1, specCards.length - 1) // main card
93
+ const card = specCards[cardIdx]
94
+ const elements = card.elements || []
95
+ const specTexts = elements.filter(e => e.type === 'text').length
96
+ const specSlices = elements.filter(e => e.type === 'slice').length
97
+ const specShapes = elements.filter(e => e.type === 'shape').length
98
+ const schemaTexts = allNodes.filter(n => n.className === 'Text').length
99
+ const schemaImages = allNodes.filter(n => n.className === 'Image').length
100
+ const schemaDividers = allNodes.filter(n => n.className === 'Divider').length
101
+ // Node count should be >= spec element count (merging is OK, missing is not)
102
+ const specTotal = specTexts + specSlices + specShapes
103
+ const schemaTotal = allNodes.length
104
+ if (schemaTotal < specTotal * 0.5) {
105
+ check('r29-element-count', 'warn', `Schema 节点数(${schemaTotal}) 远少于 spec 元素数(${specTotal}) → 可能遗漏元素(R29)`, `spec: text=${specTexts} slice=${specSlices} shape=${specShapes} | schema: Text=${schemaTexts} Image=${schemaImages} Divider=${schemaDividers} total=${schemaTotal}`)
106
+ }
107
+ }
108
+
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)`)
113
+ }
114
+ }
115
+
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
+ }
128
+ }
129
+
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
+ }
136
+ }
137
+
138
+ /* ══ Check 5: Slice filename spaces (R31) ══ */
139
+ for (const n of allNodes.filter(n => n.className === 'Image')) {
140
+ const valProp = getProp(n, 'value')
141
+ if (!valProp) continue
142
+ const val = String(valProp.itemValue || '')
143
+ if (val.includes('/slices/') && /\s/.test(val.replace('/slices/', ''))) {
144
+ check('r31-slice-filename-space', 'error', `Image "${n.componentId}" 引用路径含空格: "${val}" → CSS url() 空格断裂 → background-image:none(R31)。重命名为纯 ASCII`)
145
+ }
146
+ }
147
+
148
+ /* ══ Check 6: Icon eventaction format (R30) ══ */
149
+ for (const n of allNodes.filter(n => n.className === 'Icon')) {
150
+ const eas = n.eventactions || []
151
+ for (const ea of eas) {
152
+ if (ea.event?.eventKey && !ea.action) {
153
+ check('r30-icon-no-action', 'warn', `Icon "${n.componentId}" 有 event 但无 action → 可能双触发(R30)`)
154
+ }
155
+ }
156
+ }
157
+
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
+ }
171
+ }
172
+ for (const n of allNodes) walkExprs(n)
173
+
174
+ /* ══ Check 8: Divider for separators (R28) ══ */
175
+ // Heuristic: Row with height ≤2 and background color but no children = likely a divider
176
+ for (const n of allNodes.filter(n => n.className === 'Row')) {
177
+ const h = getStyleChild(n, 'rect', 'height')
178
+ const bg = getStyleChild(n, 'background', 'backgroundColor')
179
+ const hasChildren = (n.slots || []).some(s => (s.components || []).length > 0)
180
+ const hVal = h ? parseFloat(String(h.itemValue)) : NaN
181
+ if (!hasChildren && !isNaN(hVal) && hVal > 0 && hVal <= 2 && bg) {
182
+ check('r28-row-as-divider', 'warn', `Row "${n.componentId}" height=${hVal} + bg 色块但无子节点 → 疑似分割线,应用 Divider 组件(R28)`)
183
+ }
184
+ }
185
+
186
+ /* ══ Check 9: Card root style completeness (R33) ══ */
187
+ if (specCards.length > 0) {
188
+ const card = specCards[0]
189
+ // Find card bg shape
190
+ const bgShape = (card.elements || []).find(e =>
191
+ e.type === 'shape' &&
192
+ Math.abs(e.rect?.width - card.rect?.width) < 2 &&
193
+ Math.abs(e.rect?.height - card.rect?.height) < 2
194
+ )
195
+ if (bgShape?.style) {
196
+ const st = bgShape.style
197
+ // Check border
198
+ if (st.border?.isBorder) {
199
+ const rootBorder = getStyleChild(viewNode, 'border', 'isBorder')
200
+ // Also check first-level card container
201
+ const cardContainer = (viewNode?.components || [])[0]
202
+ const ccBorder = cardContainer ? getStyleChild(cardContainer, 'border', 'isBorder') : null
203
+ if (!rootBorder && !ccBorder) {
204
+ check('r33-root-missing-border', 'warn', `spec 卡片 bg shape 有 border(isBorder=true, w=${st.border.width}, color=${st.border.color}) 但 schema 根容器未设 border → 视觉缺失(R33)`)
205
+ }
206
+ }
207
+ // Check shadow
208
+ if (st.shadow?.isShadow) {
209
+ const rootShadow = getStyleChild(viewNode, 'shadow', 'isShadow')
210
+ const cardContainer = (viewNode?.components || [])[0]
211
+ const ccShadow = cardContainer ? getStyleChild(cardContainer, 'shadow', 'isShadow') : null
212
+ if (!rootShadow && !ccShadow) {
213
+ check('r33-root-missing-shadow', 'warn', `spec 卡片 bg shape 有 shadow 但 schema 根容器未设 → 视觉缺失(R33)`)
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+ /* ══ Check 10: Empty style itemId ══ */
220
+ for (const n of allNodes) {
221
+ for (const sp of n.style?.properties || []) {
222
+ for (const c of sp.itemChildren?.properties || []) {
223
+ if (c.itemId !== undefined && String(c.itemId).trim() === '') {
224
+ check('empty-style-itemid', 'error', `"${n.componentId}" style.${sp.itemKey}.${c.itemKey} itemId 为空 → 运行时静默丢弃该样式`)
225
+ }
226
+ }
227
+ }
228
+ }
229
+
230
+ /* ── Output ── */
231
+ const errors = results.filter(r => r.severity === 'error')
232
+ const warns = results.filter(r => r.severity === 'warn')
233
+
234
+ if (args.json) {
235
+ console.log(JSON.stringify({ ok: errors.length === 0, errors, warnings: warns }, null, 2))
236
+ } else {
237
+ if (results.length === 0) {
238
+ console.log(`[preflight] ✅ ${args.id || ''} 全部检查通过(0 error, 0 warn)`)
239
+ } else {
240
+ console.log(`[preflight] ${args.id || ''} 共 ${results.length} 条:${errors.length} error / ${warns.length} warn`)
241
+ for (const r of [...errors, ...warns]) {
242
+ const icon = r.severity === 'error' ? '❌' : '⚠️'
243
+ console.log(` ${icon} ${r.code}: ${r.message}`)
244
+ if (r.detail) console.log(` ${r.detail}`)
245
+ }
246
+ }
247
+ }
248
+
249
+ process.exit(errors.length > 0 ? 1 : 0)
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * register_card_template.mjs — 模板沉淀闭环的「登记时刻」。
4
+ *
5
+ * 首次做出一种新卡片模式(布局/交互在模板库中没有近亲)并交付后,用本工具把它
6
+ * 登记进整卡模板库:下次同类需求直接 scaffold --from 克隆秒出,不再走完整生成链路。
7
+ *
8
+ * 两种用法:
9
+ * ① 新颖性检查(登记前先问一句,只报告不写入):
10
+ * node register_card_template.mjs --id <cardId> --check [--threshold 0.6]
11
+ * → 输出本卡与各簇代表的结构签名相似度(Jaccard),给出「新模式/已有近亲」结论;
12
+ * 相似度 < 阈值 = 新模式,建议新建簇登记;否则建议顶替该簇或不登记。
13
+ *
14
+ * ② 登记(人工确认后执行):
15
+ * node register_card_template.mjs --id <cardId> --cluster <簇key> [--keywords a,b,c]
16
+ * → 健康门禁(validate 0 error + lint 0 error,模板必须是健康卡)→
17
+ * 簇已存在:顶替代表卡并 pin(重建不被打分覆盖);簇不存在:新建自定义簇。
18
+ *
19
+ * 安全语义:--check 永不写盘;登记前必须过健康门禁;重建(build_card_templates.mjs)
20
+ * 保留 pinned 代表与 keywords。本工具不做自动登记——是否沉淀永远由人决定。
21
+ *
22
+ * 退出码:0 = 正常;1 = 健康门禁未过 / 参数错误。
23
+ */
24
+ import fs from 'node:fs'
25
+ import path from 'node:path'
26
+ import { execFileSync } from 'node:child_process'
27
+ import { fileURLToPath } from 'node:url'
28
+ import { cardsDir, skillRoot } from './lib/paths.mjs'
29
+ import { extractCardFeatures, loadRegistry } from './build_card_templates.mjs'
30
+
31
+ const OUT = path.join(skillRoot, 'references', 'card_templates.json')
32
+ const VALIDATOR = path.join(skillRoot, 'scripts', 'validate_schema.py')
33
+ const LINTER = path.join(skillRoot, 'scripts', 'lint_card.mjs')
34
+ const DEFAULT_THRESHOLD = 0.6
35
+
36
+ function parseArgs(argv) {
37
+ const out = { id: '', cluster: '', keywords: [], check: false, threshold: DEFAULT_THRESHOLD }
38
+ for (let i = 0; i < argv.length; i++) {
39
+ const a = argv[i]
40
+ if (a === '--check') out.check = true
41
+ else if (a === '--id') out.id = argv[++i] || ''
42
+ else if (a === '--cluster') out.cluster = argv[++i] || ''
43
+ else if (a === '--keywords') out.keywords = (argv[++i] || '').split(/[,,]/).map((s) => s.trim()).filter(Boolean)
44
+ else if (a === '--threshold') out.threshold = Number(argv[++i]) || DEFAULT_THRESHOLD
45
+ }
46
+ return out
47
+ }
48
+
49
+ function resolvePython() {
50
+ if (process.env.DSL_PYTHON) return process.env.DSL_PYTHON
51
+ for (const cmd of ['python3', 'python']) {
52
+ try {
53
+ execFileSync(cmd, ['--version'], { stdio: 'ignore', timeout: 5000 })
54
+ return cmd
55
+ } catch { /* try next */ }
56
+ }
57
+ return null
58
+ }
59
+
60
+ /** 健康门禁:validate 0 error + lint 0 error。返回 { ok, reasons[] } */
61
+ function healthGate(id) {
62
+ const reasons = []
63
+ const schemaPath = path.join(cardsDir, id, 'schema.json')
64
+ const python = resolvePython()
65
+ if (!python) {
66
+ reasons.push('python-not-found:无法跑 validate 门禁')
67
+ } else {
68
+ try {
69
+ const raw = execFileSync(python, [VALIDATOR, schemaPath, '--json'], { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 })
70
+ const v = JSON.parse(raw)
71
+ if (!v.ok) reasons.push(`validate ${v.errors.length} 个 error:${(v.errors[0] || '').slice(0, 80)}`)
72
+ } catch (e) {
73
+ reasons.push(`validate 执行失败:${String(e.message).slice(0, 120)}`)
74
+ }
75
+ }
76
+ try {
77
+ const raw = execFileSync(process.execPath, [LINTER, '--id', id, '--json'], { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 })
78
+ const l = JSON.parse(raw)
79
+ const findings = (l.reports && l.reports[0] && l.reports[0].findings) || []
80
+ const errors = findings.filter((f) => f.severity === 'error')
81
+ if (errors.length) reasons.push(`lint ${errors.length} 个 error:${(errors[0].message || '').slice(0, 80)}`)
82
+ } catch (e) {
83
+ // lint 有 error 时 exit≠0,stdout 仍在 error 对象上
84
+ const raw = e && e.stdout
85
+ if (raw) {
86
+ try {
87
+ const l = JSON.parse(raw)
88
+ const findings = (l.reports && l.reports[0] && l.reports[0].findings) || []
89
+ const errors = findings.filter((f) => f.severity === 'error')
90
+ if (errors.length) reasons.push(`lint ${errors.length} 个 error:${(errors[0].message || '').slice(0, 80)}`)
91
+ else reasons.push(`lint 执行异常:exit ${e.code}`)
92
+ } catch { reasons.push('lint 输出解析失败') }
93
+ } else {
94
+ reasons.push(`lint 执行失败:${String(e.message).slice(0, 120)}`)
95
+ }
96
+ }
97
+ return { ok: reasons.length === 0, reasons }
98
+ }
99
+
100
+ /** 结构签名 → 可比集合(comp:xxx / recipe:xxx 前缀区分两域) */
101
+ function signatureSet(f) {
102
+ return new Set([...f.components.map((c) => `comp:${c}`), ...f.recipes.map((r) => `recipe:${r}`)])
103
+ }
104
+
105
+ function jaccard(a, b) {
106
+ let inter = 0
107
+ for (const x of a) if (b.has(x)) inter++
108
+ const union = a.size + b.size - inter
109
+ return union === 0 ? 1 : inter / union
110
+ }
111
+
112
+ function main() {
113
+ const opts = parseArgs(process.argv.slice(2))
114
+ if (!opts.id) {
115
+ console.error('[usage] --id <cardId> --check [--threshold 0.6] (新颖性检查,只报告)')
116
+ console.error('[usage] --id <cardId> --cluster <簇key> [--keywords a,b,c] (登记,过健康门禁)')
117
+ process.exit(1)
118
+ }
119
+ const features = extractCardFeatures(opts.id)
120
+ if (!features) {
121
+ console.error(`[error] 卡片不存在或 schema 无法解析:${opts.id}`)
122
+ process.exit(1)
123
+ }
124
+ const registry = loadRegistry()
125
+ const clusters = registry.clusters || []
126
+ const sig = signatureSet(features)
127
+
128
+ // ---- ① 新颖性检查 ----
129
+ if (opts.check) {
130
+ const scored = clusters
131
+ .map((c) => ({
132
+ key: c.key,
133
+ sourceCard: c.template.sourceCard,
134
+ similarity: jaccard(sig, signatureSet(c.template)),
135
+ }))
136
+ .sort((a, b) => b.similarity - a.similarity)
137
+ console.log(`\n🔍 新颖性检查:${opts.id}(${features.name},${features.nodeCount} 节点)`)
138
+ console.log(` 签名:components=[${features.components.join(',')}] recipes=[${features.recipes.join(',') || '无'}]`)
139
+ for (const s of scored.slice(0, 5)) {
140
+ console.log(` vs ${s.key.padEnd(12)}(${s.sourceCard}) 相似度 ${(s.similarity * 100).toFixed(1)}%`)
141
+ }
142
+ const top = scored[0]
143
+ if (!top || top.similarity < opts.threshold) {
144
+ console.log(`\n ✅ 结论:新模式(最高相似度 ${top ? (top.similarity * 100).toFixed(1) + '%' : '无簇'} < ${opts.threshold * 100}%)。`)
145
+ console.log(` 建议登记为新簇:node register_card_template.mjs --id ${opts.id} --cluster <新簇key> --keywords <意图词,逗号分隔>`)
146
+ } else {
147
+ console.log(`\n ℹ️ 结论:已有近亲(${top.key} 簇 / ${top.sourceCard},相似度 ${(top.similarity * 100).toFixed(1)}%)。`)
148
+ console.log(` 若本卡版式明显更优,可顶替该簇代表:node register_card_template.mjs --id ${opts.id} --cluster ${top.key}`)
149
+ }
150
+ return
151
+ }
152
+
153
+ // ---- ② 登记 ----
154
+ if (!opts.cluster) {
155
+ console.error('[error] 登记需要 --cluster <簇key>(先用 --check 判断新建还是顶替)')
156
+ process.exit(1)
157
+ }
158
+ if (!/^[a-z][a-z0-9-]*$/.test(opts.cluster)) {
159
+ console.error('[error] 簇 key 必须是小写字母/数字/连字符(如 progress-drawer)')
160
+ process.exit(1)
161
+ }
162
+ const gate = healthGate(opts.id)
163
+ if (!gate.ok) {
164
+ console.error(`\n🚫 健康门禁未过,模板必须是健康卡:`)
165
+ for (const r of gate.reasons) console.error(` - ${r}`)
166
+ process.exit(1)
167
+ }
168
+
169
+ const existing = clusters.find((c) => c.key === opts.cluster)
170
+ const template = {
171
+ sourceCard: features.id,
172
+ name: features.name,
173
+ tags: features.tags,
174
+ recipes: features.recipes,
175
+ components: features.components,
176
+ nodeCount: features.nodeCount,
177
+ pinned: true,
178
+ }
179
+ if (existing) {
180
+ existing.template = template
181
+ existing.keywords = [...new Set([...(existing.keywords || []), ...opts.keywords])]
182
+ if (!existing.members.includes(features.id)) existing.members.push(features.id)
183
+ existing.cardCount = existing.members.length
184
+ console.log(`\n📌 已顶替并 pin:簇 ${opts.cluster} 代表卡 → ${features.id}(${features.name})`)
185
+ if (!existing.custom && features.cluster !== opts.cluster) {
186
+ console.log(` ⚠️ 注意:该卡结构签名自动归类为「${features.cluster}」簇,与目标簇不同——已按你的人工意图 pin,重建不会回退。`)
187
+ }
188
+ } else {
189
+ clusters.push({
190
+ key: opts.cluster,
191
+ custom: true,
192
+ keywords: [...opts.keywords],
193
+ cardCount: 1,
194
+ template,
195
+ members: [features.id],
196
+ })
197
+ console.log(`\n🌱 已新建自定义簇 ${opts.cluster}:代表卡 ${features.id}(${features.name})`)
198
+ }
199
+ if (opts.keywords.length) console.log(` 意图关键词:${opts.keywords.join(' / ')}`)
200
+
201
+ registry.generatedAt = new Date().toISOString()
202
+ fs.mkdirSync(path.dirname(OUT), { recursive: true })
203
+ fs.writeFileSync(OUT, JSON.stringify({ ...registry, clusters }, null, 2) + '\n')
204
+ console.log(` 注册表已更新:${OUT}`)
205
+ console.log(` 下次同类需求:scaffold_card.mjs --from ${features.id} --id <新卡id>`)
206
+ }
207
+
208
+ main()
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * render_baseline.mjs — 渲染回归基线生成 + 像素 diff。
4
+ *
5
+ * 用途:
6
+ * 改了某个组件的 Vue 渲染器后,没人发现某张卡视觉变了。
7
+ * 本工具给每张导出卡在固定数据/视口下存一份 PNG 基线,
8
+ * 后续任何时候重跑都能比对当前渲染 vs 基线的像素差异。
9
+ *
10
+ * 两种模式:
11
+ * --snapshot 生成/刷新基线(首次建立、渲染器改完后更新基线)
12
+ * --diff 对比当前渲染与基线(CI 回归检测)
13
+ * --snapshot --diff 先快照再立即对比(与上次的差异)
14
+ *
15
+ * 实现:复用同目录 render_card.mjs 的独立渲染器(vendor/preview 构建产物 + 系统 Chrome CDP),
16
+ * 不依赖 npm run dev、vite/vue/playwright,也不占用 5180/8787。
17
+ *
18
+ * 基线存放:<project>/.card-baselines/<cardId>.png
19
+ * diff 输出:<project>/.card-baselines/__diff__/<cardId>.png(红色高亮差异区)+ 控制台汇总
20
+ *
21
+ * 用法:
22
+ * node render_baseline.mjs --snapshot # 全部卡建立基线
23
+ * node render_baseline.mjs --snapshot --id <cardId> # 单卡刷新基线
24
+ * node render_baseline.mjs --diff # 全部卡对比基线
25
+ * node render_baseline.mjs --diff --threshold 0.05 # 差异像素比例 > 5% 才报警
26
+ * node render_baseline.mjs --json # JSON 输出(CI 友好)
27
+ *
28
+ * 前置条件:
29
+ * - 系统 Chrome 已安装(独立渲染器用 CDP 启动 headless Chrome)
30
+ * - 可选:sharp 已装(用于真实像素 diff;未装时降级为文件大小比较)
31
+ *
32
+ * 退出码:
33
+ * 0 成功 / 无差异;1 参数错;3 IO 错;
34
+ * --diff 模式下有差异时退出 70(CI 可识别)
35
+ */
36
+ import fs from 'node:fs'
37
+ import path from 'node:path'
38
+
39
+ import { cardsDir, baselinesDir } from './lib/paths.mjs'
40
+ import { renderCardScreenshot } from './render_card.mjs'
41
+
42
+ const BASELINE_DIR = baselinesDir
43
+ const DIFF_DIR = path.join(BASELINE_DIR, '__diff__')
44
+ const DEFAULT_WIDTH = 390
45
+ const CARD_SCREEN_WIDTH = 375
46
+ const DEFAULT_THRESHOLD = 0.01 // 差异像素占比 > 1% 报警
47
+
48
+ /* ------------------------------------------------------------------ */
49
+ /* 参数解析 */
50
+ /* ------------------------------------------------------------------ */
51
+ function parseArgs(argv) {
52
+ const out = { snapshot: false, diff: false, id: '', threshold: DEFAULT_THRESHOLD, json: false }
53
+ for (let i = 0; i < argv.length; i++) {
54
+ const a = argv[i]
55
+ if (a === '--snapshot') out.snapshot = true
56
+ else if (a === '--diff') out.diff = true
57
+ else if (a === '--id') out.id = argv[++i]
58
+ else if (a === '--threshold') out.threshold = parseFloat(argv[++i])
59
+ else if (a === '--json') out.json = true
60
+ else if (a === '-h' || a === '--help') {
61
+ console.log(`Usage: node render_baseline.mjs [options]
62
+
63
+ Options:
64
+ --snapshot 生成/刷新基线(写入 <project>/.card-baselines/<id>.png)
65
+ --diff 对比当前渲染与基线(输出差异报告)
66
+ --id <cardId> 只处理单张卡(缺省全部 src/cards/*)
67
+ --threshold <0-1> diff 模式下差异像素占比阈值(缺省 0.01 = 1%)
68
+ --json JSON 输出(CI 友好)
69
+ -h, --help 显示本帮助
70
+
71
+ Examples:
72
+ node render_baseline.mjs --snapshot # 首次建立基线
73
+ node render_baseline.mjs --diff # 改完渲染器后回归检测
74
+ node render_baseline.mjs --snapshot --id account # 单卡刷新`)
75
+ process.exit(0)
76
+ }
77
+ }
78
+ if (!out.snapshot && !out.diff) {
79
+ console.error('[error] 必须指定 --snapshot 或 --diff(或两者)')
80
+ process.exit(1)
81
+ }
82
+ return out
83
+ }
84
+
85
+ /* ------------------------------------------------------------------ */
86
+ /* 截图获取(复用 render_card.mjs 的独立渲染器) */
87
+ /* ------------------------------------------------------------------ */
88
+ async function fetchScreenshot(id, width = DEFAULT_WIDTH) {
89
+ // 旧截图端点 width=390 表示外层视口;卡片屏幕实际固定 375。独立渲染器按卡宽收参。
90
+ const cardWidth = width === DEFAULT_WIDTH ? CARD_SCREEN_WIDTH : width
91
+ const r = await renderCardScreenshot({ id, width: cardWidth, format: 'png' })
92
+ if (r.error) throw new Error(r.message || r.error)
93
+ return Buffer.from(r.base64, 'base64')
94
+ }
95
+
96
+ /* ------------------------------------------------------------------ */
97
+ /* 像素 diff(用 sharp;若未装则降级为文件大小/hash 比较) */
98
+ /* ------------------------------------------------------------------ */
99
+ async function pixelDiff(baselineBuf, currentBuf, threshold) {
100
+ // 快速路径:字节完全一致 → 0 差异
101
+ if (Buffer.compare(baselineBuf, currentBuf) === 0) {
102
+ return { diffRatio: 0, diffPixels: 0, totalPixels: 0, note: 'byte-identical' }
103
+ }
104
+ // 尝试加载 sharp 做真实像素 diff
105
+ try {
106
+ const sharp = (await import('sharp')).default
107
+ const a = sharp(baselineBuf).ensureAlpha().raw()
108
+ const b = sharp(currentBuf).ensureAlpha().raw()
109
+ const [metaA, metaB] = await Promise.all([a.metadata(), b.metadata()])
110
+ if (metaA.width !== metaB.width || metaA.height !== metaB.height) {
111
+ return {
112
+ diffRatio: 1,
113
+ diffPixels: -1,
114
+ totalPixels: -1,
115
+ note: `size-changed: ${metaA.width}x${metaA.height} → ${metaB.width}x${metaB.height}`,
116
+ }
117
+ }
118
+ const w = metaA.width
119
+ const h = metaA.height
120
+ const total = w * h
121
+ const dataA = await a.toBuffer()
122
+ const dataB = await b.toBuffer()
123
+ let diffPixels = 0
124
+ const channels = Math.min(metaA.channels, metaB.channels, 3)
125
+ // 简单像素差异:任一通道差 > 16 算差异
126
+ for (let i = 0; i < total; i++) {
127
+ const off = i * metaA.channels
128
+ let isDiff = false
129
+ for (let c = 0; c < channels; c++) {
130
+ if (Math.abs(dataA[off + c] - dataB[off + c]) > 16) {
131
+ isDiff = true
132
+ break
133
+ }
134
+ }
135
+ if (isDiff) diffPixels++
136
+ }
137
+ const ratio = diffPixels / total
138
+ return { diffRatio: ratio, diffPixels, totalPixels: total, note: 'pixel-diff' }
139
+ } catch {
140
+ // sharp 未装:降级为文件大小比较
141
+ return {
142
+ diffRatio: baselineBuf.length === currentBuf.length ? 0 : 1,
143
+ diffPixels: -1,
144
+ totalPixels: -1,
145
+ note: 'fallback-size-diff(装 sharp 可启用真实像素 diff)',
146
+ }
147
+ }
148
+ }
149
+
150
+ /* ------------------------------------------------------------------ */
151
+ /* 主流程 */
152
+ /* ------------------------------------------------------------------ */
153
+ function listCardIds(filterId) {
154
+ if (filterId) return [filterId]
155
+ return fs
156
+ .readdirSync(cardsDir)
157
+ .filter((d) => fs.existsSync(path.join(cardsDir, d, 'schema.json')))
158
+ }
159
+
160
+ async function snapshotOne(id) {
161
+ const buf = await fetchScreenshot(id)
162
+ const outFile = path.join(BASELINE_DIR, `${id}.png`)
163
+ fs.mkdirSync(BASELINE_DIR, { recursive: true })
164
+ fs.writeFileSync(outFile, buf)
165
+ return { id, bytes: buf.length, path: outFile }
166
+ }
167
+
168
+ async function diffOne(id, threshold) {
169
+ const baselinePath = path.join(BASELINE_DIR, `${id}.png`)
170
+ if (!fs.existsSync(baselinePath)) {
171
+ return { id, skipped: true, reason: 'no-baseline(先跑 --snapshot)' }
172
+ }
173
+ const baselineBuf = fs.readFileSync(baselinePath)
174
+ const currentBuf = await fetchScreenshot(id)
175
+ const r = await pixelDiff(baselineBuf, currentBuf, threshold)
176
+ // 差异超阈值时保存当前截图到 __diff__/ 便于人工查看
177
+ if (r.diffRatio > threshold) {
178
+ fs.mkdirSync(DIFF_DIR, { recursive: true })
179
+ fs.writeFileSync(path.join(DIFF_DIR, `${id}.current.png`), currentBuf)
180
+ fs.writeFileSync(path.join(DIFF_DIR, `${id}.baseline.png`), baselineBuf)
181
+ }
182
+ return { id, ...r, overThreshold: r.diffRatio > threshold }
183
+ }
184
+
185
+ async function main() {
186
+ const args = parseArgs(process.argv.slice(2))
187
+
188
+ fs.mkdirSync(BASELINE_DIR, { recursive: true })
189
+ const ids = listCardIds(args.id)
190
+ const results = []
191
+
192
+ for (const id of ids) {
193
+ try {
194
+ if (args.snapshot) {
195
+ const r = await snapshotOne(id)
196
+ if (!args.json) console.log(`[snapshot] ${id} → ${r.bytes} bytes`)
197
+ results.push({ op: 'snapshot', ...r })
198
+ }
199
+ if (args.diff) {
200
+ const r = await diffOne(id, args.threshold)
201
+ if (!args.json) {
202
+ if (r.skipped) console.log(`[diff] ${id}: 跳过(${r.reason})`)
203
+ else if (r.overThreshold) {
204
+ const pct = (r.diffRatio * 100).toFixed(2)
205
+ console.log(`[diff] ${id}: ❌ 差异 ${pct}% (${r.diffPixels}/${r.totalPixels} px) — 已存 ${DIFF_DIR}/${id}.{current,baseline}.png`)
206
+ } else {
207
+ console.log(`[diff] ${id}: ✅ 一致 (${r.note})`)
208
+ }
209
+ }
210
+ results.push({ op: 'diff', ...r })
211
+ }
212
+ } catch (e) {
213
+ if (!args.json) console.error(`[error] ${id}: ${e.message}`)
214
+ results.push({ op: args.snapshot ? 'snapshot' : 'diff', id, error: e.message })
215
+ }
216
+ }
217
+
218
+ if (args.json) console.log(JSON.stringify({ results }, null, 2))
219
+
220
+ // diff 模式下,有任何差异 → exit 70(CI 可识别)
221
+ if (args.diff) {
222
+ const overCount = results.filter((r) => r.op === 'diff' && r.overThreshold).length
223
+ if (!args.json && overCount) {
224
+ console.log(`\n共 ${overCount} 张卡差异超阈值(${args.threshold * 100}%)`)
225
+ }
226
+ if (overCount) process.exit(70)
227
+ }
228
+ process.exit(0)
229
+ }
230
+
231
+ main()