@xiaobuyu/nodesign 0.0.1 → 0.0.3

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 (358) hide show
  1. package/README.md +156 -106
  2. package/bin/nodesign.js +0 -0
  3. package/package.json +1 -1
  4. package/server/api/README.md +45 -45
  5. package/server/api/_guard.js +75 -75
  6. package/server/api/admin.js +187 -187
  7. package/server/api/assets/docx-page.js +103 -103
  8. package/server/api/assets/helpers.js +42 -22
  9. package/server/api/assets/notes.js +194 -144
  10. package/server/api/assets.js +891 -896
  11. package/server/api/board.js +134 -57
  12. package/server/api/browse.js +138 -138
  13. package/server/api/canvas.js +444 -444
  14. package/server/api/chatai.js +324 -324
  15. package/server/api/exports/build-standalone.js +980 -980
  16. package/server/api/exports/cards.js +118 -118
  17. package/server/api/exports/docx-pdf.js +46 -46
  18. package/server/api/exports/export-page.js +142 -142
  19. package/server/api/exports/handoff.js +147 -147
  20. package/server/api/exports.js +690 -690
  21. package/server/api/instruction.js +75 -75
  22. package/server/api/me.js +116 -116
  23. package/server/api/memory.js +185 -185
  24. package/server/api/pending-changes.js +366 -366
  25. package/server/api/plugins.js +164 -164
  26. package/server/api/projects.js +220 -220
  27. package/server/api/publish.js +64 -64
  28. package/server/api/recent.js +73 -73
  29. package/server/api/sessions.js +589 -589
  30. package/server/api/skills.js +77 -77
  31. package/server/api/standalone-fit.js +168 -168
  32. package/server/api/turn-compose.js +180 -180
  33. package/server/api/turn-inflight.js +44 -44
  34. package/server/auth/README.md +17 -17
  35. package/server/auth/middleware.js +138 -138
  36. package/server/auth/origin-guard.js +71 -71
  37. package/server/auth/session.js +108 -108
  38. package/server/auth/tier.js +113 -113
  39. package/server/auth/users-store.js +348 -348
  40. package/server/edit/README.md +29 -29
  41. package/server/engine/README.md +113 -113
  42. package/server/engine/agent/agent-shared.js +550 -550
  43. package/server/engine/agent/auto-mode-default-hard-deny.txt +4 -4
  44. package/server/engine/agent/auto-mode-rules.js +78 -78
  45. package/server/engine/agent/context.js +418 -418
  46. package/server/engine/agent/events.js +465 -464
  47. package/server/engine/agent/hooks/canvas-validate.js +199 -199
  48. package/server/engine/agent/hooks/failure.js +120 -120
  49. package/server/engine/agent/hooks/file-events.js +75 -75
  50. package/server/engine/agent/hooks/lifecycle.js +233 -233
  51. package/server/engine/agent/hooks/post-canvas-focus.js +80 -80
  52. package/server/engine/agent/hooks/post-guidance.js +206 -206
  53. package/server/engine/agent/hooks/post-subagent-report.js +104 -104
  54. package/server/engine/agent/hooks/post-trim.js +58 -58
  55. package/server/engine/agent/hooks/pre-board-neighborhood.js +34 -34
  56. package/server/engine/agent/hooks/pre-defaults.js +84 -84
  57. package/server/engine/agent/hooks/pre-injectors.js +277 -277
  58. package/server/engine/agent/hooks/pre-performance-log-guard.js +78 -78
  59. package/server/engine/agent/hooks/pre-starter-files.js +61 -61
  60. package/server/engine/agent/hooks/pre-workspace-scope-guard.js +78 -78
  61. package/server/engine/agent/hooks/site-validate.js +94 -94
  62. package/server/engine/agent/hooks/tool-prompts.js +31 -31
  63. package/server/engine/agent/hooks/turn-state-memory.js +56 -56
  64. package/server/engine/agent/hooks/user-prompt-submit.js +258 -215
  65. package/server/engine/agent/hooks.js +354 -354
  66. package/server/engine/agent/init-contract.js +69 -69
  67. package/server/engine/agent/isolation.js +159 -159
  68. package/server/engine/agent/model-table.js +292 -292
  69. package/server/engine/agent/plugin-loader.js +266 -266
  70. package/server/engine/agent/prompts/nodesign-prelude.md +388 -336
  71. package/server/engine/agent/prompts/tools/ask-user-question-protocol.md +71 -71
  72. package/server/engine/agent/prompts/tools/direct-edit-protocol.md +98 -98
  73. package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +583 -583
  74. package/server/engine/agent/prompts/tools/generate-image-cookbook.md +483 -483
  75. package/server/engine/agent/prompts/tools/hybrid-reference.md +100 -100
  76. package/server/engine/agent/prompts/tools/paint-still-cookbook.md +270 -270
  77. package/server/engine/agent/prompts/tools/roll-film-cookbook.md +70 -70
  78. package/server/engine/agent/prompts/tools/site-reference.md +106 -106
  79. package/server/engine/agent/prompts/tools/tweaks-syntax.md +125 -125
  80. package/server/engine/agent/prompts/tools/vision-checker-dispatch.md +48 -48
  81. package/server/engine/agent/session-loop.js +1035 -1024
  82. package/server/engine/agent/session-model.js +164 -164
  83. package/server/engine/agent/skill.js +204 -204
  84. package/server/engine/agent/task-events.js +78 -78
  85. package/server/engine/agents/ds-extractor.md +132 -132
  86. package/server/engine/agents/explorer.md +190 -190
  87. package/server/engine/agents/index.js +230 -230
  88. package/server/engine/agents/schemas/design-system.json +156 -156
  89. package/server/engine/agents/schemas/tweak-schema.json +113 -113
  90. package/server/engine/agents/tweak-proposer.md +127 -127
  91. package/server/engine/agents/vision-checker.md +254 -254
  92. package/server/engine/browse/capture.js +422 -422
  93. package/server/engine/browse/card.js +137 -137
  94. package/server/engine/browse/handover.js +59 -59
  95. package/server/engine/browse/page-digest.js +119 -119
  96. package/server/engine/browse/refs.js +196 -196
  97. package/server/engine/browse/registry.js +307 -307
  98. package/server/engine/browse/screencast.js +170 -170
  99. package/server/engine/browse/state.js +121 -121
  100. package/server/engine/chatai/chat-log.js +79 -79
  101. package/server/engine/chatai/index.js +193 -193
  102. package/server/engine/chatai/openai-compat.js +152 -152
  103. package/server/engine/chatai/orchestrate.js +309 -309
  104. package/server/engine/chatai/perform.js +40 -40
  105. package/server/engine/chatai/summarize.js +79 -79
  106. package/server/engine/mcp/capability-gate.js +52 -52
  107. package/server/engine/mcp/index.js +351 -339
  108. package/server/engine/mcp/param-sanitizer.js +142 -142
  109. package/server/engine/mcp/tools/arrange-on-board.js +124 -124
  110. package/server/engine/mcp/tools/artifact-session.js +278 -278
  111. package/server/engine/mcp/tools/browse-computer.js +421 -421
  112. package/server/engine/mcp/tools/browse-find-batch.js +183 -183
  113. package/server/engine/mcp/tools/browse-screenshot.js +157 -157
  114. package/server/engine/mcp/tools/browse.js +480 -480
  115. package/server/engine/mcp/tools/build-docx.js +101 -101
  116. package/server/engine/mcp/tools/clear-pending-changes.js +99 -99
  117. package/server/engine/mcp/tools/create-on-board.js +152 -155
  118. package/server/engine/mcp/tools/crystallize-skill.js +168 -168
  119. package/server/engine/mcp/tools/edit-sketch.js +167 -0
  120. package/server/engine/mcp/tools/explain-style.js +237 -237
  121. package/server/engine/mcp/tools/export-handoff.js +133 -133
  122. package/server/engine/mcp/tools/expose-tweaks.js +149 -149
  123. package/server/engine/mcp/tools/generate-image.js +842 -842
  124. package/server/engine/mcp/tools/get-computed-styles.js +164 -164
  125. package/server/engine/mcp/tools/get-pending-changes.js +205 -205
  126. package/server/engine/mcp/tools/h3box-ssh.js +84 -84
  127. package/server/engine/mcp/tools/helpers/acquire-page.js +82 -82
  128. package/server/engine/mcp/tools/helpers/motion-lab.js +554 -554
  129. package/server/engine/mcp/tools/helpers/motion-scroll.js +112 -112
  130. package/server/engine/mcp/tools/helpers/perception-page.js +215 -215
  131. package/server/engine/mcp/tools/helpers/reference-download.js +103 -103
  132. package/server/engine/mcp/tools/helpers/rembg-bridge.py +80 -80
  133. package/server/engine/mcp/tools/helpers/shot-pipeline.js +280 -280
  134. package/server/engine/mcp/tools/highlight.js +68 -68
  135. package/server/engine/mcp/tools/list-pages.js +222 -222
  136. package/server/engine/mcp/tools/look-at-board.js +124 -0
  137. package/server/engine/mcp/tools/lookup-tags.js +59 -59
  138. package/server/engine/mcp/tools/navigate-to-page.js +60 -60
  139. package/server/engine/mcp/tools/organize-board.js +64 -64
  140. package/server/engine/mcp/tools/paint-still.js +434 -434
  141. package/server/engine/mcp/tools/pin-to-board.js +130 -130
  142. package/server/engine/mcp/tools/preview-deck.js +130 -130
  143. package/server/engine/mcp/tools/profile-scroll.js +305 -305
  144. package/server/engine/mcp/tools/publish-site.js +102 -102
  145. package/server/engine/mcp/tools/query-elements.js +195 -195
  146. package/server/engine/mcp/tools/read-board.js +190 -103
  147. package/server/engine/mcp/tools/read-document.js +93 -93
  148. package/server/engine/mcp/tools/read-page.js +264 -264
  149. package/server/engine/mcp/tools/read-tavern-json.js +143 -143
  150. package/server/engine/mcp/tools/read-user-view.js +59 -0
  151. package/server/engine/mcp/tools/record-decision.js +140 -140
  152. package/server/engine/mcp/tools/relate-on-board.js +112 -110
  153. package/server/engine/mcp/tools/report-issue.js +115 -115
  154. package/server/engine/mcp/tools/roll-film.js +266 -266
  155. package/server/engine/mcp/tools/screenshot-docx.js +138 -138
  156. package/server/engine/mcp/tools/screenshot-url.js +195 -195
  157. package/server/engine/mcp/tools/screenshot.js +583 -583
  158. package/server/engine/mcp/tools/sketch-on-board.js +337 -0
  159. package/server/engine/mcp/tools/tier-gate.js +96 -96
  160. package/server/engine/mcp/tools/trace-motion.js +215 -215
  161. package/server/engine/mcp/tools/web-search.js +548 -548
  162. package/server/engine/mcp/tools/write-on-board.js +130 -0
  163. package/server/engine/motion/inventory.js +349 -349
  164. package/server/engine/perception/session.js +235 -235
  165. package/server/engine/plugins/nodesign/.claude-plugin/plugin.json +5 -5
  166. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +199 -192
  167. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/canvas.template.html +667 -667
  168. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/hybrid-grid.md +22 -22
  169. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/image-led-cover.md +25 -25
  170. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/portrait.md +23 -23
  171. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/quote-backdrop.md +21 -21
  172. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/section-divider.md +23 -23
  173. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/text-led.md +21 -21
  174. package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +169 -162
  175. package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +407 -407
  176. package/server/engine/plugins/nodesign/skills/docx-craft//346/226/207/346/241/243.template.json +80 -80
  177. package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +235 -228
  178. package/server/engine/plugins/nodesign/skills/rp-craft/patterns//346/274/224/345/207/272/351/241/265-/346/234/200/345/260/217/345/256/236/347/216/260.html +78 -78
  179. package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +268 -268
  180. package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +330 -323
  181. package/server/engine/plugins/nodesign/skills/site-craft/patterns/build-lane.md +134 -134
  182. package/server/engine/plugins/nodesign/skills/site-craft/patterns/cutout-collage.md +61 -61
  183. package/server/engine/plugins/nodesign/skills/site-craft/patterns/mock-app.md +163 -163
  184. package/server/engine/plugins/nodesign/skills/site-craft/patterns/page-transitions.md +86 -86
  185. package/server/engine/runs/active-runs.js +698 -698
  186. package/server/engine/runs/board-tasklist.js +130 -0
  187. package/server/engine/runs/live-turn.js +252 -252
  188. package/server/engine/runs/store.js +364 -364
  189. package/server/engine/runs/turn-relay.js +217 -217
  190. package/server/engine/runtime/workspace.js +164 -164
  191. package/server/index.js +3 -0
  192. package/server/lib/artifact-file-path.js +80 -80
  193. package/server/lib/artifact-target.js +284 -284
  194. package/server/lib/asset-refs.js +239 -239
  195. package/server/lib/async-queue.js +104 -104
  196. package/server/lib/auto-relations.js +130 -130
  197. package/server/lib/binding-types.js +72 -60
  198. package/server/lib/board-graph-export.js +191 -0
  199. package/server/lib/board-groups.js +167 -0
  200. package/server/lib/board-hero.js +76 -0
  201. package/server/lib/board-kind-sizes.js +85 -62
  202. package/server/lib/board-relations.js +133 -133
  203. package/server/lib/browse-proxy.js +180 -180
  204. package/server/lib/canvas-id.js +36 -36
  205. package/server/lib/chalk.js +121 -0
  206. package/server/lib/cover.js +227 -227
  207. package/server/lib/danbooru-tags.js +291 -291
  208. package/server/lib/doc-extract.js +156 -156
  209. package/server/lib/docx/build-from-source.js +260 -260
  210. package/server/lib/docx/build.js +490 -490
  211. package/server/lib/docx/dump-styles.js +287 -287
  212. package/server/lib/docx/fonts/nodesign-cjk.conf +164 -164
  213. package/server/lib/docx/merge-runs.js +129 -129
  214. package/server/lib/docx/numbering.js +218 -218
  215. package/server/lib/docx/order.js +152 -152
  216. package/server/lib/docx/rawzip.js +172 -172
  217. package/server/lib/docx/render.js +92 -92
  218. package/server/lib/docx/text-lint.js +225 -225
  219. package/server/lib/docx/tokens.js +335 -335
  220. package/server/lib/docx/units.js +29 -29
  221. package/server/lib/docx/xml.js +291 -291
  222. package/server/lib/docx-pages.js +187 -187
  223. package/server/lib/export-collect.js +289 -289
  224. package/server/lib/export-package.js +210 -210
  225. package/server/lib/html-srcset.js +145 -145
  226. package/server/lib/image-variant.js +460 -460
  227. package/server/lib/ingress/forward-openai-chat.js +305 -305
  228. package/server/lib/ingress/openai-chat.js +491 -491
  229. package/server/lib/ingress/session-notice.js +66 -66
  230. package/server/lib/ingress/session-routes.js +68 -68
  231. package/server/lib/ingress/slot-probe.js +130 -130
  232. package/server/lib/ingress/upstream-billing.js +52 -52
  233. package/server/lib/ingress/upstream-fail-streak.js +72 -72
  234. package/server/lib/ingress/upstream-truncation.js +48 -48
  235. package/server/lib/issues-store.js +182 -182
  236. package/server/lib/kinds/deck.js +72 -72
  237. package/server/lib/kinds/docx.js +283 -283
  238. package/server/lib/kinds/file-kinds.js +113 -113
  239. package/server/lib/kinds/index.js +202 -202
  240. package/server/lib/kinds/site.js +161 -161
  241. package/server/lib/model-ingress.js +543 -543
  242. package/server/lib/moderation.js +255 -255
  243. package/server/lib/notice-store.js +103 -103
  244. package/server/lib/plugin-install.js +104 -104
  245. package/server/lib/plugin-validator.js +721 -721
  246. package/server/lib/publish-store.js +115 -115
  247. package/server/lib/quick-summary.js +52 -52
  248. package/server/lib/quota.js +270 -270
  249. package/server/lib/rate-window.js +29 -29
  250. package/server/lib/reference-assets.js +92 -92
  251. package/server/lib/region-shot.js +135 -135
  252. package/server/lib/safe-path.js +73 -73
  253. package/server/lib/sdk-session.js +33 -33
  254. package/server/lib/showcase-store.js +92 -92
  255. package/server/lib/site-publish.js +465 -465
  256. package/server/lib/sketch-layout.js +277 -0
  257. package/server/lib/ssrf-guard.js +432 -432
  258. package/server/lib/task-scan.js +158 -158
  259. package/server/lib/tavern-json.js +154 -154
  260. package/server/lib/video-variant.js +237 -237
  261. package/server/lib/workspace-path.js +33 -33
  262. package/server/ops/fix-sdk-musl.mjs +40 -40
  263. package/server/ops/install-macos-fonts.sh +114 -114
  264. package/server/ops/macos-fonts.conf +336 -336
  265. package/server/ops/sandbox-shim/bwrap +57 -57
  266. package/server/projects/assets-summary.js +119 -119
  267. package/server/projects/auto-name.js +53 -53
  268. package/server/projects/board-limits.js +12 -0
  269. package/server/projects/board-sanitize.js +252 -0
  270. package/server/projects/board-store.js +507 -640
  271. package/server/projects/move-entry.js +103 -103
  272. package/server/projects/store.js +270 -270
  273. package/server/projects/ui-config.js +60 -54
  274. package/server/projects/viewpoint-store.js +78 -0
  275. package/server/projects/workspace-templates.js +53 -53
  276. package/server/projects/workspace.js +1025 -1025
  277. package/server/runtime/local-config.js +177 -177
  278. package/server/runtime/profile.js +72 -72
  279. package/server/services/rembg-service.py +334 -334
  280. package/server/shared/README.md +25 -25
  281. package/server/shared/deck.js +54 -54
  282. package/server/shared/time.js +49 -49
  283. package/server/style-pipeline/README.md +53 -53
  284. package/server/ws/broker.js +39 -36
  285. package/server/ws/browse-channel.js +177 -177
  286. package/server/ws/index.js +386 -386
  287. package/web/README.md +89 -89
  288. package/web/dist/assets/BrowserWindow-P_vIdh-G.js +11 -0
  289. package/web/dist/assets/{DeckWindow-4G0xX7P3.js → DeckWindow-BpLv5082.js} +3 -3
  290. package/web/dist/assets/{DocxWindow-BXQsoPkh.js → DocxWindow-DSAU1-tD.js} +4 -4
  291. package/web/dist/assets/MermaidBlock-DR1GikCv.js +313 -0
  292. package/web/dist/assets/SiteWindow-92uBuRRR.js +18 -0
  293. package/web/dist/assets/abnfDiagram-VCTEODGH-uA8Esix4.js +1 -0
  294. package/web/dist/assets/arc-D_khH_xN.js +1 -0
  295. package/web/dist/assets/architectureDiagram-5GKGNRK7-DydJHqOt.js +36 -0
  296. package/web/dist/assets/blockDiagram-NRAW4CY4-B7wBbKiW.js +129 -0
  297. package/web/dist/assets/c4Diagram-UCG6FXSJ-6mrH0x7Y.js +38 -0
  298. package/web/dist/assets/channel-DGkccMO1.js +1 -0
  299. package/web/dist/assets/chunk-2Q5K7J3B-Cuikho5Q.js +1 -0
  300. package/web/dist/assets/chunk-5VM5RSS4-3-KZ5AUz.js +15 -0
  301. package/web/dist/assets/chunk-F27PBJKO-Bkwq2gGU.js +1 -0
  302. package/web/dist/assets/chunk-G27WJ6UU-BLyQzdGE.js +231 -0
  303. package/web/dist/assets/chunk-JWPE2WC7-B7HxxQds.js +1 -0
  304. package/web/dist/assets/chunk-LCL6LL3I-CtXrnUwF.js +206 -0
  305. package/web/dist/assets/chunk-POPQ4Y6H-dxddZlXt.js +1 -0
  306. package/web/dist/assets/chunk-SVP7TREG-DpkmrgHn.js +88 -0
  307. package/web/dist/assets/chunk-XXDRQBXY-BwZ7RbpQ.js +1 -0
  308. package/web/dist/assets/classDiagram-DTDB5LWJ-BGscw3OD.js +1 -0
  309. package/web/dist/assets/classDiagram-v2-JRS7N3AN-BGscw3OD.js +1 -0
  310. package/web/dist/assets/cose-bilkent-JH36ORCC-DGFOsMUt.js +1 -0
  311. package/web/dist/assets/cynefin-OW5HDTMX-DUwn3ehS.js +178 -0
  312. package/web/dist/assets/cynefinDiagram-5FMLGOSQ-DlLAOZ9x.js +62 -0
  313. package/web/dist/assets/cytoscape.esm-BjAbmLdb.js +331 -0
  314. package/web/dist/assets/dagre-3AP2YEHR-CyourLfz.js +4 -0
  315. package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  316. package/web/dist/assets/diagram-S7CK7UJ4-BTMbx2np.js +30 -0
  317. package/web/dist/assets/diagram-UQ7AKVKN-UK94_1-P.js +41 -0
  318. package/web/dist/assets/diagram-VSXAHHWV-C7zS-v9v.js +3 -0
  319. package/web/dist/assets/diagram-VX7I27RA-DKJRa6CK.js +24 -0
  320. package/web/dist/assets/diagram-Z3DM3KII-kUbAfGAp.js +24 -0
  321. package/web/dist/assets/ebnfDiagram-PWID7BFC-CVW16_vb.js +1 -0
  322. package/web/dist/assets/erDiagram-SSCWMZ5O-BRQMFQ5y.js +99 -0
  323. package/web/dist/assets/flowDiagram-A5DVABFB-S34LpLtK.js +168 -0
  324. package/web/dist/assets/ganttDiagram-EL5Y4UJY-BFiqk1-G.js +292 -0
  325. package/web/dist/assets/gitGraphDiagram-WWUBYQGX-LZsrdyyv.js +106 -0
  326. package/web/dist/assets/index-GAamh1p4.js +2152 -0
  327. package/web/dist/assets/infoDiagram-RXCK75RN-0DvN6KIp.js +2 -0
  328. package/web/dist/assets/init-Gi6I4Gst.js +1 -0
  329. package/web/dist/assets/ishikawaDiagram-5VMMS53U-CaY3A-Bx.js +70 -0
  330. package/web/dist/assets/journeyDiagram-EYS64GPL-BBeiaCrj.js +139 -0
  331. package/web/dist/assets/kanban-definition-3QL26DDD-ux42DRXQ.js +89 -0
  332. package/web/dist/assets/layout-CnXiF_NB.js +1 -0
  333. package/web/dist/assets/linear-BMW0u6G6.js +1 -0
  334. package/web/dist/assets/mindmap-definition-FBJOCRG2-hUxSpdKn.js +96 -0
  335. package/web/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  336. package/web/dist/assets/pegDiagram-XKGWAZYB-aCaQPH0w.js +1 -0
  337. package/web/dist/assets/{pending-edit-apply-DTjo2geW.js → pending-edit-apply-AcdOQ9Ij.js} +6 -6
  338. package/web/dist/assets/pieDiagram-E7YTZNPT-CmOEZ8zg.js +39 -0
  339. package/web/dist/assets/quadrantDiagram-AXDQQJYC-VW6fAhsV.js +7 -0
  340. package/web/dist/assets/railroadDiagram-O6MQD6OU-CKJ7Ba5K.js +1 -0
  341. package/web/dist/assets/requirementDiagram-EFPCY7ZU-pNGkq9Vw.js +84 -0
  342. package/web/dist/assets/{rotate-cw-LMT3x1Zj.js → rotate-cw-KAfChbGV.js} +1 -1
  343. package/web/dist/assets/sankeyDiagram-P5KCCOFB-BIMmDTfZ.js +40 -0
  344. package/web/dist/assets/sequenceDiagram-WJ2MYXX4-BBW7wzKY.js +162 -0
  345. package/web/dist/assets/sizeCapture-X5ZJPWSS-DdkXmLYR.js +1 -0
  346. package/web/dist/assets/{square-dashed-mouse-pointer-CBoAkfhi.js → square-dashed-mouse-pointer-CDUlhUP7.js} +1 -1
  347. package/web/dist/assets/stateDiagram-HBIQ2CUA-W313Uz16.js +1 -0
  348. package/web/dist/assets/stateDiagram-v2-4QOOHH4V-DDhJBZzf.js +1 -0
  349. package/web/dist/assets/swimlanes-XN3QIQJK-C29TL26K.js +1 -0
  350. package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BfN-Xdmu.js +8 -0
  351. package/web/dist/assets/timeline-definition-24CTP7MA-CF0JMObX.js +120 -0
  352. package/web/dist/assets/vennDiagram-4TSXK5OY-DrDFsaH3.js +34 -0
  353. package/web/dist/assets/wardleyDiagram-VM6X3IG4-CVrnRIS_.js +78 -0
  354. package/web/dist/assets/xychartDiagram-S5SC5T6Z-BvkN1zsT.js +7 -0
  355. package/web/dist/index.html +17 -17
  356. package/web/dist/assets/BrowserWindow-Dc7J7CYc.js +0 -11
  357. package/web/dist/assets/SiteWindow-D6eS-2gM.js +0 -18
  358. package/web/dist/assets/index-Cbl3ATvp.js +0 -2125
