scrum-skills 0.3.2 → 0.3.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 (384) hide show
  1. package/.agents/skills/diagram-design/LICENSE +4 -0
  2. package/.agents/skills/diagram-design/SKILL.md +413 -0
  3. package/.agents/skills/diagram-design/UPSTREAM.json +76 -0
  4. package/.agents/skills/diagram-design/UPSTREAM.md +23 -0
  5. package/.agents/skills/diagram-design/assets/example-architecture-containment.html +205 -0
  6. package/.agents/skills/diagram-design/assets/example-architecture-delta.html +193 -0
  7. package/.agents/skills/diagram-design/assets/example-architecture.html +183 -0
  8. package/.agents/skills/diagram-design/assets/example-axonometric-plan-campus.html +108 -0
  9. package/.agents/skills/diagram-design/assets/example-axonometric-plan-coffee-shop.html +121 -0
  10. package/.agents/skills/diagram-design/assets/example-axonometric-plan-warehouse.html +127 -0
  11. package/.agents/skills/diagram-design/assets/example-axonometric-plan.html +114 -0
  12. package/.agents/skills/diagram-design/assets/example-bar.html +129 -0
  13. package/.agents/skills/diagram-design/assets/example-beeswarm.html +384 -0
  14. package/.agents/skills/diagram-design/assets/example-box-plot.html +127 -0
  15. package/.agents/skills/diagram-design/assets/example-bubble.html +147 -0
  16. package/.agents/skills/diagram-design/assets/example-bump.html +142 -0
  17. package/.agents/skills/diagram-design/assets/example-data-flow.html +143 -0
  18. package/.agents/skills/diagram-design/assets/example-datalake.html +269 -0
  19. package/.agents/skills/diagram-design/assets/example-db-schema.html +302 -0
  20. package/.agents/skills/diagram-design/assets/example-dependency.html +224 -0
  21. package/.agents/skills/diagram-design/assets/example-deployment.html +190 -0
  22. package/.agents/skills/diagram-design/assets/example-dp-integration.html +81 -0
  23. package/.agents/skills/diagram-design/assets/example-dp-security-matrix.html +88 -0
  24. package/.agents/skills/diagram-design/assets/example-er.html +202 -0
  25. package/.agents/skills/diagram-design/assets/example-exploded-ai-stack.html +85 -0
  26. package/.agents/skills/diagram-design/assets/example-exploded-keyboard.html +84 -0
  27. package/.agents/skills/diagram-design/assets/example-exploded-phone.html +83 -0
  28. package/.agents/skills/diagram-design/assets/example-exploded-unboxing.html +84 -0
  29. package/.agents/skills/diagram-design/assets/example-exploded.html +83 -0
  30. package/.agents/skills/diagram-design/assets/example-fishbone.html +152 -0
  31. package/.agents/skills/diagram-design/assets/example-flowchart.html +163 -0
  32. package/.agents/skills/diagram-design/assets/example-gantt.html +137 -0
  33. package/.agents/skills/diagram-design/assets/example-heatmap.html +224 -0
  34. package/.agents/skills/diagram-design/assets/example-high-level-vertical.html +325 -0
  35. package/.agents/skills/diagram-design/assets/example-high-level.html +301 -0
  36. package/.agents/skills/diagram-design/assets/example-import-drawio.html +185 -0
  37. package/.agents/skills/diagram-design/assets/example-import-excalidraw.html +70 -0
  38. package/.agents/skills/diagram-design/assets/example-import-mermaid.html +70 -0
  39. package/.agents/skills/diagram-design/assets/example-it-state.html +128 -0
  40. package/.agents/skills/diagram-design/assets/example-journey.html +148 -0
  41. package/.agents/skills/diagram-design/assets/example-kanban.html +174 -0
  42. package/.agents/skills/diagram-design/assets/example-layers.html +123 -0
  43. package/.agents/skills/diagram-design/assets/example-line.html +111 -0
  44. package/.agents/skills/diagram-design/assets/example-loop.html +100 -0
  45. package/.agents/skills/diagram-design/assets/example-marimekko.html +148 -0
  46. package/.agents/skills/diagram-design/assets/example-medallion.html +159 -0
  47. package/.agents/skills/diagram-design/assets/example-nested.html +136 -0
  48. package/.agents/skills/diagram-design/assets/example-org-chart.html +45 -0
  49. package/.agents/skills/diagram-design/assets/example-polar.html +116 -0
  50. package/.agents/skills/diagram-design/assets/example-process.html +64 -0
  51. package/.agents/skills/diagram-design/assets/example-pyramid.html +119 -0
  52. package/.agents/skills/diagram-design/assets/example-quadrant-consultant.html +166 -0
  53. package/.agents/skills/diagram-design/assets/example-quadrant.html +135 -0
  54. package/.agents/skills/diagram-design/assets/example-radar.html +139 -0
  55. package/.agents/skills/diagram-design/assets/example-ridgeline.html +104 -0
  56. package/.agents/skills/diagram-design/assets/example-sankey.html +150 -0
  57. package/.agents/skills/diagram-design/assets/example-scatter.html +141 -0
  58. package/.agents/skills/diagram-design/assets/example-sequence-oauth.html +227 -0
  59. package/.agents/skills/diagram-design/assets/example-sequence.html +222 -0
  60. package/.agents/skills/diagram-design/assets/example-service-blueprint.html +185 -0
  61. package/.agents/skills/diagram-design/assets/example-slopegraph.html +119 -0
  62. package/.agents/skills/diagram-design/assets/example-state-lifecycle.html +92 -0
  63. package/.agents/skills/diagram-design/assets/example-state.html +147 -0
  64. package/.agents/skills/diagram-design/assets/example-story-map.html +184 -0
  65. package/.agents/skills/diagram-design/assets/example-streamgraph.html +99 -0
  66. package/.agents/skills/diagram-design/assets/example-swimlane.html +172 -0
  67. package/.agents/skills/diagram-design/assets/example-timeline.html +135 -0
  68. package/.agents/skills/diagram-design/assets/example-tree-block-decomposition.html +151 -0
  69. package/.agents/skills/diagram-design/assets/example-tree.html +173 -0
  70. package/.agents/skills/diagram-design/assets/example-treemap.html +107 -0
  71. package/.agents/skills/diagram-design/assets/example-uml-class.html +188 -0
  72. package/.agents/skills/diagram-design/assets/example-venn.html +110 -0
  73. package/.agents/skills/diagram-design/assets/example-wardley.html +153 -0
  74. package/.agents/skills/diagram-design/assets/example-waterfall.html +129 -0
  75. package/.agents/skills/diagram-design/assets/template-dark.html +99 -0
  76. package/.agents/skills/diagram-design/assets/template-full.html +421 -0
  77. package/.agents/skills/diagram-design/assets/template-motion.html +448 -0
  78. package/.agents/skills/diagram-design/assets/template-terminal.html +188 -0
  79. package/.agents/skills/diagram-design/assets/template.html +100 -0
  80. package/.agents/skills/diagram-design/references/animation.md +143 -0
  81. package/.agents/skills/diagram-design/references/export-registry.md +78 -0
  82. package/.agents/skills/diagram-design/references/export.md +376 -0
  83. package/.agents/skills/diagram-design/references/import-drawio.md +181 -0
  84. package/.agents/skills/diagram-design/references/import-excalidraw.md +130 -0
  85. package/.agents/skills/diagram-design/references/import-mermaid.md +137 -0
  86. package/.agents/skills/diagram-design/references/layout-budget.md +97 -0
  87. package/.agents/skills/diagram-design/references/onboarding.md +368 -0
  88. package/.agents/skills/diagram-design/references/output-spec.md +277 -0
  89. package/.agents/skills/diagram-design/references/primitive-annotation.md +36 -0
  90. package/.agents/skills/diagram-design/references/primitive-icons.md +827 -0
  91. package/.agents/skills/diagram-design/references/primitive-sketchy.md +43 -0
  92. package/.agents/skills/diagram-design/references/primitive-terminal.md +76 -0
  93. package/.agents/skills/diagram-design/references/primitives-core.md +146 -0
  94. package/.agents/skills/diagram-design/references/profiles.md +183 -0
  95. package/.agents/skills/diagram-design/references/semantic-patterns.md +154 -0
  96. package/.agents/skills/diagram-design/references/style-guide.md +236 -0
  97. package/.agents/skills/diagram-design/references/style-vivid.md +148 -0
  98. package/.agents/skills/diagram-design/references/type-architecture-delta.md +143 -0
  99. package/.agents/skills/diagram-design/references/type-architecture.md +117 -0
  100. package/.agents/skills/diagram-design/references/type-axonometric-plan.md +83 -0
  101. package/.agents/skills/diagram-design/references/type-bar.md +108 -0
  102. package/.agents/skills/diagram-design/references/type-data-flow.md +371 -0
  103. package/.agents/skills/diagram-design/references/type-db-schema.md +48 -0
  104. package/.agents/skills/diagram-design/references/type-dependency.md +44 -0
  105. package/.agents/skills/diagram-design/references/type-deployment.md +45 -0
  106. package/.agents/skills/diagram-design/references/type-dp-integration.md +407 -0
  107. package/.agents/skills/diagram-design/references/type-dp-security-matrix.md +379 -0
  108. package/.agents/skills/diagram-design/references/type-er.md +25 -0
  109. package/.agents/skills/diagram-design/references/type-exploded.md +115 -0
  110. package/.agents/skills/diagram-design/references/type-fishbone.md +75 -0
  111. package/.agents/skills/diagram-design/references/type-flowchart.md +23 -0
  112. package/.agents/skills/diagram-design/references/type-gantt.md +45 -0
  113. package/.agents/skills/diagram-design/references/type-heatmap.md +80 -0
  114. package/.agents/skills/diagram-design/references/type-high-level.md +470 -0
  115. package/.agents/skills/diagram-design/references/type-it-state.md +468 -0
  116. package/.agents/skills/diagram-design/references/type-journey.md +55 -0
  117. package/.agents/skills/diagram-design/references/type-kanban.md +49 -0
  118. package/.agents/skills/diagram-design/references/type-layers.md +26 -0
  119. package/.agents/skills/diagram-design/references/type-line.md +337 -0
  120. package/.agents/skills/diagram-design/references/type-loop.md +223 -0
  121. package/.agents/skills/diagram-design/references/type-medallion.md +356 -0
  122. package/.agents/skills/diagram-design/references/type-nested.md +22 -0
  123. package/.agents/skills/diagram-design/references/type-org-chart.md +44 -0
  124. package/.agents/skills/diagram-design/references/type-polar.md +136 -0
  125. package/.agents/skills/diagram-design/references/type-process.md +490 -0
  126. package/.agents/skills/diagram-design/references/type-pyramid.md +33 -0
  127. package/.agents/skills/diagram-design/references/type-quadrant.md +81 -0
  128. package/.agents/skills/diagram-design/references/type-radar.md +82 -0
  129. package/.agents/skills/diagram-design/references/type-sankey.md +75 -0
  130. package/.agents/skills/diagram-design/references/type-scatter.md +276 -0
  131. package/.agents/skills/diagram-design/references/type-sequence.md +130 -0
  132. package/.agents/skills/diagram-design/references/type-service-blueprint.md +103 -0
  133. package/.agents/skills/diagram-design/references/type-state.md +30 -0
  134. package/.agents/skills/diagram-design/references/type-story-map.md +46 -0
  135. package/.agents/skills/diagram-design/references/type-swimlane.md +22 -0
  136. package/.agents/skills/diagram-design/references/type-timeline.md +20 -0
  137. package/.agents/skills/diagram-design/references/type-tree.md +24 -0
  138. package/.agents/skills/diagram-design/references/type-treemap.md +137 -0
  139. package/.agents/skills/diagram-design/references/type-uml-class.md +65 -0
  140. package/.agents/skills/diagram-design/references/type-venn.md +26 -0
  141. package/.agents/skills/diagram-design/references/type-wardley.md +39 -0
  142. package/.agents/skills/diagram-design/references/type-waterfall.md +75 -0
  143. package/.agents/skills/scrum-meta/SKILL.md +36 -36
  144. package/.agents/skills/scrum-planning/SKILL.md +61 -61
  145. package/.agents/skills/scrum-planning/references/schema.md +384 -336
  146. package/.agents/skills/scrum-report/SKILL.md +42 -42
  147. package/.agents/skills/scrum-sprint/SKILL.md +49 -49
  148. package/.agents/skills/scrum-tdd/SKILL.md +140 -127
  149. package/README.md +224 -201
  150. package/assets/board/200.html +1 -1
  151. package/assets/board/404.html +1 -1
  152. package/assets/board/_nuxt/1XlYaCp9.js +1 -0
  153. package/assets/board/_nuxt/4gQF_HS3.js +1 -0
  154. package/assets/board/_nuxt/5okfNy5D.js +1 -0
  155. package/assets/board/_nuxt/{Bb3Limos.js → B0C2jsdk.js} +1 -1
  156. package/assets/board/_nuxt/B1j6UC4U.js +1 -0
  157. package/assets/board/_nuxt/B3LKy6o2.js +1 -0
  158. package/assets/board/_nuxt/BAQQngAq.js +13 -0
  159. package/assets/board/_nuxt/BE6t7Q2m.js +1 -0
  160. package/assets/board/_nuxt/BLvbupFS.js +1 -0
  161. package/assets/board/_nuxt/BQgzP7ur.js +1 -0
  162. package/assets/board/_nuxt/BRZIF_wo.js +1 -0
  163. package/assets/board/_nuxt/BW3xJdEx.js +1 -0
  164. package/assets/board/_nuxt/{Cm9lkUvP.js → BY36DFyj.js} +1 -1
  165. package/assets/board/_nuxt/BZlZD94i.js +1 -0
  166. package/assets/board/_nuxt/BZvXNwev.js +3 -0
  167. package/assets/board/_nuxt/BbxmvdIF.js +1 -0
  168. package/assets/board/_nuxt/BiWrMOsL.js +1 -0
  169. package/assets/board/_nuxt/BizDeOK5.js +1 -0
  170. package/assets/board/_nuxt/BkcaOYQ_.js +95 -0
  171. package/assets/board/_nuxt/BkoKCNUY.js +1 -0
  172. package/assets/board/_nuxt/BlKmZhhF.js +1 -0
  173. package/assets/board/_nuxt/BmBpmU73.js +1 -0
  174. package/assets/board/_nuxt/BrWE0up6.js +1 -0
  175. package/assets/board/_nuxt/Bs52qkgk.js +1 -0
  176. package/assets/board/_nuxt/BtzUbHO_.js +1 -0
  177. package/assets/board/_nuxt/BuBzMkFZ.js +1 -0
  178. package/assets/board/_nuxt/Bubk0xn-.js +1 -0
  179. package/assets/board/_nuxt/BxuBMtk_.js +3 -0
  180. package/assets/board/_nuxt/By1KXXLR.js +1 -0
  181. package/assets/board/_nuxt/BygIGL69.js +2 -0
  182. package/assets/board/_nuxt/C1iO5KH3.js +1 -0
  183. package/assets/board/_nuxt/C2YPmTzt.js +1 -0
  184. package/assets/board/_nuxt/C3SpAhCW.js +1 -0
  185. package/assets/board/_nuxt/C3ZRWz5L.js +1 -0
  186. package/assets/board/_nuxt/C4WdPP6K.js +1 -0
  187. package/assets/board/_nuxt/C6o03JHe.js +1 -0
  188. package/assets/board/_nuxt/C8qB5llI.js +1 -0
  189. package/assets/board/_nuxt/C9MgRdhu.js +1 -0
  190. package/assets/board/_nuxt/CCA81-4P.js +1 -0
  191. package/assets/board/_nuxt/{D2O8Q8x9.js → CFCM18fF.js} +1 -1
  192. package/assets/board/_nuxt/CGXljzd_.js +1 -0
  193. package/assets/board/_nuxt/CI0scS-N.js +1 -0
  194. package/assets/board/_nuxt/CJs9B42o.js +1 -0
  195. package/assets/board/_nuxt/COecDWbb.js +1 -0
  196. package/assets/board/_nuxt/COmtTtfB.js +1 -0
  197. package/assets/board/_nuxt/CPYm4J_K.js +1 -0
  198. package/assets/board/_nuxt/CReA4TEB.js +3 -0
  199. package/assets/board/_nuxt/CXG7GZes.js +1 -0
  200. package/assets/board/_nuxt/CYGiZn-X.js +1 -0
  201. package/assets/board/_nuxt/CaE8VlUp.js +1 -0
  202. package/assets/board/_nuxt/CcGiNUgA.js +1 -0
  203. package/assets/board/_nuxt/Ccgv3lDj.js +1 -0
  204. package/assets/board/_nuxt/CdPGFZyy.js +1 -0
  205. package/assets/board/_nuxt/{DfR3IZN_.js → Cdf_T8Xa.js} +1 -1
  206. package/assets/board/_nuxt/CgQ30j_6.js +1 -0
  207. package/assets/board/_nuxt/ChUHb39z.js +1 -0
  208. package/assets/board/_nuxt/Ck0-FqVI.js +1 -0
  209. package/assets/board/_nuxt/CkWKLtjO.js +1 -0
  210. package/assets/board/_nuxt/Cq7hsCKB.js +1 -0
  211. package/assets/board/_nuxt/CtFknnHZ.js +1 -0
  212. package/assets/board/_nuxt/D166WVUG.js +1 -0
  213. package/assets/board/_nuxt/D4Dpzo9e.js +1 -0
  214. package/assets/board/_nuxt/D5i92OZO.js +1 -0
  215. package/assets/board/_nuxt/D6dUp4Nm.js +9 -0
  216. package/assets/board/_nuxt/DFQDFspC.js +1 -0
  217. package/assets/board/_nuxt/DHX2tw9J.js +1 -0
  218. package/assets/board/_nuxt/DJM7u8P3.js +1 -0
  219. package/assets/board/_nuxt/{BPTrRhcw.js → DLr85ebo.js} +1 -1
  220. package/assets/board/_nuxt/DOMM1wfl.js +1 -0
  221. package/assets/board/_nuxt/DU6ArlUN.js +1 -0
  222. package/assets/board/_nuxt/{B_SlFcWp.js → DUgGsde6.js} +1 -1
  223. package/assets/board/_nuxt/DV4ti-Pq.js +1 -0
  224. package/assets/board/_nuxt/DZ--fXuU.js +1 -0
  225. package/assets/board/_nuxt/D_ct-Stz.js +2 -0
  226. package/assets/board/_nuxt/DbWAComO.js +1 -0
  227. package/assets/board/_nuxt/DbwlcHAt.js +1 -0
  228. package/assets/board/_nuxt/DjzLR9Bl.js +1 -0
  229. package/assets/board/_nuxt/Dki9VkAL.js +3 -0
  230. package/assets/board/_nuxt/DlNJhOoc.js +1 -0
  231. package/assets/board/_nuxt/DqG7CtD4.js +1 -0
  232. package/assets/board/_nuxt/DqUSsCJ2.js +1 -0
  233. package/assets/board/_nuxt/Du2F4WXh.js +1 -0
  234. package/assets/board/_nuxt/DuRiEXn4.js +1 -0
  235. package/assets/board/_nuxt/DxBwVNyP.js +2 -0
  236. package/assets/board/_nuxt/DzmVW5-D.js +1 -0
  237. package/assets/board/_nuxt/I-sGtDIB.js +6 -0
  238. package/assets/board/_nuxt/JU9-x68N.js +1 -0
  239. package/assets/board/_nuxt/Lozm7hhE.js +1 -0
  240. package/assets/board/_nuxt/MMsTMkcB.js +1 -0
  241. package/assets/board/_nuxt/MS7CGmCB.js +1 -0
  242. package/assets/board/_nuxt/OKKaKILz.js +1 -0
  243. package/assets/board/_nuxt/{Csqgy925.js → P6qlvBHA.js} +1 -1
  244. package/assets/board/_nuxt/SrNUvNwO.js +1 -0
  245. package/assets/board/_nuxt/{DSaWoFHS.js → SywdkA98.js} +1 -1
  246. package/assets/board/_nuxt/TBIRpVLG.js +1 -0
  247. package/assets/board/_nuxt/TlTrMIMm.js +1 -0
  248. package/assets/board/_nuxt/Zv9-SOQF.js +1 -0
  249. package/assets/board/_nuxt/_id_.ItzoSaBL.css +1 -0
  250. package/assets/board/_nuxt/builds/latest.json +1 -1
  251. package/assets/board/_nuxt/builds/meta/2218461a-f45b-41d6-ae44-b0d2332a9511.json +1 -0
  252. package/assets/board/_nuxt/burndown.CT8fx1Ge.css +1 -0
  253. package/assets/board/_nuxt/diagrams.D1ybqBem.css +1 -0
  254. package/assets/board/_nuxt/{entry.CKHePurH.css → entry.BCuHiFgF.css} +1 -1
  255. package/assets/board/_nuxt/epics.CUeDIEa0.css +1 -0
  256. package/assets/board/_nuxt/error-404.Y9imKiDC.css +1 -0
  257. package/assets/board/_nuxt/error-500.DufSBGVC.css +1 -0
  258. package/assets/board/_nuxt/gantt.CiC2Ts1t.css +1 -0
  259. package/assets/board/_nuxt/jbri6q1p.js +1 -0
  260. package/assets/board/_nuxt/last-change.C05Vc3BF.css +1 -0
  261. package/assets/board/_nuxt/milestones.Civ3sMiQ.css +1 -0
  262. package/assets/board/_nuxt/oSJF4yHw.js +1 -0
  263. package/assets/board/_nuxt/pages.BshWbOyo.css +1 -0
  264. package/assets/board/_nuxt/q_8bxRMt.js +1 -0
  265. package/assets/board/_nuxt/tests.Ci9fBCka.css +1 -0
  266. package/assets/board/burndown/index.html +1 -1
  267. package/assets/board/diagrams/index.html +1 -0
  268. package/assets/board/epics/index.html +1 -1
  269. package/assets/board/favicon.svg +16 -16
  270. package/assets/board/gantt/index.html +1 -1
  271. package/assets/board/index.html +1 -1
  272. package/assets/board/last-change/index.html +1 -1
  273. package/assets/board/milestones/index.html +1 -1
  274. package/assets/board/tests/index.html +1 -1
  275. package/bin/scrum-skills.mjs +204 -191
  276. package/docs/ai-assistant-guide.md +1008 -1008
  277. package/package.json +42 -41
  278. package/templates/AGENTS.md +84 -74
  279. package/tools/agile-tools.mjs +510 -510
  280. package/tools/ai.mjs +464 -464
  281. package/tools/diagram-import.mjs +238 -0
  282. package/tools/diagram-lint.mjs +518 -0
  283. package/tools/diagram.mjs +299 -0
  284. package/tools/git-commit.mjs +216 -216
  285. package/tools/last-change.mjs +326 -326
  286. package/tools/projects.mjs +240 -215
  287. package/tools/serve.mjs +180 -154
  288. package/tools/validate.mjs +781 -664
  289. package/assets/board/_nuxt/1eqboJxR.js +0 -1
  290. package/assets/board/_nuxt/2K7ag6ES.js +0 -1
  291. package/assets/board/_nuxt/51OWHj8c.js +0 -2
  292. package/assets/board/_nuxt/6gFtTBwm.js +0 -1
  293. package/assets/board/_nuxt/B3jkABw9.js +0 -1
  294. package/assets/board/_nuxt/B5n2_jZN.js +0 -1
  295. package/assets/board/_nuxt/BDK2Hj7B.js +0 -1
  296. package/assets/board/_nuxt/BDcnzXHq.js +0 -1
  297. package/assets/board/_nuxt/BaG8y0Wt.js +0 -1
  298. package/assets/board/_nuxt/BbwJq5pb.js +0 -1
  299. package/assets/board/_nuxt/BgLeKwx0.js +0 -1
  300. package/assets/board/_nuxt/Bi9Axj3V.js +0 -1
  301. package/assets/board/_nuxt/BmxvtaMq.js +0 -1
  302. package/assets/board/_nuxt/BodFvto3.js +0 -1
  303. package/assets/board/_nuxt/BpWeHLuN.js +0 -1
  304. package/assets/board/_nuxt/BuOKDFiJ.js +0 -1
  305. package/assets/board/_nuxt/C40iTe-x.js +0 -1
  306. package/assets/board/_nuxt/C7K0R7F-.js +0 -2
  307. package/assets/board/_nuxt/C8vL692X.js +0 -1
  308. package/assets/board/_nuxt/C9StZL3D.js +0 -3
  309. package/assets/board/_nuxt/CGI_ZV79.js +0 -1
  310. package/assets/board/_nuxt/CJ2mz0WY.js +0 -1
  311. package/assets/board/_nuxt/CJMBJQuf.js +0 -1
  312. package/assets/board/_nuxt/CK-lOYG9.js +0 -1
  313. package/assets/board/_nuxt/CK9_veoz.js +0 -1
  314. package/assets/board/_nuxt/CLBcBrYf.js +0 -1
  315. package/assets/board/_nuxt/CMUi2-Lj.js +0 -1
  316. package/assets/board/_nuxt/COf6E6h4.js +0 -1
  317. package/assets/board/_nuxt/CPCUMJnF.js +0 -1
  318. package/assets/board/_nuxt/CPcKlmSm.js +0 -1
  319. package/assets/board/_nuxt/CQTvWh4i.js +0 -1
  320. package/assets/board/_nuxt/CSEEUF8S.js +0 -1
  321. package/assets/board/_nuxt/CSSb154I.js +0 -13
  322. package/assets/board/_nuxt/CgDTd7jS.js +0 -1
  323. package/assets/board/_nuxt/ChG76uh9.js +0 -1
  324. package/assets/board/_nuxt/Ci0qbkgh.js +0 -1
  325. package/assets/board/_nuxt/CqoQ__I_.js +0 -1
  326. package/assets/board/_nuxt/CuJUHLl8.js +0 -1
  327. package/assets/board/_nuxt/CvLiA4_O.js +0 -1
  328. package/assets/board/_nuxt/Cvs12oA0.js +0 -3
  329. package/assets/board/_nuxt/CyEXFe3V.js +0 -1
  330. package/assets/board/_nuxt/Cz6rZcLK.js +0 -1
  331. package/assets/board/_nuxt/D2FhHl1o.js +0 -1
  332. package/assets/board/_nuxt/D3YCAmfu.js +0 -1
  333. package/assets/board/_nuxt/D5CE81jK.js +0 -1
  334. package/assets/board/_nuxt/D75vbfSb.js +0 -1
  335. package/assets/board/_nuxt/D99LbGfb.js +0 -9
  336. package/assets/board/_nuxt/DEfLc7p3.js +0 -6
  337. package/assets/board/_nuxt/DF-nsa1Z.js +0 -1
  338. package/assets/board/_nuxt/DIc8yx98.js +0 -1
  339. package/assets/board/_nuxt/DLkAKotw.js +0 -1
  340. package/assets/board/_nuxt/DRPlYvb3.js +0 -1
  341. package/assets/board/_nuxt/DRlQgul3.js +0 -1
  342. package/assets/board/_nuxt/DSLbmoWa.js +0 -1
  343. package/assets/board/_nuxt/DWO9c9yp.js +0 -1
  344. package/assets/board/_nuxt/DZo3dYLJ.js +0 -3
  345. package/assets/board/_nuxt/D_S6N7_b.js +0 -1
  346. package/assets/board/_nuxt/DajA-Tkt.js +0 -1
  347. package/assets/board/_nuxt/DbHiyab-.js +0 -1
  348. package/assets/board/_nuxt/Dc7Ql6eE.js +0 -1
  349. package/assets/board/_nuxt/Dd9ZukPU.js +0 -1
  350. package/assets/board/_nuxt/Dg2LiqmN.js +0 -1
  351. package/assets/board/_nuxt/DhuleNnm.js +0 -1
  352. package/assets/board/_nuxt/DkI1Qxpp.js +0 -95
  353. package/assets/board/_nuxt/DnDHGSYI.js +0 -1
  354. package/assets/board/_nuxt/DpXJ_tCU.js +0 -1
  355. package/assets/board/_nuxt/DqZgF3hU.js +0 -1
  356. package/assets/board/_nuxt/DqcxvfbD.js +0 -1
  357. package/assets/board/_nuxt/DsA2lVci.js +0 -1
  358. package/assets/board/_nuxt/Dtsa-1NS.js +0 -1
  359. package/assets/board/_nuxt/Du91iq0p.js +0 -1
  360. package/assets/board/_nuxt/Dx1wcYFo.js +0 -1
  361. package/assets/board/_nuxt/EYTShbt4.js +0 -1
  362. package/assets/board/_nuxt/MaXJprwN.js +0 -1
  363. package/assets/board/_nuxt/P9x67aq2.js +0 -1
  364. package/assets/board/_nuxt/S08nFESZ.js +0 -3
  365. package/assets/board/_nuxt/V0uynWE2.js +0 -1
  366. package/assets/board/_nuxt/Vo4wMbnS.js +0 -1
  367. package/assets/board/_nuxt/ZVTqot-_.js +0 -1
  368. package/assets/board/_nuxt/ZngEd3HB.js +0 -1
  369. package/assets/board/_nuxt/_id_.B2NDKSlX.css +0 -1
  370. package/assets/board/_nuxt/assMu_Wn.js +0 -1
  371. package/assets/board/_nuxt/builds/meta/8e23aec9-5b67-4569-b8bf-d4f07e50ffdf.json +0 -1
  372. package/assets/board/_nuxt/burndown.Do6IbM6o.css +0 -1
  373. package/assets/board/_nuxt/epics.Dmessplu.css +0 -1
  374. package/assets/board/_nuxt/error-404.BFmXOnCT.css +0 -1
  375. package/assets/board/_nuxt/error-500.BZ-DdjQ6.css +0 -1
  376. package/assets/board/_nuxt/gantt.Dmp00GUo.css +0 -1
  377. package/assets/board/_nuxt/last-change.1Su4GrCN.css +0 -1
  378. package/assets/board/_nuxt/milestones.U0xKCsEo.css +0 -1
  379. package/assets/board/_nuxt/pages.Dbj2IVk8.css +0 -1
  380. package/assets/board/_nuxt/rlePnzpc.js +0 -1
  381. package/assets/board/_nuxt/tests.dXlUX16A.css +0 -1
  382. package/assets/board/_nuxt/uKzCKCTq.js +0 -1
  383. package/assets/board/_nuxt/vwxaNGfc.js +0 -1
  384. package/assets/board/_nuxt/yh6sf_D5.js +0 -1
