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,700 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * iterate.mjs — 单进程一条龙迭代闭环(expand → lint → render → pixel-diff)。
4
+ *
5
+ * 相比逐脚本调用(expand_ir.mjs + lint_card.mjs + render_card.mjs + render_diff.mjs),
6
+ * 消除 3 次 Node 进程冷启 + 1 次 Chrome 冷启,单轮从 ~7.7s → ~1.5s。
7
+ *
8
+ * 用法(单次):
9
+ * node iterate.mjs --ir <path.json> --out <cardDir> --design <design.png> [--width 343]
10
+ *
11
+ * 用法(带 IR 微调):
12
+ * node iterate.mjs --ir <path.json> --out <cardDir> --design <design.png> \
13
+ * --set 'children.0.children.2.children.1.s.margin.marginTop="7"'
14
+ * --set 可重复;点路径(数字段=数组下标)寻址,原子写回 IR(tmp+rename),
15
+ * 中间路径不存在则响亮报错且不写文件。
16
+ *
17
+ * 用法(keep-alive,迭代间复用 Chrome):
18
+ * node iterate.mjs --keep-alive
19
+ * stdin 逐行输入 JSON:
20
+ * {"ir":"/tmp/card.ir.json","out":"src/cards/myCard/","design":"UI/.../card-1.png","width":343}
21
+ * 每行输出一行 JSON 结果。Ctrl-D 结束。
22
+ *
23
+ * 输出(stdout JSON):
24
+ * {
25
+ * "ok": true,
26
+ * "cardId": "myCard",
27
+ * "validate": { "errors": 0, "warnings": 2 },
28
+ * "lint": { "errors": 0, "warnings": 0, "infos": 1 },
29
+ * "render": { "ms": 486, "width": 686, "height": 1234 },
30
+ * "pixelDiff": { "match": 89.89, "diffRatio": 0.1011, "totalPixels": 844188, "diffPixels": 85432 },
31
+ * "diffImage": ".card-diffs/myCard_diff.png"
32
+ * }
33
+ */
34
+ import fs from 'node:fs'
35
+ import path from 'node:path'
36
+ import readline from 'node:readline'
37
+ import crypto from 'node:crypto'
38
+ import { spawnSync } from 'node:child_process'
39
+
40
+ import { expandIR, runAutofixAndValidate, syncReferencedSlices } from './expand_ir.mjs'
41
+ import { rewriteSliceRefs } from './lib/slice_dedupe.mjs'
42
+ import { lint } from './lint_card.mjs'
43
+ import { createRenderSession, renderCardScreenshot } from './render_card.mjs'
44
+ import { copyCardDesignImage } from './lib/design_image.mjs'
45
+ import { cardsDir, cardSchemaPath, cardDataPath, readJson, repoRoot } from './lib/paths.mjs'
46
+
47
+ const { PNG } = await import('pngjs')
48
+
49
+ const DEFAULT_DIFF_DIR = path.join(repoRoot, '.card-diffs')
50
+ const DEFAULT_WIDTH = 375
51
+ /** 报告落盘目录:门禁(--gate)只认这里的机器产物,不认模型自述 */
52
+ const SCRATCH_DIR = path.join(repoRoot, '.cache', 'ai-scratch')
53
+ const PREFLIGHT_SCRIPT = path.join(repoRoot, 'skills', 'dsl-card-generator', 'scripts', 'preflight.mjs')
54
+ /** 收敛门禁阈值(协议 S5/S6 的机器化版本,改这里即改门禁) */
55
+ const GATE = { match: 95, heightDelta: 0, widthDelta: 0, contentOffset1x: 2 }
56
+
57
+ /* ── IR 原子编辑(--set)────────────────────────────────────
58
+ * 点路径寻址,数字段按数组下标:children.0.s.margin.marginTop="7"
59
+ * 中间节点必须已存在(防拼写错误静默建错分支),末段可新建。
60
+ * 值先按 JSON.parse 解析(数字/布尔/对象),失败则去引号按字符串。
61
+ * 写回采用 tmp+rename 原子替换,崩溃不留半状态。 */
62
+ function setByPath(root, dottedPath, rawValue) {
63
+ const segs = String(dottedPath).split('.').filter(Boolean)
64
+ if (!segs.length) throw new Error(`空路径: "${dottedPath}"`)
65
+ let node = root
66
+ for (let i = 0; i < segs.length - 1; i++) {
67
+ const k = segs[i]
68
+ const next = Array.isArray(node) ? node[Number(k)] : node[k]
69
+ if (next === undefined || next === null || typeof next !== 'object') {
70
+ throw new Error(`路径中段不存在或不是对象: ${segs.slice(0, i + 1).join('.')}(完整路径 ${dottedPath})`)
71
+ }
72
+ node = next
73
+ }
74
+ let value
75
+ try {
76
+ value = JSON.parse(rawValue)
77
+ } catch {
78
+ value = String(rawValue).replace(/^["']|["']$/g, '')
79
+ }
80
+ const lastKey = segs[segs.length - 1]
81
+ if (Array.isArray(node)) node[Number(lastKey)] = value
82
+ else node[lastKey] = value
83
+ return value
84
+ }
85
+
86
+ function applySetsAtomic(irPath, sets) {
87
+ const ir = JSON.parse(fs.readFileSync(irPath, 'utf8'))
88
+ const applied = []
89
+ for (const expr of sets) {
90
+ const eq = expr.indexOf('=')
91
+ if (eq <= 0) throw new Error(`--set 表达式须为 path=value 形式: "${expr}"`)
92
+ const p = expr.slice(0, eq)
93
+ const v = expr.slice(eq + 1)
94
+ applied.push({ path: p, value: setByPath(ir, p, v) })
95
+ }
96
+ const tmp = `${irPath}.tmp-${process.pid}`
97
+ fs.writeFileSync(tmp, JSON.stringify(ir, null, 2) + '\n')
98
+ fs.renameSync(tmp, irPath)
99
+ return { ir, applied }
100
+ }
101
+
102
+ /** 墨迹包围盒:跳过透明与近白底,返回内容的外接矩形(设备像素)。
103
+ * threshold 取 48(=与纯白的 RGB 距离和):浅色背景带(如 #F7F4F1,距离 33)算底不算内容,
104
+ * 否则「设计有浅色渐变带、渲染没有」会被误报成整体平移。
105
+ * 用途 = 判断「内容整体平移」:match 卡住时,若渲染与设计的墨迹起点差了几个像素,
106
+ * 问题在水平/垂直对齐(Root padding、首层 margin),继续逐行调间距纯属浪费轮次。
107
+ * 实测事故:模型把这类偏移误判成「渲染器顶部蓝条 = 测量噪声」并据此收工。 */
108
+ function inkBounds(png, { threshold = 48 } = {}) {
109
+ let x0 = png.width
110
+ let y0 = png.height
111
+ let x1 = -1
112
+ let y1 = -1
113
+ for (let y = 0; y < png.height; y++) {
114
+ for (let x = 0; x < png.width; x++) {
115
+ const i = (y * png.width + x) * 4
116
+ if (png.data[i + 3] < 8) continue
117
+ const near = Math.abs(255 - png.data[i]) + Math.abs(255 - png.data[i + 1]) + Math.abs(255 - png.data[i + 2])
118
+ if (near <= threshold) continue
119
+ if (x < x0) x0 = x
120
+ if (x > x1) x1 = x
121
+ if (y < y0) y0 = y
122
+ if (y > y1) y1 = y
123
+ }
124
+ }
125
+ if (x1 < 0) return null
126
+ return { x0, y0, x1, y1, w: x1 - x0 + 1, h: y1 - y0 + 1 }
127
+ }
128
+
129
+ /* ── 像素对比 ──────────────────────────────────────────────── */
130
+ function pixelDiff(renderBuf, designBuf) {
131
+ const rPng = PNG.sync.read(renderBuf)
132
+ const dPng = PNG.sync.read(designBuf)
133
+ // 并集画布对比:只比重叠区会让「删内容 / 高度塌缩」反而提分
134
+ // (历史事故:match 89.96 但渲染高度比设计少 204px)。超出任一侧的采样点一律计为差异,
135
+ // 尺寸偏差因此直接体现在 match 上,删元素不可能再刷分。
136
+ const w = Math.max(rPng.width, dPng.width)
137
+ const h = Math.max(rPng.height, dPng.height)
138
+ const idxOf = (png, i, j) => (i < png.height && j < png.width ? (i * png.width + j) * 4 : -1)
139
+ const isDiff = (i, j) => {
140
+ const ri = idxOf(rPng, i, j)
141
+ const di = idxOf(dPng, i, j)
142
+ if (ri < 0 || di < 0) return true
143
+ const dr = Math.abs(rPng.data[ri] - dPng.data[di])
144
+ const dg = Math.abs(rPng.data[ri + 1] - dPng.data[di + 1])
145
+ const db = Math.abs(rPng.data[ri + 2] - dPng.data[di + 2])
146
+ return Math.max(dr, dg, db) > 30
147
+ }
148
+ const count = (step) => {
149
+ let diff = 0
150
+ let sampled = 0
151
+ for (let i = 0; i < h; i += step) {
152
+ for (let j = 0; j < w; j += step) {
153
+ if (isDiff(i, j)) diff++
154
+ sampled++
155
+ }
156
+ }
157
+ return { diff, sampled }
158
+ }
159
+ // 降采样 step=2 只采样 1/4 像素;接近阈值(±3%)时回退全量保证精度
160
+ const s = count(2)
161
+ const matchApprox = 100 - (s.diff / s.sampled) * 100
162
+ const totalPixels = w * h
163
+ const actualDiffPixels = matchApprox > 82 && matchApprox < 88 ? count(1).diff : Math.round((s.diff / s.sampled) * totalPixels)
164
+ // 内容偏移:渲染墨迹盒相对设计墨迹盒的位移(设备像素,报告里再折算 1x)
165
+ const rBox = inkBounds(rPng)
166
+ const dBox = inkBounds(dPng)
167
+ const contentOffset = rBox && dBox ? { dx: rBox.x0 - dBox.x0, dy: rBox.y0 - dBox.y0 } : null
168
+ return {
169
+ match: Number((100 - (actualDiffPixels / totalPixels) * 100).toFixed(2)),
170
+ diffRatio: Number(((actualDiffPixels / totalPixels) * 100).toFixed(2)),
171
+ totalPixels,
172
+ diffPixels: actualDiffPixels,
173
+ size: {
174
+ render: [rPng.width, rPng.height],
175
+ design: [dPng.width, dPng.height],
176
+ widthDelta: rPng.width - dPng.width,
177
+ heightDelta: rPng.height - dPng.height,
178
+ },
179
+ contentOffset,
180
+ inkBox: rBox && dBox ? { render: rBox, design: dBox } : null,
181
+ }
182
+ }
183
+
184
+ /* ── diff 热区摘要(AI 免读整张对比图)────────────────────────
185
+ * 把渲染/设计重叠区划分为 cols×rows 网格,逐格算 diff%,
186
+ * 返回超过 floorPct 的热格(按差异降序,坐标换算为 1x 设计稿坐标)。
187
+ * AI 据此直接定位差异区域(如 "热区 [16,288,96,142] diff 18%"),
188
+ * 无需每轮 view_image 整张 1080×726 并排对比图。 */
189
+ function diffHotspots(renderPng, designPng, { cols = 4, rows = 8, floorPct = 3, maxCells = 8, scale = 2 } = {}) {
190
+ const w = Math.min(renderPng.width, designPng.width)
191
+ const h = Math.min(renderPng.height, designPng.height)
192
+ const cellW = Math.max(1, Math.floor(w / cols))
193
+ const cellH = Math.max(1, Math.floor(h / rows))
194
+ const step = 2
195
+ const diff = new Float64Array(cols * rows)
196
+ const seen = new Float64Array(cols * rows)
197
+ for (let y = 0; y < h; y += step) {
198
+ const cellRow = Math.min(rows - 1, Math.floor(y / cellH))
199
+ for (let x = 0; x < w; x += step) {
200
+ const cellCol = Math.min(cols - 1, Math.floor(x / cellW))
201
+ const ci = cellRow * cols + cellCol
202
+ const idx = (y * w + x) * 4
203
+ const dr = Math.abs(renderPng.data[idx] - designPng.data[idx])
204
+ const dg = Math.abs(renderPng.data[idx + 1] - designPng.data[idx + 1])
205
+ const db = Math.abs(renderPng.data[idx + 2] - designPng.data[idx + 2])
206
+ if (Math.max(dr, dg, db) > 30) diff[ci]++
207
+ seen[ci]++
208
+ }
209
+ }
210
+ const cells = []
211
+ for (let r = 0; r < rows; r++) {
212
+ for (let c = 0; c < cols; c++) {
213
+ const ci = r * cols + c
214
+ if (!seen[ci]) continue
215
+ const pct = (diff[ci] / seen[ci]) * 100
216
+ if (pct < floorPct) continue
217
+ cells.push({
218
+ x: Math.round((c * cellW) / scale),
219
+ y: Math.round((r * cellH) / scale),
220
+ w: Math.round(cellW / scale),
221
+ h: Math.round(cellH / scale),
222
+ diffPct: Number(pct.toFixed(1)),
223
+ })
224
+ }
225
+ }
226
+ cells.sort((a, b) => b.diffPct - a.diffPct)
227
+ return cells.slice(0, maxCells)
228
+ }
229
+
230
+ /* ── 合成并排对比图(内存,可选写盘) ──────────────────────── */
231
+ function composeSideBySide(designBuf, renderBuf) {
232
+ const designPng = PNG.sync.read(designBuf)
233
+ const renderPng = PNG.sync.read(renderBuf)
234
+ const targetH = Math.min(designPng.height, renderPng.height, 700)
235
+ const ds = targetH / designPng.height
236
+ const rs = targetH / renderPng.height
237
+ const dW = Math.round(designPng.width * ds)
238
+ const rW = Math.round(renderPng.width * rs)
239
+ const HEADER_H = 24
240
+ const GAP = 4
241
+ const PAD = 1
242
+ const totalW = PAD + dW + GAP + rW + PAD
243
+ const totalH = PAD + HEADER_H + targetH + PAD
244
+ const canvas = new PNG({ width: totalW, height: totalH })
245
+ for (let i = 0; i < canvas.data.length; i += 4) {
246
+ canvas.data[i] = 255
247
+ canvas.data[i + 1] = 255
248
+ canvas.data[i + 2] = 255
249
+ canvas.data[i + 3] = 255
250
+ }
251
+ function setPx(x, y, rgba) {
252
+ if (x < 0 || x >= totalW || y < 0 || y >= totalH) return
253
+ const idx = (totalW * y + x) * 4
254
+ canvas.data[idx] = rgba[0]
255
+ canvas.data[idx + 1] = rgba[1]
256
+ canvas.data[idx + 2] = rgba[2]
257
+ canvas.data[idx + 3] = 255
258
+ }
259
+ // headers
260
+ for (let dy = 0; dy < HEADER_H; dy++)
261
+ for (let dx = 0; dx < dW; dx++) setPx(PAD + dx, PAD + dy, [102, 102, 102])
262
+ for (let dy = 0; dy < HEADER_H; dy++)
263
+ for (let dx = 0; dx < rW; dx++) setPx(PAD + dW + GAP + dx, PAD + dy, [41, 98, 255])
264
+ // blit
265
+ function blit(src, dstX, dstY, dstW, dstH) {
266
+ const sw = src.width, sh = src.height
267
+ for (let y = 0; y < dstH; y++) {
268
+ const sy = Math.min(sh - 1, Math.floor(y / (dstH / sh)))
269
+ for (let x = 0; x < dstW; x++) {
270
+ const sx = Math.min(sw - 1, Math.floor(x / (dstW / sw)))
271
+ const sIdx = (sw * sy + sx) * 4
272
+ setPx(dstX + x, dstY + y, [src.data[sIdx], src.data[sIdx + 1], src.data[sIdx + 2], src.data[sIdx + 3]])
273
+ }
274
+ }
275
+ }
276
+ blit(designPng, PAD, PAD + HEADER_H, dW, Math.round(targetH))
277
+ blit(renderPng, PAD + dW + GAP, PAD + HEADER_H, rW, Math.round(targetH))
278
+ return { buffer: PNG.sync.write(canvas), width: totalW, height: totalH }
279
+ }
280
+
281
+ /* ── 机器门禁基础设施 ────────────────────────────────────────
282
+ * 历史事故(all-components-test / 07 画板):模型在 match=93.11、heightDelta=-6 的情况下
283
+ * 正文宣称「门禁全绿」,还编了条「渲染图顶部蓝条是迭代器标注、属测量噪声」的假因果
284
+ * (蓝条只是 composeSideBySide 的标题栏,pixelDiff 根本不读合成图)。
285
+ * 结论:门禁不能由模型自述,必须由脚本落盘 + 机器判定,正文只能引用判定结果。 */
286
+ function hashFile(p) {
287
+ try {
288
+ return crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex').slice(0, 16)
289
+ } catch {
290
+ return null
291
+ }
292
+ }
293
+
294
+ /** preflight.mjs = quickref「Pre-flight Checklist」12 条里可机械化的 10 条。
295
+ * 此前它没被任何协议阶段点名,模型从来不知道它存在 —— 这里直接并进每轮迭代报告。 */
296
+ function runPreflight({ cardId, specPath }) {
297
+ if (!fs.existsSync(PREFLIGHT_SCRIPT)) return null
298
+ const args = [PREFLIGHT_SCRIPT, '--id', cardId, '--json']
299
+ if (specPath) args.push('--spec', specPath)
300
+ try {
301
+ const r = spawnSync(process.execPath, args, { cwd: repoRoot, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 })
302
+ const text = String(r.stdout || '')
303
+ const j = JSON.parse(text.slice(text.indexOf('{')))
304
+ const errors = Array.isArray(j.errors) ? j.errors : []
305
+ const warnings = Array.isArray(j.warnings) ? j.warnings : []
306
+ return {
307
+ errors: errors.length,
308
+ warnings: warnings.length,
309
+ details: errors.slice(0, 6).map((e) => `${e.code}: ${e.message}`),
310
+ warningDetails: warnings.slice(0, 4).map((e) => `${e.code}: ${e.message}`),
311
+ }
312
+ } catch {
313
+ return null
314
+ }
315
+ }
316
+
317
+ /** 门禁判定:全部数值项来自本轮报告,任何一项不达标 passed=false。 */
318
+ function evaluateGate(r) {
319
+ const pd = r.pixelDiff || {}
320
+ const co = r.contentOffset1x || null
321
+ const checks = [
322
+ { name: 'validate.errors', value: r.validate?.errors ?? null, required: '=0', ok: (r.validate?.errors ?? 1) === 0 },
323
+ { name: 'lint.errors', value: r.lint?.errors ?? null, required: '=0', ok: (r.lint?.errors ?? 1) === 0 },
324
+ ]
325
+ if (r.preflight) checks.push({ name: 'preflight.errors', value: r.preflight.errors, required: '=0', ok: r.preflight.errors === 0 })
326
+ if (pd.match != null) checks.push({ name: 'pixelDiff.match', value: pd.match, required: `>=${GATE.match}`, ok: pd.match >= GATE.match })
327
+ if (pd.size) {
328
+ checks.push({ name: 'pixelDiff.size.heightDelta', value: pd.size.heightDelta, required: `=${GATE.heightDelta}`, ok: pd.size.heightDelta === GATE.heightDelta })
329
+ checks.push({ name: 'pixelDiff.size.widthDelta', value: pd.size.widthDelta, required: `=${GATE.widthDelta}`, ok: pd.size.widthDelta === GATE.widthDelta })
330
+ }
331
+ if (co) checks.push({ name: 'contentOffset1x', value: `${co.dx}/${co.dy}`, required: `|dx|,|dy|<=${GATE.contentOffset1x}`, ok: Math.abs(co.dx) <= GATE.contentOffset1x && Math.abs(co.dy) <= GATE.contentOffset1x })
332
+ const failed = checks.filter((c) => !c.ok)
333
+ return {
334
+ passed: failed.length === 0,
335
+ checks,
336
+ failed: failed.map((c) => `${c.name}=${c.value}(要求 ${c.required})`),
337
+ verdict: failed.length === 0 ? 'PASS' : `FAIL:${failed.map((c) => `${c.name}=${c.value}`).join(',')}`,
338
+ }
339
+ }
340
+
341
+ /** 报告落盘 + 轮次历史 + 停滞检测(同热区连调多轮 = 微调已无收益,必须换证据链)。 */
342
+ function persistReport(result, { sp, dp }) {
343
+ try {
344
+ fs.mkdirSync(SCRATCH_DIR, { recursive: true })
345
+ const reportPath = path.join(SCRATCH_DIR, `${result.cardId}.iterate.json`)
346
+ let prev = null
347
+ try {
348
+ prev = JSON.parse(fs.readFileSync(reportPath, 'utf8'))
349
+ } catch {}
350
+ const pd = result.pixelDiff || {}
351
+ const hotSig = (pd.hotspots || []).slice(0, 3).map((h) => `${h.x},${h.y}`).join('|')
352
+ const history = Array.isArray(prev?.history) ? prev.history.slice(-9) : []
353
+ const last = history[history.length - 1]
354
+ let stalledRounds = 1
355
+ if (last && pd.match != null && last.match != null) {
356
+ const sameScore = Math.abs(last.match - pd.match) < 0.3
357
+ const sameHot = hotSig && last.hotSig === hotSig
358
+ if (sameScore && sameHot) stalledRounds = (prev?.stalled?.rounds || 1) + 1
359
+ }
360
+ history.push({
361
+ ts: Date.now(),
362
+ match: pd.match ?? null,
363
+ heightDelta: pd.size?.heightDelta ?? null,
364
+ contentOffset1x: result.contentOffset1x || null,
365
+ lintErrors: result.lint?.errors ?? null,
366
+ validateErrors: result.validate?.errors ?? null,
367
+ hotSig,
368
+ sets: (result.appliedSets || []).map((a) => a.path),
369
+ })
370
+ const persisted = {
371
+ ...result,
372
+ elements: undefined,
373
+ schemaHash: hashFile(sp),
374
+ dataHash: hashFile(dp),
375
+ history,
376
+ stalled: stalledRounds >= 2 ? { rounds: stalledRounds } : undefined,
377
+ }
378
+ persisted.gate = evaluateGate(persisted)
379
+ fs.writeFileSync(reportPath, JSON.stringify(persisted, null, 2) + '\n')
380
+ return { reportPath, stalled: persisted.stalled, gate: persisted.gate, rounds: history.length }
381
+ } catch {
382
+ return null
383
+ }
384
+ }
385
+
386
+ /* ── 核心管线 ──────────────────────────────────────────────── */
387
+ async function runPipeline({ ir: irPath, out: outDir, design: designPath, width, session, alwaysDiffImage, withElements, sets, specPath, skipPreflight, slicesDir: slicesDirArg }) {
388
+ const t0 = performance.now()
389
+
390
+ // 1. Read IR(可选 --set 编辑,原子写回)→ expand
391
+ let ir
392
+ let appliedSets = null
393
+ if (sets && sets.length) {
394
+ const r = applySetsAtomic(irPath, sets)
395
+ ir = r.ir
396
+ appliedSets = r.applied
397
+ } else {
398
+ ir = JSON.parse(fs.readFileSync(irPath, 'utf8'))
399
+ }
400
+ const { schema, data } = expandIR(ir)
401
+
402
+ // 2. Write schema + data
403
+ fs.mkdirSync(outDir, { recursive: true })
404
+ const sp = path.join(outDir, 'schema.json')
405
+ const dp = path.join(outDir, 'data.json')
406
+ fs.writeFileSync(sp, JSON.stringify(schema, null, 2) + '\n')
407
+ fs.writeFileSync(dp, JSON.stringify(data, null, 2) + '\n')
408
+
409
+ // 1b. 切图汇聚 + 同内容去重(与 expand_ir CLI 同源,slices_convention.md §3.1):
410
+ // src/slices 已有同内容文件 → 不重复导入,引用改写到既有语义名并回写 schema/data。
411
+ const sliceSync = syncReferencedSlices(schema, irPath, slicesDirArg)
412
+ if (sliceSync.rewrites.length) {
413
+ rewriteSliceRefs(data, sliceSync.rewrites)
414
+ fs.writeFileSync(sp, JSON.stringify(schema, null, 2) + '\n')
415
+ fs.writeFileSync(dp, JSON.stringify(data, null, 2) + '\n')
416
+ }
417
+
418
+ const cardId = ir.id || path.basename(path.dirname(outDir))
419
+
420
+ // 归档设计稿:card-*.png → <outDir>/<cardId>.png(复制而非移动,保留 UI 原件)
421
+ const designImage = copyCardDesignImage({ designPath, outDir, cardId })
422
+
423
+ // 3. Lint (in-process)
424
+ const lintFindings = lint(schema, data)
425
+ const lintErrors = lintFindings.filter((f) => f.severity === 'error')
426
+ const lintWarns = lintFindings.filter((f) => f.severity === 'warn')
427
+ const lintInfos = lintFindings.filter((f) => f.severity === 'info')
428
+
429
+ // 3.5 真校验门禁:autofix(机械修复)+ validate_schema.py。
430
+ // 历史事故:这里曾硬编码 validate:{errors:0},模型信任它把 flexEnd 之类的硬错误 ship 了出去。
431
+ let validateReport = { errors: 0, warnings: 0, details: [] }
432
+ try {
433
+ const v = runAutofixAndValidate(sp)
434
+ const errs = Array.isArray(v.errors) ? v.errors : []
435
+ const warns = Array.isArray(v.warnings) ? v.warnings : []
436
+ validateReport = { ok: !!v.ok, errors: errs.length, warnings: warns.length, details: errs.slice(0, 5) }
437
+ } catch (e) {
438
+ validateReport = { ok: false, errors: 1, warnings: 0, details: [`validate 执行异常: ${e.message || e}`] }
439
+ }
440
+
441
+ // 4. Render (reuse session if provided, else cold start)
442
+ const renderWidth = width || Number(schema.view?.width) || DEFAULT_WIDTH
443
+ let renderRes
444
+ if (session) {
445
+ renderRes = await session.render({ id: cardId, width: renderWidth, format: 'png', hotReload: true, captureElements: !!withElements })
446
+ } else {
447
+ renderRes = await renderCardScreenshot({ id: cardId, width: renderWidth, format: 'png', captureElements: !!withElements })
448
+ }
449
+
450
+ if (renderRes.error) {
451
+ return {
452
+ ok: false,
453
+ cardId,
454
+ error: renderRes.error,
455
+ message: renderRes.message || 'render failed',
456
+ validate: validateReport,
457
+ lint: { errors: lintErrors.length, warnings: lintWarns.length, infos: lintInfos.length },
458
+ }
459
+ }
460
+
461
+ const renderBuf = Buffer.from(renderRes.base64, 'base64')
462
+
463
+ // 5. Pixel diff vs design (if provided) — 仅计算 match%,对比图每轮都重写(历史事故:
464
+ // 旧对比图只在 match<90 时更新,模型/用户读到的是上一轮的坏产物,误判收敛状态)
465
+ let pixelDiffResult = null
466
+ let diffImagePath = null
467
+ let renderImagePath = null
468
+ if (designPath && fs.existsSync(designPath)) {
469
+ const designBuf = fs.readFileSync(designPath)
470
+ pixelDiffResult = pixelDiff(renderBuf, designBuf)
471
+ // diff 热区摘要:AI 直接读坐标定位差异,不必每轮查看整张并排对比图
472
+ try {
473
+ const renderPng = PNG.sync.read(renderBuf)
474
+ const designPng = PNG.sync.read(designBuf)
475
+ const scale = renderWidth ? renderPng.width / renderWidth : 2
476
+ pixelDiffResult.hotspots = diffHotspots(renderPng, designPng, { scale })
477
+ } catch {}
478
+ if (true) {
479
+ const composed = composeSideBySide(designBuf, renderBuf)
480
+ diffImagePath = path.join(DEFAULT_DIFF_DIR, `${cardId}_diff.png`)
481
+ fs.mkdirSync(DEFAULT_DIFF_DIR, { recursive: true })
482
+ fs.writeFileSync(diffImagePath, composed.buffer)
483
+ }
484
+ }
485
+
486
+ // 渲染原图(无标题栏、无并排合成)落到 scratch,供像素取证与 diff_bands --render 复用
487
+ try {
488
+ fs.mkdirSync(SCRATCH_DIR, { recursive: true })
489
+ renderImagePath = path.join(SCRATCH_DIR, `${cardId}.render.png`)
490
+ fs.writeFileSync(renderImagePath, renderBuf)
491
+ } catch {
492
+ renderImagePath = null
493
+ }
494
+
495
+ const elapsed = Math.round(performance.now() - t0)
496
+
497
+ // 5.5 preflight(quickref Pre-flight Checklist 的机械化 10 条)并入本轮报告
498
+ const preflight = skipPreflight ? null : runPreflight({ cardId, specPath })
499
+
500
+ const result = {
501
+ ok: true,
502
+ cardId,
503
+ appliedSets: appliedSets || undefined,
504
+ validate: validateReport,
505
+ lint: {
506
+ errors: lintErrors.length,
507
+ warnings: lintWarns.length,
508
+ infos: lintInfos.length,
509
+ details: lintErrors.length ? lintErrors.slice(0, 5) : [],
510
+ // 预警也要回喂:以前只给 errors,warnings 只有计数没有内容,
511
+ // 模型永远看不到「快出事」的信号(例如装饰条宽度未绑标题长度)。
512
+ warningDetails: lintWarns.length ? lintWarns.slice(0, 5).map((f) => `${f.code}: ${f.message}`) : [],
513
+ },
514
+ render: { ms: renderRes.ms, width: renderRes.width, height: renderRes.height },
515
+ pixelDiff: pixelDiffResult,
516
+ diffImage: diffImagePath,
517
+ // 渲染原图落盘并回报路径:模型要做像素取证时有确定入口,
518
+ // 不必再把卡片目录下的 <cardId>.png(那是设计稿归档)误当渲染产物。
519
+ renderImage: renderImagePath,
520
+ // 并排图的图例说明:顶部 24px 灰/蓝色条是工具标题栏,不属于卡片内容。
521
+ // 缺这句时模型会把蓝条当成「渲染器标注/测量噪声」,据此放弃收敛。
522
+ diffImageNote: diffImagePath
523
+ ? '并排对比图:左=设计稿(灰标题栏) 右=渲染(蓝标题栏);顶部 24px 色条是工具标题栏,不是卡片内容,也不参与 pixelDiff 计算(match 用渲染原图 vs 设计稿原图算)。要看渲染本体用 renderImage。'
524
+ : undefined,
525
+ designImage,
526
+ preflight: preflight || undefined,
527
+ ...(withElements ? { elements: renderRes.elementBoxes || [] } : {}),
528
+ elapsedMs: elapsed,
529
+ }
530
+
531
+ // 内容偏移折算到 1x 设计像素 + 对齐提示
532
+ if (pixelDiffResult?.contentOffset) {
533
+ const scale = renderWidth ? (renderRes.width || renderWidth) / renderWidth : 2
534
+ const co = pixelDiffResult.contentOffset
535
+ result.contentOffset1x = {
536
+ dx: Math.round(co.dx / (scale || 1)),
537
+ dy: Math.round(co.dy / (scale || 1)),
538
+ }
539
+ const { dx, dy } = result.contentOffset1x
540
+ if (Math.abs(dx) > GATE.contentOffset1x || Math.abs(dy) > GATE.contentOffset1x) {
541
+ result.contentOffsetHint = `内容整体偏移 dx=${dx} dy=${dy}(1x 设计像素,渲染相对设计)——这是对齐问题不是间距微调问题:先查 Root/首层容器的左右与上下 padding、首个子节点的 margin、以及是否漏了 marginType/paddingType:"1";继续逐行调 gap/padding 不会收敛。`
542
+ }
543
+ }
544
+
545
+ // 报告落盘 + 门禁判定 + 停滞检测
546
+ const saved = persistReport(result, { sp, dp })
547
+ if (saved) {
548
+ result.report = saved.reportPath
549
+ result.rounds = saved.rounds
550
+ result.gate = saved.gate
551
+ if (saved.stalled) {
552
+ result.stalled = {
553
+ rounds: saved.stalled.rounds,
554
+ hint: `连续 ${saved.stalled.rounds} 轮 match 与热区几乎没变:--set 微调已无收益,禁止继续盲调。改按证据推进——① 跑 diff_bands.mjs(args=["--id","${cardId}","--design","${designPath || '<设计稿>'}","--render","${renderImagePath || '<renderImage>'}"])拿逐带 dy/dh、内容底边、分区主色;② 对照 references/generation_pitfalls.md 的「类型开关+值」「静默丢弃」清单排查样式被忽略(marginType/paddingType/backgroundType/isBorder/isShadow);③ 读报告里的 contentOffset1x 与 hotspots 判断是整体平移还是局部差异;④ 有 --spec 时看 preflight.details。`,
555
+ }
556
+ }
557
+ }
558
+
559
+ return result
560
+ }
561
+
562
+ /* ── CLI ───────────────────────────────────────────────────── */
563
+ function parseArgs(argv) {
564
+ const out = { ir: '', out: '', design: '', width: 0, keepAlive: false, diffImage: false, elements: false, sets: [], spec: '', gate: false, id: '', skipPreflight: false, slicesDir: '' }
565
+ for (let i = 0; i < argv.length; i++) {
566
+ const a = argv[i]
567
+ if (a === '--ir') out.ir = argv[++i] || ''
568
+ else if (a === '--out') out.out = argv[++i] || ''
569
+ else if (a === '--design') out.design = argv[++i] || ''
570
+ else if (a === '--width') out.width = Number(argv[++i]) || 0
571
+ else if (a === '--set') out.sets.push(argv[++i] || '')
572
+ else if (a === '--spec') out.spec = argv[++i] || ''
573
+ else if (a === '--slices-dir') out.slicesDir = argv[++i] || ''
574
+ else if (a === '--id') out.id = argv[++i] || ''
575
+ else if (a === '--gate') out.gate = true
576
+ else if (a === '--no-preflight') out.skipPreflight = true
577
+ else if (a === '--keep-alive') out.keepAlive = true
578
+ else if (a === '--diff-image') out.diffImage = true
579
+ else if (a === '--elements') out.elements = true
580
+ else if (a === '-h' || a === '--help') {
581
+ console.log(`Usage: node iterate.mjs --ir <IR.json> --out <cardDir> [--design <design.png>] [--width 343] [--elements]
582
+ [--spec <spec.json>](跑 preflight 元素清单比对) [--no-preflight] [--slices-dir <要素包 slices 目录>]
583
+ [--set 'children.0.s.margin.marginTop="7"'](可重复;点路径数字段=数组下标;原子写回 IR)
584
+ node iterate.mjs --gate --out <cardDir>(或 --id <cardId>):读最近一轮落盘报告做机器门禁判定,未达标 exit 1
585
+ node iterate.mjs --keep-alive (stdin JSON lines → stdout JSON lines)
586
+
587
+ 门禁(--gate / 报告里的 gate 字段):validate.errors=0 ∧ lint.errors=0 ∧ preflight.errors=0 ∧ match>=${GATE.match}
588
+ ∧ heightDelta=${GATE.heightDelta} ∧ widthDelta=${GATE.widthDelta} ∧ |contentOffset1x|<=${GATE.contentOffset1x},且 schema 未在报告之后被改过。
589
+ 报告落盘:.cache/ai-scratch/<cardId>.iterate.json(含 gate / history / stalled)。`)
590
+ process.exit(0)
591
+ }
592
+ }
593
+ return out
594
+ }
595
+
596
+ /** --gate:只读最近一轮落盘报告做判定,不重渲染。
597
+ * schema 在报告之后被改过 ⇒ 报告失效,直接判 FAIL(防止拿旧报告宣称门禁全绿)。 */
598
+ function runGateMode({ id, out: outDir }) {
599
+ let cardId = id
600
+ if (!cardId && outDir) cardId = path.basename(String(outDir).replace(/\/+$/, ''))
601
+ if (!cardId) {
602
+ console.log(JSON.stringify({ ok: false, gate: { passed: false, verdict: 'FAIL:--gate 需要 --id <cardId> 或 --out <cardDir>' } }))
603
+ process.exit(1)
604
+ }
605
+ const reportPath = path.join(SCRATCH_DIR, `${cardId}.iterate.json`)
606
+ if (!fs.existsSync(reportPath)) {
607
+ console.log(JSON.stringify({
608
+ ok: false,
609
+ cardId,
610
+ gate: { passed: false, verdict: `FAIL:没有 ${cardId} 的迭代报告(${reportPath})—— 先跑一轮 iterate.mjs,门禁只认脚本落盘的报告,不认自述` },
611
+ }))
612
+ process.exit(1)
613
+ }
614
+ const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'))
615
+ const schemaHash = hashFile(cardSchemaPath(cardId))
616
+ const stale = schemaHash && report.schemaHash && schemaHash !== report.schemaHash
617
+ const gate = evaluateGate(report)
618
+ if (stale) {
619
+ gate.passed = false
620
+ gate.verdict = `FAIL:报告已过期(schema.json 在报告之后被改过:${report.schemaHash} → ${schemaHash})—— 重跑一轮 iterate.mjs 再判门禁`
621
+ gate.checks.push({ name: 'reportFreshness', value: 'stale', required: 'schema 未变更', ok: false })
622
+ }
623
+ const out = {
624
+ ok: gate.passed,
625
+ cardId,
626
+ report: reportPath,
627
+ gate,
628
+ summary: {
629
+ match: report.pixelDiff?.match ?? null,
630
+ heightDelta: report.pixelDiff?.size?.heightDelta ?? null,
631
+ widthDelta: report.pixelDiff?.size?.widthDelta ?? null,
632
+ contentOffset1x: report.contentOffset1x || null,
633
+ validateErrors: report.validate?.errors ?? null,
634
+ lintErrors: report.lint?.errors ?? null,
635
+ preflightErrors: report.preflight?.errors ?? null,
636
+ rounds: (report.history || []).length,
637
+ reportAge: Date.now() - (report.history?.[report.history.length - 1]?.ts || Date.now()),
638
+ },
639
+ }
640
+ console.log(JSON.stringify(out, null, 2))
641
+ process.exit(gate.passed ? 0 : 1)
642
+ }
643
+
644
+ async function main() {
645
+ const args = parseArgs(process.argv.slice(2))
646
+
647
+ if (args.gate) {
648
+ runGateMode({ id: args.id, out: args.out })
649
+ return
650
+ }
651
+
652
+ if (args.keepAlive) {
653
+ const session = await createRenderSession()
654
+ const rl = readline.createInterface({ input: process.stdin, terminal: false })
655
+ try {
656
+ for await (const line of rl) {
657
+ const text = line.trim()
658
+ if (!text) continue
659
+ try {
660
+ const req = JSON.parse(text)
661
+ const result = await runPipeline({ ...req, specPath: req.spec || req.specPath, session, alwaysDiffImage: req.diffImage || req.alwaysDiffImage, withElements: req.elements })
662
+ console.log(JSON.stringify(result))
663
+ } catch (e) {
664
+ console.log(JSON.stringify({ ok: false, error: 'request-failed', message: e.message }))
665
+ }
666
+ }
667
+ } finally {
668
+ await session.close()
669
+ }
670
+ return
671
+ }
672
+
673
+ if (!args.ir || !args.out) {
674
+ console.error('用法: node iterate.mjs --ir <IR.json> --out <cardDir> [--design <design.png>] [--width 343]')
675
+ process.exit(1)
676
+ }
677
+
678
+ const result = await runPipeline({
679
+ ir: args.ir,
680
+ out: args.out,
681
+ design: args.design,
682
+ width: args.width,
683
+ alwaysDiffImage: args.diffImage,
684
+ withElements: args.elements,
685
+ sets: args.sets,
686
+ specPath: args.spec,
687
+ skipPreflight: args.skipPreflight,
688
+ slicesDir: args.slicesDir,
689
+ })
690
+
691
+ console.log(JSON.stringify(result, null, 2))
692
+ if (!result.ok) process.exit(1)
693
+ // 门禁未达标不算脚本失败(报告照常回喂),但退出码要让调用方一眼看出没收敛
694
+ if (result.gate && result.gate.passed === false) process.exitCode = 2
695
+ }
696
+
697
+ main().catch((e) => {
698
+ console.error(`iterate.mjs 未预期错误: ${e.message || e}`)
699
+ process.exit(1)
700
+ })