@@ -1,225 +1,225 @@
1
- /**
2
- * text-lint.js — 构建后的字符层/版式层体检(2026-08-19)。
3
- *
4
- * 闭合 schema 管格式键、渲染目视管版面,**文字符号这一层两边都管不着**:
5
- * 斜杠带不带空格不一致、`12ms` 缺空隙、中文后面跟半角逗号 —— 人眼查全文
6
- * 一致性不可靠,正则穷举才靠得住。检查跑在 **content 源**上(不解 docx),
7
- * 每条发现都能定位到具体块。
8
- *
9
- * 三条设计红线(放宽任何一条都会把这层变成噪音):
10
- * 1. **只收高精度规则**。假警报会训练 agent 忽略警报 —— 拿不准的宁可不报。
11
- * 2. **一致性类规则全文聚合后才裁决**:两种写法并存才是问题,全文统一用
12
- * 哪种都不是(斜杠风格、中西文间距都是排版偏好,不是对错)。
13
- * 3. **只报不改**。文字内容是 agent/用户的,引擎不代笔。
14
- *
15
- * 两档措辞:`errors` = 按 GB/T 15834 / 排版常识基本可断定是错的;
16
- * `notes` = 建议(惯例/一致性),采不采纳由写的人定。都不挡构建。
17
- */
18
-
19
- import { effectiveSizePt } from './build.js';
20
-
21
- const CJK = '\\u4e00-\\u9fff\\u3400-\\u4dbf';
22
-
23
- /** 一个段落块 → 拼好的纯文本(run 边界处的间距问题也要能查到,所以先拼再扫) */
24
- function textOfPara(b) {
25
- if (typeof b?.text === 'string') return b.text;
26
- const parts = [];
27
- for (const r of b?.runs ?? []) {
28
- if (typeof r === 'string') parts.push(r);
29
- else if (r && typeof r.text === 'string') parts.push(r.text);
30
- else if (r?.br) parts.push('\n');
31
- // fld(页码域)没有文字形态,跳过
32
- }
33
- return parts.join('');
34
- }
35
-
36
- /** content + 页眉页脚 + 表格单元格 → [{ where, text }],where 能定位回源 */
37
- export function collectTexts(content, opts = {}) {
38
- const out = [];
39
- const push = (where, b) => {
40
- const t = textOfPara(b);
41
- if (t.trim()) out.push({ where, text: t });
42
- };
43
- for (const [i, b] of (content ?? []).entries()) {
44
- if (!b || typeof b !== 'object') continue;
45
- if (b.t === 'p') push(`content[${i}]`, b);
46
- else if (b.t === 'table') {
47
- for (const [ri, row] of (b.rows ?? []).entries()) {
48
- for (const [ci, cell] of (row ?? []).entries()) {
49
- if (typeof cell === 'string') { if (cell.trim()) out.push({ where: `content[${i}].rows[${ri}][${ci}]`, text: cell }); }
50
- else if (cell && typeof cell === 'object') push(`content[${i}].rows[${ri}][${ci}]`, cell);
51
- }
52
- }
53
- }
54
- }
55
- for (const [name, blocks] of [['header', opts.header], ['footer', opts.footer]]) {
56
- for (const [i, b] of (blocks ?? []).entries()) {
57
- if (b?.t === 'p') push(`${name}[${i}]`, b);
58
- }
59
- }
60
- return out;
61
- }
62
-
63
- /** 命中清单 → 一行报文:最多点名 4 处,多的收成 ×N */
64
- function fmtHits(hits, max = 4) {
65
- const shown = hits.slice(0, max).map(h => `${h.where}「${h.sample}」`).join('、');
66
- return hits.length > max ? `${shown} 等 ${hits.length} 处` : shown;
67
- }
68
-
69
- /** 在 text 里跑正则收命中(sample 带一点上下文,agent 才对得上号) */
70
- function scan(texts, re) {
71
- const hits = [];
72
- for (const { where, text } of texts) {
73
- for (const m of text.matchAll(re)) {
74
- const s = Math.max(0, m.index - 4);
75
- hits.push({ where, sample: text.slice(s, m.index + m[0].length + 4).replace(/\n/g, ' ') });
76
- }
77
- }
78
- return hits;
79
- }
80
-
81
- export function lintDocxSource({ tokens, content, opts = {} }) {
82
- const errors = [];
83
- const notes = [];
84
- const texts = collectTexts(content, opts);
85
-
86
- /* ── 错误档(基本可断定是错的) ── */
87
-
88
- // 重复标点:「。。」「,,」几乎必是手误。!?的叠用是修辞,不管
89
- for (const [re, label] of [[new RegExp('([,。;、:])\\1+', 'g'), '重复标点']]) {
90
- const hits = scan(texts, re);
91
- if (hits.length) errors.push(`${label}:${fmtHits(hits)}`);
92
- }
93
-
94
- // 括号全半角不配对:「(x)」「(x)」
95
- {
96
- const hits = scan(texts, /([^()()\n]*\)|\([^()()\n]*)/g);
97
- if (hits.length) errors.push(`括号全半角不配对:${fmtHits(hits)}`);
98
- }
99
-
100
- // 中文后紧跟半角标点(GB/T 15834:中文语境用全角)。只查后继是中文/空白/行尾
101
- // 的情形 —— 「12:30」「Node.js, Express」这类西文语境不碰
102
- {
103
- const hits = scan(texts, new RegExp(`[${CJK}][,;?!:](?=[${CJK}\\s]|$)`, 'gm'));
104
- if (hits.length) errors.push(`中文后面跟着半角标点(该用全角):${fmtHits(hits)}`);
105
- }
106
-
107
- /* ── 建议档:单点惯例 ── */
108
-
109
- // 数值与单位符号之间留空隙(GB 3100 惯例)。单位表故意收窄:裸 s/m/g 歧义太大
110
- {
111
- const hits = scan(texts, /\d(?:ms|GB|MB|KB|TB|Hz|kHz|MHz|fps|mm|cm|km|kg|dpi)(?![A-Za-z])/g);
112
- if (hits.length) notes.push(`数字和单位之间通常留一个空格(如「150 ms」):${fmtHits(hits)}`);
113
- }
114
-
115
- /* ── 建议档:一致性类(全文聚合,两种写法并存才报) ── */
116
-
117
- // 中西文之间的空格。数字不算(「第3个」加空格属于过度矫正),URL 整段跳过
118
- {
119
- let spaced = 0;
120
- const unspaced = [];
121
- for (const { where, text } of texts) {
122
- const t = text.replace(/\S*:\/\/\S*/g, ' ');
123
- for (const m of t.matchAll(new RegExp(`[${CJK}][A-Za-z]|[A-Za-z][${CJK}]`, 'g'))) {
124
- unspaced.push({ where, sample: t.slice(Math.max(0, m.index - 4), m.index + m[0].length + 4) });
125
- }
126
- spaced += (t.match(new RegExp(`[${CJK}] [A-Za-z]|[A-Za-z] [${CJK}]`, 'g')) ?? []).length;
127
- }
128
- if (unspaced.length && spaced > unspaced.length) {
129
- notes.push(`中西文之间的空格不一致:全文 ${spaced} 处有空格,这 ${unspaced.length} 处没有 —— ${fmtHits(unspaced)}`);
130
- }
131
- // 全文都不空 = 一种自洽的风格,不报
132
- }
133
-
134
- // 斜杠两侧空格的一致性。三类豁免:带协议的 URL、无协议的域名路径
135
- //(github.com/xxx —— 给它"统一"加空格反而是改错)、两侧都是数字的(日期 03/07)
136
- {
137
- const spaced = [];
138
- const tight = [];
139
- for (const { where, text } of texts) {
140
- const t = text.replace(/\S*:\/\/\S*/g, ' ');
141
- for (const m of t.matchAll(/([\w.\-一-鿿]+) ?\/ ?([\w.\-一-鿿])/g)) {
142
- if (/\.[a-z]{2,4}$/i.test(m[1])) continue; // 左边以 .com/.cn/.js 这类结尾 = 域名或文件名
143
- if (/\d$/.test(m[1]) && /\d/.test(m[2])) continue;
144
- (m[0].includes(' ') ? spaced : tight).push({ where, sample: t.slice(Math.max(0, m.index - 3), m.index + m[0].length + 6) });
145
- }
146
- }
147
- if (spaced.length && tight.length) {
148
- const [minor, label] = spaced.length < tight.length ? [spaced, '带空格'] : [tight, '不带空格'];
149
- notes.push(`斜杠写法不一致(${spaced.length} 处「A / B」vs ${tight.length} 处「A/B」),少数派是${label}的:${fmtHits(minor)}`);
150
- }
151
- }
152
-
153
- // 日期范围连接号的一致性(字符本身不裁 —— 「-」「–」「至」都是有人在用的惯例,
154
- // 一份文档里混着用才是问题)
155
- {
156
- const styles = new Map(); // 连接串 → hits
157
- for (const { where, text } of texts) {
158
- for (const m of text.matchAll(/\d{4}[..年/-]\d{1,2}[月]?( ?[-–—~~至] ?)(?=\d{4}|至今|今)/g)) {
159
- const key = m[1];
160
- if (!styles.has(key)) styles.set(key, []);
161
- styles.get(key).push({ where, sample: text.slice(Math.max(0, m.index), m.index + m[0].length + 7) });
162
- }
163
- }
164
- if (styles.size > 1) {
165
- const desc = [...styles.entries()].map(([k, v]) => `「${k.replace(/ /g, '␣')}」×${v.length}`).join('、');
166
- notes.push(`日期范围的连接号写法不统一:${desc}`);
167
- }
168
- }
169
-
170
- /* ── 版式断言(token 层,只收零误报的三条) ── */
171
-
172
- // 行距小于单倍 = 文字上下行叠压,多半是把磅值当倍数写了
173
- const checkSp = (whereName, sp) => {
174
- if (sp && (sp.lineRule ?? 'multiple') === 'multiple' && sp.line != null && sp.line < 1) {
175
- errors.push(`${whereName}.spacing.line = ${sp.line}(倍数小于 1,文字会叠压)`);
176
- }
177
- };
178
- checkSp('base', tokens?.base?.spacing);
179
- for (const [id, st] of Object.entries(tokens?.styles ?? {})) {
180
- checkSp(`styles.${id}.para`, st?.para?.spacing);
181
-
182
- // 标题(有 outlineLevel 的)不带 keepNext → 会孤零零留在页底
183
- if (st?.para?.outlineLevel != null && st.para.keepNext !== true) {
184
- notes.push(`styles.${id} 是标题(outlineLevel ${st.para.outlineLevel})但没写 keepNext —— 分页时它可能孤零零留在页底`);
185
- }
186
- }
187
-
188
- // 标题层级字号倒挂:更深一级的某个标题比浅一级的某个标题还大。
189
- // 同层可以有多种字号(Title 和 Heading1 常同在层级 0),所以比的是
190
- // 浅层的**最小**对深层的**最大** —— 只要深层最大越过了浅层最小就是倒挂
191
- {
192
- const byLevel = new Map(); // level → { min: {id,pt}, max: {id,pt} }
193
- for (const [id, st] of Object.entries(tokens?.styles ?? {})) {
194
- const lv = st?.para?.outlineLevel;
195
- if (lv == null) continue;
196
- const pt = effectiveSizePt(tokens, id);
197
- const e = byLevel.get(lv) ?? { min: { id, pt }, max: { id, pt } };
198
- if (pt < e.min.pt) e.min = { id, pt };
199
- if (pt > e.max.pt) e.max = { id, pt };
200
- byLevel.set(lv, e);
201
- }
202
- const levels = [...byLevel.keys()].sort((a, b) => a - b);
203
- for (let i = 1; i < levels.length; i++) {
204
- const hi = byLevel.get(levels[i - 1]).min;
205
- const lo = byLevel.get(levels[i]).max;
206
- if (lo.pt > hi.pt) {
207
- notes.push(`标题字号倒挂:${lo.id}(层级 ${levels[i]},${lo.pt}pt)比 ${hi.id}(层级 ${levels[i - 1]},${hi.pt}pt)还大`);
208
- }
209
- }
210
- }
211
-
212
- return { errors, notes };
213
- }
214
-
215
- /** 给 build_docx 的返回文案拼提醒段。空的返回 ''(一个字都不占) */
216
- export function formatLint({ errors, notes }, max = 10) {
217
- const lines = [];
218
- for (const e of errors) lines.push(`⚠ ${e}`);
219
- for (const n of notes) lines.push(`· ${n}`);
220
- if (!lines.length) return '';
221
- const shown = lines.slice(0, max);
222
- const more = lines.length - shown.length;
223
- return `\n\n排版/符号体检(${errors.length} 条较确定的问题,${notes.length} 条建议 —— 不挡构建,采不采纳你判断,改了记得重新 build):\n`
224
- + shown.join('\n') + (more > 0 ? `\n(另有 ${more} 条略去)` : '');
225
- }
1
+ /**
2
+ * text-lint.js — 构建后的字符层/版式层体检(2026-08-19)。
3
+ *
4
+ * 闭合 schema 管格式键、渲染目视管版面,**文字符号这一层两边都管不着**:
5
+ * 斜杠带不带空格不一致、`12ms` 缺空隙、中文后面跟半角逗号 —— 人眼查全文
6
+ * 一致性不可靠,正则穷举才靠得住。检查跑在 **content 源**上(不解 docx),
7
+ * 每条发现都能定位到具体块。
8
+ *
9
+ * 三条设计红线(放宽任何一条都会把这层变成噪音):
10
+ * 1. **只收高精度规则**。假警报会训练 agent 忽略警报 —— 拿不准的宁可不报。
11
+ * 2. **一致性类规则全文聚合后才裁决**:两种写法并存才是问题,全文统一用
12
+ * 哪种都不是(斜杠风格、中西文间距都是排版偏好,不是对错)。
13
+ * 3. **只报不改**。文字内容是 agent/用户的,引擎不代笔。
14
+ *
15
+ * 两档措辞:`errors` = 按 GB/T 15834 / 排版常识基本可断定是错的;
16
+ * `notes` = 建议(惯例/一致性),采不采纳由写的人定。都不挡构建。
17
+ */
18
+
19
+ import { effectiveSizePt } from './build.js';
20
+
21
+ const CJK = '\\u4e00-\\u9fff\\u3400-\\u4dbf';
22
+
23
+ /** 一个段落块 → 拼好的纯文本(run 边界处的间距问题也要能查到,所以先拼再扫) */
24
+ function textOfPara(b) {
25
+ if (typeof b?.text === 'string') return b.text;
26
+ const parts = [];
27
+ for (const r of b?.runs ?? []) {
28
+ if (typeof r === 'string') parts.push(r);
29
+ else if (r && typeof r.text === 'string') parts.push(r.text);
30
+ else if (r?.br) parts.push('\n');
31
+ // fld(页码域)没有文字形态,跳过
32
+ }
33
+ return parts.join('');
34
+ }
35
+
36
+ /** content + 页眉页脚 + 表格单元格 → [{ where, text }],where 能定位回源 */
37
+ export function collectTexts(content, opts = {}) {
38
+ const out = [];
39
+ const push = (where, b) => {
40
+ const t = textOfPara(b);
41
+ if (t.trim()) out.push({ where, text: t });
42
+ };
43
+ for (const [i, b] of (content ?? []).entries()) {
44
+ if (!b || typeof b !== 'object') continue;
45
+ if (b.t === 'p') push(`content[${i}]`, b);
46
+ else if (b.t === 'table') {
47
+ for (const [ri, row] of (b.rows ?? []).entries()) {
48
+ for (const [ci, cell] of (row ?? []).entries()) {
49
+ if (typeof cell === 'string') { if (cell.trim()) out.push({ where: `content[${i}].rows[${ri}][${ci}]`, text: cell }); }
50
+ else if (cell && typeof cell === 'object') push(`content[${i}].rows[${ri}][${ci}]`, cell);
51
+ }
52
+ }
53
+ }
54
+ }
55
+ for (const [name, blocks] of [['header', opts.header], ['footer', opts.footer]]) {
56
+ for (const [i, b] of (blocks ?? []).entries()) {
57
+ if (b?.t === 'p') push(`${name}[${i}]`, b);
58
+ }
59
+ }
60
+ return out;
61
+ }
62
+
63
+ /** 命中清单 → 一行报文:最多点名 4 处,多的收成 ×N */
64
+ function fmtHits(hits, max = 4) {
65
+ const shown = hits.slice(0, max).map(h => `${h.where}「${h.sample}」`).join('、');
66
+ return hits.length > max ? `${shown} 等 ${hits.length} 处` : shown;
67
+ }
68
+
69
+ /** 在 text 里跑正则收命中(sample 带一点上下文,agent 才对得上号) */
70
+ function scan(texts, re) {
71
+ const hits = [];
72
+ for (const { where, text } of texts) {
73
+ for (const m of text.matchAll(re)) {
74
+ const s = Math.max(0, m.index - 4);
75
+ hits.push({ where, sample: text.slice(s, m.index + m[0].length + 4).replace(/\n/g, ' ') });
76
+ }
77
+ }
78
+ return hits;
79
+ }
80
+
81
+ export function lintDocxSource({ tokens, content, opts = {} }) {
82
+ const errors = [];
83
+ const notes = [];
84
+ const texts = collectTexts(content, opts);
85
+
86
+ /* ── 错误档(基本可断定是错的) ── */
87
+
88
+ // 重复标点:「。。」「,,」几乎必是手误。!?的叠用是修辞,不管
89
+ for (const [re, label] of [[new RegExp('([,。;、:])\\1+', 'g'), '重复标点']]) {
90
+ const hits = scan(texts, re);
91
+ if (hits.length) errors.push(`${label}:${fmtHits(hits)}`);
92
+ }
93
+
94
+ // 括号全半角不配对:「(x)」「(x)」
95
+ {
96
+ const hits = scan(texts, /([^()()\n]*\)|\([^()()\n]*)/g);
97
+ if (hits.length) errors.push(`括号全半角不配对:${fmtHits(hits)}`);
98
+ }
99
+
100
+ // 中文后紧跟半角标点(GB/T 15834:中文语境用全角)。只查后继是中文/空白/行尾
101
+ // 的情形 —— 「12:30」「Node.js, Express」这类西文语境不碰
102
+ {
103
+ const hits = scan(texts, new RegExp(`[${CJK}][,;?!:](?=[${CJK}\\s]|$)`, 'gm'));
104
+ if (hits.length) errors.push(`中文后面跟着半角标点(该用全角):${fmtHits(hits)}`);
105
+ }
106
+
107
+ /* ── 建议档:单点惯例 ── */
108
+
109
+ // 数值与单位符号之间留空隙(GB 3100 惯例)。单位表故意收窄:裸 s/m/g 歧义太大
110
+ {
111
+ const hits = scan(texts, /\d(?:ms|GB|MB|KB|TB|Hz|kHz|MHz|fps|mm|cm|km|kg|dpi)(?![A-Za-z])/g);
112
+ if (hits.length) notes.push(`数字和单位之间通常留一个空格(如「150 ms」):${fmtHits(hits)}`);
113
+ }
114
+
115
+ /* ── 建议档:一致性类(全文聚合,两种写法并存才报) ── */
116
+
117
+ // 中西文之间的空格。数字不算(「第3个」加空格属于过度矫正),URL 整段跳过
118
+ {
119
+ let spaced = 0;
120
+ const unspaced = [];
121
+ for (const { where, text } of texts) {
122
+ const t = text.replace(/\S*:\/\/\S*/g, ' ');
123
+ for (const m of t.matchAll(new RegExp(`[${CJK}][A-Za-z]|[A-Za-z][${CJK}]`, 'g'))) {
124
+ unspaced.push({ where, sample: t.slice(Math.max(0, m.index - 4), m.index + m[0].length + 4) });
125
+ }
126
+ spaced += (t.match(new RegExp(`[${CJK}] [A-Za-z]|[A-Za-z] [${CJK}]`, 'g')) ?? []).length;
127
+ }
128
+ if (unspaced.length && spaced > unspaced.length) {
129
+ notes.push(`中西文之间的空格不一致:全文 ${spaced} 处有空格,这 ${unspaced.length} 处没有 —— ${fmtHits(unspaced)}`);
130
+ }
131
+ // 全文都不空 = 一种自洽的风格,不报
132
+ }
133
+
134
+ // 斜杠两侧空格的一致性。三类豁免:带协议的 URL、无协议的域名路径
135
+ //(github.com/xxx —— 给它"统一"加空格反而是改错)、两侧都是数字的(日期 03/07)
136
+ {
137
+ const spaced = [];
138
+ const tight = [];
139
+ for (const { where, text } of texts) {
140
+ const t = text.replace(/\S*:\/\/\S*/g, ' ');
141
+ for (const m of t.matchAll(/([\w.\-一-鿿]+) ?\/ ?([\w.\-一-鿿])/g)) {
142
+ if (/\.[a-z]{2,4}$/i.test(m[1])) continue; // 左边以 .com/.cn/.js 这类结尾 = 域名或文件名
143
+ if (/\d$/.test(m[1]) && /\d/.test(m[2])) continue;
144
+ (m[0].includes(' ') ? spaced : tight).push({ where, sample: t.slice(Math.max(0, m.index - 3), m.index + m[0].length + 6) });
145
+ }
146
+ }
147
+ if (spaced.length && tight.length) {
148
+ const [minor, label] = spaced.length < tight.length ? [spaced, '带空格'] : [tight, '不带空格'];
149
+ notes.push(`斜杠写法不一致(${spaced.length} 处「A / B」vs ${tight.length} 处「A/B」),少数派是${label}的:${fmtHits(minor)}`);
150
+ }
151
+ }
152
+
153
+ // 日期范围连接号的一致性(字符本身不裁 —— 「-」「–」「至」都是有人在用的惯例,
154
+ // 一份文档里混着用才是问题)
155
+ {
156
+ const styles = new Map(); // 连接串 → hits
157
+ for (const { where, text } of texts) {
158
+ for (const m of text.matchAll(/\d{4}[..年/-]\d{1,2}[月]?( ?[-–—~~至] ?)(?=\d{4}|至今|今)/g)) {
159
+ const key = m[1];
160
+ if (!styles.has(key)) styles.set(key, []);
161
+ styles.get(key).push({ where, sample: text.slice(Math.max(0, m.index), m.index + m[0].length + 7) });
162
+ }
163
+ }
164
+ if (styles.size > 1) {
165
+ const desc = [...styles.entries()].map(([k, v]) => `「${k.replace(/ /g, '␣')}」×${v.length}`).join('、');
166
+ notes.push(`日期范围的连接号写法不统一:${desc}`);
167
+ }
168
+ }
169
+
170
+ /* ── 版式断言(token 层,只收零误报的三条) ── */
171
+
172
+ // 行距小于单倍 = 文字上下行叠压,多半是把磅值当倍数写了
173
+ const checkSp = (whereName, sp) => {
174
+ if (sp && (sp.lineRule ?? 'multiple') === 'multiple' && sp.line != null && sp.line < 1) {
175
+ errors.push(`${whereName}.spacing.line = ${sp.line}(倍数小于 1,文字会叠压)`);
176
+ }
177
+ };
178
+ checkSp('base', tokens?.base?.spacing);
179
+ for (const [id, st] of Object.entries(tokens?.styles ?? {})) {
180
+ checkSp(`styles.${id}.para`, st?.para?.spacing);
181
+
182
+ // 标题(有 outlineLevel 的)不带 keepNext → 会孤零零留在页底
183
+ if (st?.para?.outlineLevel != null && st.para.keepNext !== true) {
184
+ notes.push(`styles.${id} 是标题(outlineLevel ${st.para.outlineLevel})但没写 keepNext —— 分页时它可能孤零零留在页底`);
185
+ }
186
+ }
187
+
188
+ // 标题层级字号倒挂:更深一级的某个标题比浅一级的某个标题还大。
189
+ // 同层可以有多种字号(Title 和 Heading1 常同在层级 0),所以比的是
190
+ // 浅层的**最小**对深层的**最大** —— 只要深层最大越过了浅层最小就是倒挂
191
+ {
192
+ const byLevel = new Map(); // level → { min: {id,pt}, max: {id,pt} }
193
+ for (const [id, st] of Object.entries(tokens?.styles ?? {})) {
194
+ const lv = st?.para?.outlineLevel;
195
+ if (lv == null) continue;
196
+ const pt = effectiveSizePt(tokens, id);
197
+ const e = byLevel.get(lv) ?? { min: { id, pt }, max: { id, pt } };
198
+ if (pt < e.min.pt) e.min = { id, pt };
199
+ if (pt > e.max.pt) e.max = { id, pt };
200
+ byLevel.set(lv, e);
201
+ }
202
+ const levels = [...byLevel.keys()].sort((a, b) => a - b);
203
+ for (let i = 1; i < levels.length; i++) {
204
+ const hi = byLevel.get(levels[i - 1]).min;
205
+ const lo = byLevel.get(levels[i]).max;
206
+ if (lo.pt > hi.pt) {
207
+ notes.push(`标题字号倒挂:${lo.id}(层级 ${levels[i]},${lo.pt}pt)比 ${hi.id}(层级 ${levels[i - 1]},${hi.pt}pt)还大`);
208
+ }
209
+ }
210
+ }
211
+
212
+ return { errors, notes };
213
+ }
214
+
215
+ /** 给 build_docx 的返回文案拼提醒段。空的返回 ''(一个字都不占) */
216
+ export function formatLint({ errors, notes }, max = 10) {
217
+ const lines = [];
218
+ for (const e of errors) lines.push(`⚠ ${e}`);
219
+ for (const n of notes) lines.push(`· ${n}`);
220
+ if (!lines.length) return '';
221
+ const shown = lines.slice(0, max);
222
+ const more = lines.length - shown.length;
223
+ return `\n\n排版/符号体检(${errors.length} 条较确定的问题,${notes.length} 条建议 —— 不挡构建,采不采纳你判断,改了记得重新 build):\n`
224
+ + shown.join('\n') + (more > 0 ? `\n(另有 ${more} 条略去)` : '');
225
+ }