@@ -0,0 +1,518 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 图例产物质检(零依赖,node >= 18)
4
+ *
5
+ * 这是 diagram-design 上游 `scripts/self_check.py` 的 Node 移植版。
6
+ * 上游那份是纯标准库、纯静态分析、不启浏览器的检查器,为了不让用户装 Python,
7
+ * 我们按它的判定口径重写一遍(同一套七组检查、同样「拿不准就跳过、绝不误报」的策略)。
8
+ * 上游是 MIT(© cathrynlavery),见 .agents/skills/diagram-design/LICENSE。
9
+ *
10
+ * 用法:
11
+ * node tools/diagram-lint.mjs <file...> [--offline] [--skill <dir>]
12
+ * 退出码: 0 通过, 1 有错误
13
+ */
14
+ import { readFileSync, existsSync } from 'node:fs';
15
+ import { dirname, join, resolve } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+
18
+ const HERE = dirname(fileURLToPath(import.meta.url));
19
+ const DEFAULT_SKILL = resolve(HERE, '..', '.agents/skills/diagram-design');
20
+
21
+ const UNSAFE_TAGS = new Set(['base', 'embed', 'object', 'iframe']);
22
+ const VOID_TAGS = new Set([
23
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
24
+ 'link', 'meta', 'param', 'source', 'track', 'wbr',
25
+ ]);
26
+ // 内容按原文处理、不当标签解析:style / script 里的 `>` 是合法的比较符号
27
+ const RAW_TEXT = new Set(['style', 'script', 'textarea', 'title']);
28
+ const UNPAINTED = new Set(['defs', 'symbol', 'marker', 'pattern', 'clippath', 'mask', 'foreignobject']);
29
+ const PAINTED = new Set(['rect', 'line', 'circle', 'ellipse', 'polygon', 'polyline', 'text']);
30
+ const MOTION_MODES = new Set(['none', 'reveal', 'step', 'loop']);
31
+ const MOTION_ACTIONS = ['play', 'pause', 'replay', 'prev', 'next'];
32
+ const REF_ATTRS = ['src', 'href', 'xlink:href', 'poster', 'srcset', 'action', 'formaction'];
33
+ const CSS_REF_ATTRS = ['style', 'fill', 'stroke', 'filter', 'clip-path', 'mask', 'marker-start', 'marker-mid', 'marker-end', 'cursor'];
34
+ /** viewBox 容差:和上游一致,抗浮点误差与描边外扩 */
35
+ const VIEWBOX_TOLERANCE = 2.0;
36
+
37
+ // ---------- 极简 HTML 解析 ----------
38
+
39
+ /** 找开标签的 `>`,跳过引号里的内容 */
40
+ function tagEnd(src, from) {
41
+ let quote = '';
42
+ for (let i = from; i < src.length; i++) {
43
+ const ch = src[i];
44
+ if (quote) { if (ch === quote) quote = ''; continue; }
45
+ if (ch === '"' || ch === "'") { quote = ch; continue; }
46
+ if (ch === '>') return i;
47
+ }
48
+ return -1;
49
+ }
50
+
51
+ const decodeEntities = s => s
52
+ .replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
53
+ .replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, ' ');
54
+
55
+ function parseAttrs(raw) {
56
+ const attrs = {};
57
+ const order = [];
58
+ const re = /([^\s"'>/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>`]+)))?/g;
59
+ let m;
60
+ while ((m = re.exec(raw))) {
61
+ const name = m[1].toLowerCase();
62
+ if (name === '/' || name === '') continue;
63
+ attrs[name] = decodeEntities(m[2] ?? m[3] ?? m[4] ?? '');
64
+ order.push(name);
65
+ }
66
+ return { attrs, order };
67
+ }
68
+
69
+ /**
70
+ * 解析成树。HTML 那套容错语义只覆盖我们需要的部分:
71
+ * 注释 / CDATA / 声明直接跳过,raw-text 元素整体吞掉,其余按栈配对。
72
+ * 末尾仍留在栈上的就是未闭合元素(对应上游的标签平衡检查)。
73
+ */
74
+ function parse(src) {
75
+ const root = { name: '#root', attrs: {}, order: [], children: [], parent: null, text: '' };
76
+ const stack = [root];
77
+ const unclosed = [];
78
+ let i = 0;
79
+ const lower = src.toLowerCase();
80
+
81
+ const pushText = t => { if (t.trim()) stack[stack.length - 1].text += t; };
82
+
83
+ while (i < src.length) {
84
+ const lt = src.indexOf('<', i);
85
+ if (lt === -1) { pushText(src.slice(i)); break; }
86
+ if (lt > i) pushText(src.slice(i, lt));
87
+
88
+ if (src.startsWith('<!--', lt)) {
89
+ const end = src.indexOf('-->', lt + 4);
90
+ i = end === -1 ? src.length : end + 3;
91
+ continue;
92
+ }
93
+ if (src.startsWith('<![CDATA[', lt)) {
94
+ const end = src.indexOf(']]>', lt + 9);
95
+ i = end === -1 ? src.length : end + 3;
96
+ continue;
97
+ }
98
+ if (src.startsWith('<!', lt)) {
99
+ const end = src.indexOf('>', lt + 2);
100
+ i = end === -1 ? src.length : end + 1;
101
+ continue;
102
+ }
103
+ if (src.startsWith('</', lt)) {
104
+ const end = src.indexOf('>', lt + 2);
105
+ if (end === -1) { i = src.length; break; }
106
+ const name = src.slice(lt + 2, end).trim().toLowerCase();
107
+ // 找到最近一个同名开标签再一起弹栈;中间没闭合的记下来
108
+ let hit = -1;
109
+ for (let k = stack.length - 1; k > 0; k--) if (stack[k].name === name) { hit = k; break; }
110
+ if (hit > 0) {
111
+ for (let k = stack.length - 1; k > hit; k--) unclosed.push(stack[k]);
112
+ stack.length = hit;
113
+ }
114
+ i = end + 1;
115
+ continue;
116
+ }
117
+
118
+ const end = tagEnd(src, lt + 1);
119
+ if (end === -1) { pushText(src.slice(lt)); break; }
120
+ const inner = src.slice(lt + 1, end);
121
+ const m = /^([a-zA-Z][^\s/>]*)/.exec(inner);
122
+ if (!m) { i = end + 1; continue; }
123
+ const name = m[1].toLowerCase();
124
+ const selfClose = /\/\s*$/.test(inner);
125
+ const { attrs, order } = parseAttrs(inner.slice(m[1].length));
126
+ const node = { name, attrs, order, children: [], parent: stack[stack.length - 1], text: '', selfClose };
127
+ stack[stack.length - 1].children.push(node);
128
+ i = end + 1;
129
+
130
+ if (selfClose || VOID_TAGS.has(name)) continue;
131
+ if (RAW_TEXT.has(name)) {
132
+ const close = lower.indexOf(`</${name}`, i);
133
+ if (close === -1) { node.text = src.slice(i); unclosed.push(node); i = src.length; break; }
134
+ node.text = src.slice(i, close);
135
+ const gt = src.indexOf('>', close);
136
+ i = gt === -1 ? src.length : gt + 1;
137
+ continue;
138
+ }
139
+ stack.push(node);
140
+ }
141
+ if (stack.length > 1) unclosed.push(...stack.slice(1));
142
+ return { root, unclosed };
143
+ }
144
+
145
+ const walk = (node, fn, parents = []) => {
146
+ for (const c of node.children) {
147
+ fn(c, parents);
148
+ walk(c, fn, [...parents, c]);
149
+ }
150
+ };
151
+
152
+ const elements = root => {
153
+ const out = [];
154
+ walk(root, n => out.push(n));
155
+ return out;
156
+ };
157
+
158
+ /** 解码 CSS 十六进制转义(`\75 rl(` → `url(`),防止绕过引用检查 */
159
+ const decodeCss = css => css
160
+ .replace(/\\([0-9a-fA-F]{1,6})\s?/g, (_, hex) => String.fromCodePoint(parseInt(hex, 16)))
161
+ .replace(/\\(.)/g, '$1');
162
+
163
+ const stripComments = s => s.replace(/<!--[\s\S]*?-->/g, '').replace(/\/\*[\s\S]*?\*\//g, '');
164
+
165
+ const isRemote = ref => /^(?:https?:)?\/\//i.test(ref) || /^[a-z][a-z0-9+.-]*:\/\//i.test(ref);
166
+
167
+ // ---------- 单文件校验 ----------
168
+
169
+ function checkFile(file, { offline, skillDir }) {
170
+ const errors = [];
171
+ const warns = [];
172
+ const err = m => errors.push(m);
173
+ const warn = m => warns.push(m);
174
+
175
+ const src = readFileSync(file, 'utf8');
176
+ const { root, unclosed } = parse(src);
177
+ const all = elements(root);
178
+
179
+ // ① 安全边界
180
+ for (const el of all) {
181
+ if (UNSAFE_TAGS.has(el.name)) err(`[unsafe] 禁用标签 <${el.name}>:产物必须是单文件,不许嵌外部文档`);
182
+ for (const key of Object.keys(el.attrs)) {
183
+ if (key.startsWith('on')) err(`[unsafe] <${el.name}> 上不许有事件属性 ${key}`);
184
+ if (key === 'srcdoc') err(`[unsafe] <${el.name}> 上不许有 srcdoc`);
185
+ }
186
+ }
187
+
188
+ // ② 文档完整性(截断 / 乱码 / 未闭合)
189
+ const noComments = stripComments(src).trim();
190
+ if (!/<\/html>\s*$/i.test(noComments)) err('[doc] 文档没有以 </html> 结尾,疑似被截断');
191
+ const bad = src.indexOf('\ufffd');
192
+ if (bad !== -1) {
193
+ const line = src.slice(0, bad).split('\n').length;
194
+ err(`[doc] 第 ${line} 行出现 U+FFFD 替换字符:多字节内容被截断了`);
195
+ }
196
+ const seen = new Set();
197
+ for (const el of unclosed) {
198
+ if (seen.has(el.name)) continue;
199
+ seen.add(el.name);
200
+ err(`[doc] <${el.name}> 未闭合(容器标签必须成对)`);
201
+ }
202
+
203
+ // ③ CSS 引用 + ④ 资源引用
204
+ const styleText = all.filter(e => e.name === 'style').map(e => e.text).join('\n');
205
+ const css = decodeCss(styleText);
206
+ if (/@import/i.test(css)) err('[css] 不许用 @import 引外部样式');
207
+ if (/image-set\s*\(/i.test(css)) err('[css] 不许用 image-set() 引外部图片');
208
+ if (/(?:https?:)?\/\//i.test(stripComments(css))) err('[css] 样式里出现远程地址:产物必须自包含');
209
+
210
+ const checkRef = (ref, where) => {
211
+ const v = ref.trim();
212
+ if (!v) return;
213
+ if (/^javascript:/i.test(v)) return err(`[ref] ${where} 使用了 javascript: 伪协议`);
214
+ if (/^data:text\/html/i.test(v)) return err(`[ref] ${where} 内嵌了 data:text/html`);
215
+ if (/^data:/i.test(v) && !/^data:image\//i.test(v)) return err(`[ref] ${where} 的 data: 只允许图片类型`);
216
+ if (isRemote(v)) return err(`[ref] ${where} 引用了远程地址 ${v.slice(0, 60)}:看板里跑在 sandbox iframe 中,外链会直接白屏`);
217
+ };
218
+
219
+ for (const el of all) {
220
+ for (const key of REF_ATTRS) {
221
+ const v = el.attrs[key];
222
+ if (!v) continue;
223
+ // Google Fonts 是唯一白名单;--offline 下连它也不许
224
+ if (el.name === 'link' && key === 'href') {
225
+ let u;
226
+ try { u = new URL(v); } catch { err(`[ref] <link> 的 href ${v} 不是合法地址`); continue; }
227
+ const ok = u.protocol === 'https:' && u.host === 'fonts.googleapis.com' && u.pathname.startsWith('/css2');
228
+ if (!ok) err(`[ref] <link rel=stylesheet> 只允许 https://fonts.googleapis.com/css2,实际 ${v}`);
229
+ else if (offline) err('[ref] --offline 模式下不许有任何网络请求(包括 Google Fonts)');
230
+ continue;
231
+ }
232
+ if (key === 'srcset') { v.split(',').forEach(part => checkRef(part.trim().split(/\s+/)[0], '<srcset>')); continue; }
233
+ checkRef(v, `<${el.name} ${key}>`);
234
+ }
235
+ for (const key of CSS_REF_ATTRS) {
236
+ const v = el.attrs[key];
237
+ if (!v) continue;
238
+ const decoded = decodeCss(v);
239
+ if (/@import/i.test(decoded)) err(`[css] ${key} 里出现 @import`);
240
+ for (const m of decoded.matchAll(/url\(\s*(['"]?)([^'")]+)\1\s*\)/gi)) {
241
+ const ref = m[2].trim();
242
+ if (!ref.startsWith('#')) checkRef(ref, `<${el.name} ${key}> 的 url()`);
243
+ }
244
+ if (key !== 'style' && !/^url\(/i.test(decoded) && isRemote(decoded)) checkRef(decoded, `<${el.name} ${key}>`);
245
+ }
246
+ }
247
+
248
+ // ⑤ SVG 无障碍契约
249
+ const svgs = all.filter(e => e.name === 'svg');
250
+ const accessible = svgs.filter(e => e.attrs['aria-hidden'] !== 'true');
251
+ if (!accessible.length) err('[svg] 至少需要一个可访问的 <svg>(aria-hidden 的不算)');
252
+ const ids = new Map();
253
+ for (const el of all) {
254
+ const id = el.attrs.id;
255
+ if (!id) continue;
256
+ if (ids.has(id)) err(`[svg] id "${id}" 重复:同一页多张图会互相串台`);
257
+ ids.set(id, el);
258
+ }
259
+ for (const svg of accessible) {
260
+ if (svg.attrs.role !== 'img') err('[svg] 可访问的 <svg> 必须带 role="img"');
261
+ const first = svg.children.find(c => c.name !== '#text');
262
+ if (!first || first.name !== 'title') err('[svg] <title> 必须是 <svg> 的第一个子元素');
263
+ const title = svg.children.find(c => c.name === 'title');
264
+ const desc = svg.children.find(c => c.name === 'desc');
265
+ if (!title) err('[svg] 缺少 <title>');
266
+ else {
267
+ if (!title.text.trim()) err('[svg] <title> 不能为空');
268
+ if (!title.attrs.id || ['title', 'desc'].includes(title.attrs.id)) err('[svg] <title> 的 id 必须带图表前缀(不能是裸的 title)');
269
+ }
270
+ if (desc) {
271
+ if (!desc.text.trim()) err('[svg] <desc> 不能为空');
272
+ if (!desc.attrs.id || ['title', 'desc'].includes(desc.attrs.id)) err('[svg] <desc> 的 id 必须带图表前缀(不能是裸的 desc)');
273
+ }
274
+ const labelled = svg.attrs['aria-labelledby'];
275
+ if (title && desc) {
276
+ const want = `${title.attrs.id} ${desc.attrs.id}`;
277
+ if (labelled !== want) err(`[svg] aria-labelledby 必须是「title 的 id 然后 desc 的 id」:期望 "${want}",实际 "${labelled ?? ''}"`);
278
+ } else if (labelled && title && labelled !== title.attrs.id) {
279
+ err('[svg] aria-labelledby 与 <title> 的 id 不一致');
280
+ }
281
+ }
282
+
283
+ // ⑥ viewBox 越界(静态近似:只认 translate,其余一律跳过)
284
+ const dynamicSelectors = [];
285
+ for (const m of stripComments(styleText).matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
286
+ const decl = m[2];
287
+ if (!/(transform|translate|rotate|scale|display|visibility|clip-path|mask|animation|offset)/i.test(decl)) continue;
288
+ const sel = m[1].split(',').map(s => s.trim().split(/\s+/).pop()).filter(Boolean);
289
+ dynamicSelectors.push(...sel);
290
+ }
291
+ const matchesDynamic = el => dynamicSelectors.some(sel => {
292
+ if (sel.startsWith('.')) return (el.attrs.class ?? '').split(/\s+/).includes(sel.slice(1));
293
+ if (sel.startsWith('#')) return el.attrs.id === sel.slice(1);
294
+ return el.name === sel.toLowerCase();
295
+ });
296
+ const isStatic = el => {
297
+ if (el.attrs.transform && !/^\s*translate\s*\(/i.test(el.attrs.transform)) return false;
298
+ const style = el.attrs.style ?? '';
299
+ if (/(transform|translate|rotate|scale|clip-path|mask|animation|offset)/i.test(style)) return false;
300
+ if (/display\s*:\s*none/i.test(style) || /visibility\s*:\s*hidden/i.test(style)) return false;
301
+ if (el.attrs['clip-path'] || el.attrs.mask) return false;
302
+ if (matchesDynamic(el)) return false;
303
+ return true;
304
+ };
305
+ const num = v => {
306
+ const n = Number.parseFloat(v);
307
+ return Number.isFinite(n) ? n : null;
308
+ };
309
+ const boxOf = (el, tx, ty) => {
310
+ const a = el.attrs;
311
+ const rect = (x, y, w, h) => ({ minX: tx + x, minY: ty + y, maxX: tx + x + w, maxY: ty + y + h });
312
+ switch (el.name) {
313
+ case 'rect': {
314
+ const x = num(a.x) ?? 0, y = num(a.y) ?? 0, w = num(a.width), h = num(a.height);
315
+ return w === null || h === null ? null : rect(x, y, w, h);
316
+ }
317
+ case 'line': {
318
+ const x1 = num(a.x1), y1 = num(a.y1), x2 = num(a.x2), y2 = num(a.y2);
319
+ if ([x1, y1, x2, y2].some(v => v === null)) return null;
320
+ return { minX: tx + Math.min(x1, x2), minY: ty + Math.min(y1, y2), maxX: tx + Math.max(x1, x2), maxY: ty + Math.max(y1, y2) };
321
+ }
322
+ case 'circle': {
323
+ const cx = num(a.cx), cy = num(a.cy), r = num(a.r);
324
+ if ([cx, cy, r].some(v => v === null)) return null;
325
+ return { minX: tx + cx - r, minY: ty + cy - r, maxX: tx + cx + r, maxY: ty + cy + r };
326
+ }
327
+ case 'ellipse': {
328
+ const cx = num(a.cx), cy = num(a.cy), rx = num(a.rx), ry = num(a.ry);
329
+ if ([cx, cy, rx, ry].some(v => v === null)) return null;
330
+ return { minX: tx + cx - rx, minY: ty + cy - ry, maxX: tx + cx + rx, maxY: ty + cy + ry };
331
+ }
332
+ case 'polygon': case 'polyline': {
333
+ const pts = (a.points ?? '').trim().split(/[\s,]+/).map(Number);
334
+ if (pts.length < 2 || pts.some(v => !Number.isFinite(v))) return null;
335
+ const xs = pts.filter((_, i) => i % 2 === 0), ys = pts.filter((_, i) => i % 2 === 1);
336
+ return { minX: tx + Math.min(...xs), minY: ty + Math.min(...ys), maxX: tx + Math.max(...xs), maxY: ty + Math.max(...ys) };
337
+ }
338
+ case 'text': {
339
+ // 文本不排版,只按锚点算(上游同样不算字体实际宽度)
340
+ const x = num(a.x), y = num(a.y);
341
+ if (x === null || y === null) return null;
342
+ return { minX: tx + x, minY: ty + y, maxX: tx + x, maxY: ty + y };
343
+ }
344
+ default: return null;
345
+ }
346
+ };
347
+
348
+ for (const svg of svgs) {
349
+ const vb = (svg.attrs.viewbox ?? '').trim().split(/[\s,]+/).map(Number);
350
+ if (vb.length !== 4 || vb.some(v => !Number.isFinite(v))) {
351
+ if (svg.attrs.viewbox !== undefined) err('[svg] viewBox 必须是 4 个数字');
352
+ continue;
353
+ }
354
+ const [minX, minY, w, h] = vb;
355
+ const maxX = minX + w, maxY = minY + h;
356
+
357
+ const visit = (el, tx, ty, skip) => {
358
+ const skipped = skip || !isStatic(el) || UNPAINTED.has(el.name);
359
+ let ntx = tx, nty = ty;
360
+ const tr = el.attrs.transform;
361
+ if (tr && /^\s*translate\s*\(/i.test(tr)) {
362
+ const nums = tr.replace(/^\s*translate\s*\(/i, '').replace(/\)\s*$/, '').split(/[\s,]+/).map(Number);
363
+ if (Number.isFinite(nums[0])) ntx += nums[0];
364
+ if (Number.isFinite(nums[1])) nty += nums[1];
365
+ }
366
+ if (!skipped && PAINTED.has(el.name)) {
367
+ const box = boxOf(el, ntx, nty);
368
+ if (box) {
369
+ const over = [
370
+ ['左', minX - box.minX], ['上', minY - box.minY],
371
+ ['右', box.maxX - maxX], ['下', box.maxY - maxY],
372
+ ].filter(([, v]) => v > VIEWBOX_TOLERANCE).sort((a, b) => b[1] - a[1]);
373
+ if (over.length) {
374
+ const [edge, amount] = over[0];
375
+ err(`[viewBox] <${el.name}> 超出 ${edge}边界 ${amount.toFixed(1)}px(容差 ${VIEWBOX_TOLERANCE}):渲染时会被裁掉`);
376
+ }
377
+ }
378
+ }
379
+ for (const child of el.children) visit(child, ntx, nty, skipped || (el.name === 'svg' && el !== svg));
380
+ };
381
+ for (const child of svg.children) visit(child, 0, 0, false);
382
+ }
383
+
384
+ // ⑦ 脚本与动画契约
385
+ const scripts = all.filter(e => e.name === 'script');
386
+ if (scripts.length > 1) err(`[script] 最多允许一个 <script>,实际 ${scripts.length} 个`);
387
+ let canonical = null;
388
+ const templateFile = join(skillDir, 'assets/template-motion.html');
389
+ if (existsSync(templateFile)) {
390
+ const tpl = readFileSync(templateFile, 'utf8');
391
+ const found = [...tpl.matchAll(/<script\b([^>]*)>([\s\S]*?)<\/script>/gi)];
392
+ if (found.length === 1) canonical = found[0][2].replace(/\r\n/g, '\n').trim();
393
+ else warn('[script] 模板 template-motion.html 里的 controller 不唯一,跳过逐字比对');
394
+ } else {
395
+ warn(`[script] 找不到 ${templateFile},跳过 controller 逐字比对(--skill 可指定 skill 目录)`);
396
+ }
397
+ for (const s of scripts) {
398
+ const keys = s.order.filter(k => k !== '/');
399
+ if (keys.length !== 1 || keys[0] !== 'data-diagram-controls' || (s.attrs[keys[0]] ?? '') !== '') {
400
+ err(`[script] <script> 的属性只允许一个 data-diagram-controls="",实际 ${keys.join(' ') || '(无)'}`);
401
+ }
402
+ if (canonical !== null && s.text.replace(/\r\n/g, '\n').trim() !== canonical) {
403
+ err('[script] 控制器脚本体与 template-motion.html 不一致:动画图必须逐字复制规范控制器');
404
+ }
405
+ }
406
+
407
+ const motionRoots = all.filter(e => 'data-motion-root' in e.attrs);
408
+ const motionItems = all.filter(e => 'data-motion-item' in e.attrs);
409
+ const controls = all.filter(e => 'data-motion-controls' in e.attrs);
410
+ const status = all.filter(e => 'data-motion-status' in e.attrs);
411
+
412
+ if (motionRoots.length || motionItems.length || scripts.length) {
413
+ if (motionRoots.length > 1) err(`[motion] data-motion-root 只能有一个,实际 ${motionRoots.length}`);
414
+ for (const rootEl of motionRoots) {
415
+ const mode = rootEl.attrs['data-motion-mode'] ?? '';
416
+ if (!MOTION_MODES.has(mode)) err(`[motion] data-motion-mode 必须是 ${[...MOTION_MODES].join('/')},实际 "${mode}"`);
417
+ const rawCount = rootEl.attrs['data-step-count'];
418
+ const minStep = mode === 'none' ? 0 : 1;
419
+ if (rawCount !== undefined && !/^\d+$/.test(rawCount)) {
420
+ err(`[motion] data-step-count 必须是十进制整数,实际 "${rawCount}"`);
421
+ } else if (rawCount !== undefined) {
422
+ const count = Number(rawCount);
423
+ if (count < minStep || count > 8) err(`[motion] data-step-count ${count} 越界:${mode} 模式允许 ${minStep}~8`);
424
+ }
425
+ if (motionItems.length > 12) err(`[motion] 动画分步元素最多 12 个,实际 ${motionItems.length}`);
426
+ const steps = new Map();
427
+ for (const item of motionItems) {
428
+ const step = item.attrs['data-step'];
429
+ if (step === undefined || !/^\d+$/.test(step)) err(`[motion] 每个 data-motion-item 都必须有十进制的 data-step`);
430
+ const decorative = item.attrs['aria-hidden'] === 'true';
431
+ if (decorative) {
432
+ if (item.attrs.focusable !== 'false') err('[motion] 装饰性分步元素必须同时带 aria-hidden="true" 与 focusable="false"');
433
+ } else {
434
+ const label = (item.attrs['aria-label'] ?? '').trim();
435
+ if (!label) err('[motion] 语义分步元素必须有非空的 aria-label(读屏要知道这一步是什么)');
436
+ else if (/^#[0-9a-f]{3,8}$/i.test(label)) err('[motion] aria-label 不能只有一个颜色值');
437
+ }
438
+ const style = item.attrs.style ?? '';
439
+ if (/display\s*:\s*none/i.test(style) || /visibility\s*:\s*hidden/i.test(style) || /opacity\s*:\s*0(?:[^\d.]|$)/i.test(style)) {
440
+ err('[motion] 分步元素不许在源码里隐藏:静态回退必须可见');
441
+ }
442
+ if (!decorative && step !== undefined) {
443
+ const n = Number(step);
444
+ if (!steps.has(n)) steps.set(n, 0);
445
+ steps.set(n, steps.get(n) + 1);
446
+ }
447
+ }
448
+ for (const [n, count] of steps) if (count > 2) err(`[motion] 第 ${n} 步最多 2 个语义元素,实际 ${count}`);
449
+
450
+ const controlled = mode === 'step' || scripts.length > 0;
451
+ if (mode === 'none' || mode === 'loop') {
452
+ if (scripts.length) err(`[motion] ${mode} 模式不许带脚本`);
453
+ if (controls.length) err(`[motion] ${mode} 模式不许带播放控件`);
454
+ if (status.length) err(`[motion] ${mode} 模式不许带 live status`);
455
+ }
456
+ if (controlled) {
457
+ if (controls.length !== 1) err(`[motion] 受控动画必须有且只有一个 data-motion-controls,实际 ${controls.length}`);
458
+ const actions = all.filter(e => e.attrs['data-motion-action']).map(e => e.attrs['data-motion-action']);
459
+ const missing = MOTION_ACTIONS.filter(a => !actions.includes(a));
460
+ if (missing.length) err(`[motion] 播放控件缺少 action:${missing.join('、')}`);
461
+ if (status.length !== 1) err(`[motion] 受控动画必须有且只有一个 data-motion-status`);
462
+ for (const st of status) {
463
+ if (st.attrs.role !== 'status' || st.attrs['aria-live'] !== 'polite' || st.attrs['aria-atomic'] !== 'true')
464
+ err('[motion] status 必须带 role="status" aria-live="polite" aria-atomic="true"');
465
+ let p = st.parent, inside = false;
466
+ while (p) { if ('data-motion-controls' in (p.attrs ?? {})) { inside = true; break; } p = p.parent; }
467
+ if (inside) err('[motion] status 必须放在播放控件之外');
468
+ }
469
+ if (!scripts.length) err('[motion] 受控动画必须有脚本(否则按钮点了没反应)');
470
+ }
471
+ }
472
+ if (scripts.length) {
473
+ if (!/prefers-reduced-motion\s*:\s*reduce/i.test(css)) err('[motion] 有脚本就必须有 prefers-reduced-motion: reduce 回退');
474
+ if (!/@media\s+print/i.test(css)) err('[motion] 有脚本就必须有 @media print 打印回退');
475
+ }
476
+ }
477
+
478
+ return { errors, warns };
479
+ }
480
+
481
+ // ---------- CLI ----------
482
+
483
+ const argv = process.argv.slice(2);
484
+ const files = [];
485
+ let offline = false;
486
+ let skillDir = DEFAULT_SKILL;
487
+ for (let i = 0; i < argv.length; i++) {
488
+ const a = argv[i];
489
+ if (a === '--offline') offline = true;
490
+ else if (a === '--skill') { skillDir = resolve(argv[++i] ?? DEFAULT_SKILL); }
491
+ else if (a.startsWith('--')) { console.error(`✘ 未知参数: ${a}`); process.exit(1); }
492
+ else files.push(a);
493
+ }
494
+ if (!files.length) {
495
+ console.error('用法: node tools/diagram-lint.mjs <file...> [--offline] [--skill <dir>]');
496
+ process.exit(1);
497
+ }
498
+
499
+ let failed = false;
500
+ for (const file of files) {
501
+ let out;
502
+ try {
503
+ out = checkFile(file, { offline, skillDir });
504
+ } catch (e) {
505
+ failed = true;
506
+ console.error(`✘ ${file}\n - [io] 读不了文件: ${e.message}`);
507
+ continue;
508
+ }
509
+ for (const w of out.warns) console.warn(`WARN ${file}: ${w}`);
510
+ if (out.errors.length) {
511
+ failed = true;
512
+ console.error(`✘ ${file} 未通过质检(${out.errors.length} 处):`);
513
+ for (const e of out.errors) console.error(` - ${e}`);
514
+ } else {
515
+ console.log(`OK ${file}`);
516
+ }
517
+ }
518
+ process.exit(failed ? 1 : 0);