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
@@ -1,215 +1,240 @@
1
- import { readFileSync, writeFileSync, mkdirSync, realpathSync } from 'node:fs';
2
- import { join, dirname } from 'node:path';
3
- import { homedir } from 'node:os';
4
- import { execFile, execFileSync } from 'node:child_process';
5
- import { promisify } from 'node:util';
6
- import { randomBytes } from 'node:crypto';
7
- import { fileURLToPath } from 'node:url';
8
- import { createAgileTools } from './agile-tools.mjs';
9
- import { checkLastChange, listLastChangeHistory, readLastChangeAt } from './last-change.mjs';
10
- import { commitAll, commitPreview } from './git-commit.mjs';
11
-
12
- const exec = promisify(execFile);
13
- const validator = fileURLToPath(new URL('./validate.mjs', import.meta.url));
14
- const defaultStore = join(process.env.APPDATA || join(homedir(), '.config'), 'scrum-skills', 'projects.json');
15
-
16
- export async function pickDirectory() {
17
- let command, args;
18
- if (process.platform === 'win32') {
19
- command = 'powershell.exe';
20
- const script = `
21
- [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
22
- Add-Type -TypeDefinition @'
23
- using System;
24
- using System.Runtime.InteropServices;
25
- public static class FolderPickerDpi {
26
- [DllImport("user32.dll")] private static extern IntPtr SetThreadDpiAwarenessContext(IntPtr context);
27
- [DllImport("user32.dll")] private static extern bool SetProcessDPIAware();
28
- public static void Enable() {
29
- try {
30
- if (SetThreadDpiAwarenessContext(new IntPtr(-4)) != IntPtr.Zero) return;
31
- if (SetThreadDpiAwarenessContext(new IntPtr(-3)) != IntPtr.Zero) return;
32
- } catch (EntryPointNotFoundException) {}
33
- SetProcessDPIAware();
34
- }
35
- }
36
- '@
37
- [FolderPickerDpi]::Enable()
38
- Add-Type -AssemblyName System.Windows.Forms
39
- [System.Windows.Forms.Application]::EnableVisualStyles()
40
- $d = New-Object System.Windows.Forms.FolderBrowserDialog
41
- $d.Description = '选择包含 agile 文件夹的项目根目录'
42
- $d.ShowNewFolderButton = $false
43
- try {
44
- if ($d.ShowDialog() -eq 'OK') { [Console]::Write($d.SelectedPath) }
45
- } finally { $d.Dispose() }
46
- `;
47
- args = ['-NoProfile', '-STA', '-EncodedCommand', Buffer.from(script, 'utf16le').toString('base64')];
48
- } else if (process.platform === 'darwin') {
49
- command = 'osascript';
50
- args = ['-e', 'try', '-e', 'POSIX path of (choose folder with prompt "选择包含 agile 的项目根目录")', '-e', 'on error number -128', '-e', 'return ""', '-e', 'end try'];
51
- } else {
52
- command = 'zenity';
53
- args = ['--file-selection', '--directory', '--title=选择包含 agile 的项目根目录'];
54
- }
55
- try {
56
- const { stdout } = await exec(command, args, { encoding: 'utf8', timeout: 300000, maxBuffer: 65536 });
57
- return stdout.trim() || null;
58
- } catch (e) {
59
- if (process.platform === 'linux' && e.code === 1) return null;
60
- throw new Error('无法打开系统目录选择框,请确认本机有图形桌面(Linux 需安装 zenity)。');
61
- }
62
- }
63
-
64
- /**
65
- * 故事状态单向车道。看板卡片的「下一步 / 退回」就按这条线走,
66
- * done 是终点、cancelled 是另一条终点,两者都不许再动。
67
- */
68
- export const STORY_FLOW = ['draft', 'ready', 'in-progress', 'review', 'done'];
69
-
70
- const ACTION_LABEL = { next: '推进到下一状态', prev: '退回上一状态', cancel: '标记为已取消' };
71
-
72
- /** 按动作算出目标状态;算不出来(已是终点 / 动作不认识)返回 null */
73
- export function statusAfter(from, action) {
74
- if (action === 'cancel') return ['draft', 'ready', 'in-progress', 'review'].includes(from) ? 'cancelled' : null;
75
- if (!STORY_FLOW.includes(from)) return null;
76
- const i = STORY_FLOW.indexOf(from);
77
- if (action === 'next') return STORY_FLOW[i + 1] ?? null;
78
- if (action === 'prev') return STORY_FLOW[i - 1] ?? null;
79
- return null;
80
- }
81
-
82
- function snapshot(root) {
83
- const read = file => JSON.parse(readFileSync(join(root, 'agile', file + '.json'), 'utf8'));
84
- // last-change.json 可选:还没交付过东西就没有它,看板按 null 显示空状态。
85
- // 这里吞掉解析异常,坏文件交给 validate 报,别让整个 /api/agile 挂掉。
86
- let lastChange = null;
87
- try {
88
- lastChange = read('last-change');
89
- } catch {}
90
- // tests.json 同样可选:没登记用例的项目就是空数组,页面给空状态。
91
- // 坏文件交给 validate 报,这里吞掉异常,别让整个 /api/agile 挂掉。
92
- let tests = null;
93
- try {
94
- tests = read('tests');
95
- } catch {}
96
- // 页面开头要说明「已提交才覆盖、未提交要合并」,所以状态跟着快照一起给出去
97
- const { state, advice, hash, date, subject } = checkLastChange(root);
98
- return {
99
- backlog: read('backlog'), sprints: read('sprints'), journal: read('journal'), lastChange, tests,
100
- meta: { root, servedAt: new Date().toISOString(), lastChangeGit: { state, advice, hash, date, subject } },
101
- };
102
- }
103
-
104
- function validate(root) {
105
- try {
106
- execFileSync(process.execPath, [validator, root], { encoding: 'utf8', timeout: 15000, maxBuffer: 1024 * 1024 });
107
- } catch (e) {
108
- throw new Error(`项目敏捷数据校验失败:${e.stdout || e.message}`);
109
- }
110
- }
111
-
112
- export function createProjects({ root, store = defaultStore, pick = pickDirectory }) {
113
- const token = randomBytes(24).toString('hex');
114
- let recent = [];
115
- try {
116
- const saved = JSON.parse(readFileSync(store, 'utf8'));
117
- if (Array.isArray(saved.recent)) recent = saved.recent.filter(x => typeof x === 'string').slice(0, 10);
118
- } catch {}
119
- // 启动目录优先:从哪个目录起服务,看板就打开哪个目录。
120
- // 最近列表只是「切回上次那个项目」的入口,不许顶替启动目录——
121
- // 否则在 A 项目里 serve,会悄悄打开上一次的 B 项目数据(JSON 看着像是 A 的,其实全错)。
122
- const initial = realpathSync(root);
123
- let current = initial;
124
- recent = [...new Set([current, ...recent])].slice(0, 10);
125
- let selecting = false;
126
- const list = () => ({ recent: [...recent], root: current, token });
127
- const read = (path = current) => {
128
- if (!recent.includes(path)) throw new Error('该项目未打开,请先选择文件夹。');
129
- return snapshot(path);
130
- };
131
- const open = async path => {
132
- const canonical = realpathSync(path);
133
- validate(canonical);
134
- const data = snapshot(canonical);
135
- const next = [canonical, ...recent.filter(x => x !== canonical)].slice(0, 10);
136
- mkdirSync(dirname(store), { recursive: true });
137
- writeFileSync(store, JSON.stringify({ recent: next }, null, 2) + '\n', { mode: 0o600 });
138
- recent = next;
139
- current = canonical;
140
- return { ...list(), data };
141
- };
142
- const select = async () => {
143
- if (selecting) throw new Error('目录选择框已打开,请先完成选择。');
144
- selecting = true;
145
- try {
146
- const selected = await pick();
147
- return selected ? await open(selected) : null;
148
- } finally { selecting = false; }
149
- };
150
- // 写盘复用 AI 助手那套「文本补丁 + 写后校验 + 失败回滚」,
151
- // 不另写一份:改一个状态只差一行字符,校验不过自动还原。
152
- const tools = createAgileTools({ root: () => current });
153
- const setStoryStatus = ({ id, action } = {}) => {
154
- if (!id) throw new Error('缺少故事 id');
155
- const { backlog } = snapshot(current);
156
- const story = (backlog.stories ?? []).find(s => s.id === id);
157
- if (!story) throw new Error(`找不到故事 ${id}`);
158
- const from = story.status;
159
- const to = statusAfter(from, action);
160
- if (!to) throw new Error(`${id} 当前是 ${from},没法${ACTION_LABEL[action] ?? `执行 ${action}`}`);
161
- const patches = [{ path: `stories.${id}.status`, value: to }];
162
- // 取消一条还被阻塞的故事:阻塞标记要一起摘掉。
163
- // 校验器不许「已取消却还标着 blocked」,不摘会整条改动作废。
164
- if (to === 'cancelled' && story.blocked) patches.push({ op: 'remove', path: `stories.${id}.blocked` });
165
- tools.patch({ file: 'backlog', patches, reason: `看板卡片操作:${from} → ${to}` });
166
- return { id, from, to };
167
- };
168
- /**
169
- * 「自动提交」:提交信息由看板按交付记录生成后整份带上来,这里只负责执行。
170
- * 生成器只有一份(apps/board/app/utils/commit-message.js)——复制按钮与自动提交用的是同一段文本,
171
- * 不会出现「复制的和提交的不一样」。tools/ 不 import 看板源码,发布包里也就不用带 apps/。
172
- */
173
- const commit = ({ message } = {}) => commitAll(current, message);
174
- return { list, read, open, select, setStoryStatus, commit };
175
- }
176
-
177
- export async function handleProjectApi(projects, { method, path, headers, body }) {
178
- const host = headers.host || '';
179
- const denied = () => ({ status: 403, body: { error: '仅允许本机同来源访问。' } });
180
- if (!/^(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/.test(host)) return denied();
181
- if (headers.origin && headers.origin !== `http://${host}`) return denied();
182
- if (headers['sec-fetch-site'] === 'cross-site') return denied();
183
- const url = new URL(path, `http://${host}`);
184
- try {
185
- if (method === 'GET' && url.pathname === '/api/projects') return { status: 200, body: projects.list() };
186
- if (method === 'GET' && url.pathname === '/api/agile') return { status: 200, body: projects.read(url.searchParams.get('root') || undefined) };
187
- // 交付说明的历史版本:只读,直接从 git 里取(不写文件,所以不需要 token)
188
- if (method === 'GET' && url.pathname === '/api/agile/last-change/history') {
189
- return { status: 200, body: listLastChangeHistory(projects.list().root) };
190
- }
191
- if (method === 'GET' && url.pathname === '/api/agile/last-change/revision') {
192
- return { status: 200, body: readLastChangeAt(projects.list().root, url.searchParams.get('hash')) };
193
- }
194
- // 自动提交前的预览(能不能提交、会带走哪些文件):只读,同样不要 token
195
- if (method === 'GET' && url.pathname === '/api/agile/git/status') {
196
- return { status: 200, body: commitPreview(projects.list().root) };
197
- }
198
- if (method !== 'POST') return { status: 405, body: { error: '不支持的请求方法。' } };
199
- if (headers['x-project-token'] !== projects.list().token) return denied();
200
- // 看板卡片的退回 / 下一步 / 取消:改的是当前打开项目的 backlog.json
201
- if (url.pathname === '/api/agile/story-status') {
202
- return { status: 200, body: projects.setStoryStatus({ id: body?.id, action: body?.action }) };
203
- }
204
- // 自动提交:真跑 git add -A + git commit(不 push)
205
- if (url.pathname === '/api/agile/git/commit') {
206
- return { status: 200, body: projects.commit({ message: body?.message }) };
207
- }
208
- if (url.pathname === '/api/projects/select') return { status: 200, body: await projects.select() };
209
- if (url.pathname === '/api/projects/open') {
210
- if (!projects.list().recent.includes(body?.root)) throw new Error('该项目不在最近列表中,请选择文件夹。');
211
- return { status: 200, body: await projects.open(body.root) };
212
- }
213
- return { status: 404, body: { error: '接口不存在。' } };
214
- } catch (e) { return { status: 400, body: { error: String(e.message) } }; }
215
- }
1
+ import { readFileSync, writeFileSync, mkdirSync, realpathSync, existsSync } from 'node:fs';
2
+ import { join, dirname, resolve, sep } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { execFile, execFileSync } from 'node:child_process';
5
+ import { promisify } from 'node:util';
6
+ import { randomBytes } from 'node:crypto';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { createAgileTools } from './agile-tools.mjs';
9
+ import { checkLastChange, listLastChangeHistory, readLastChangeAt } from './last-change.mjs';
10
+ import { commitAll, commitPreview } from './git-commit.mjs';
11
+
12
+ const exec = promisify(execFile);
13
+ const validator = fileURLToPath(new URL('./validate.mjs', import.meta.url));
14
+ const defaultStore = join(process.env.APPDATA || join(homedir(), '.config'), 'scrum-skills', 'projects.json');
15
+
16
+ export async function pickDirectory() {
17
+ let command, args;
18
+ if (process.platform === 'win32') {
19
+ command = 'powershell.exe';
20
+ const script = `
21
+ [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
22
+ Add-Type -TypeDefinition @'
23
+ using System;
24
+ using System.Runtime.InteropServices;
25
+ public static class FolderPickerDpi {
26
+ [DllImport("user32.dll")] private static extern IntPtr SetThreadDpiAwarenessContext(IntPtr context);
27
+ [DllImport("user32.dll")] private static extern bool SetProcessDPIAware();
28
+ public static void Enable() {
29
+ try {
30
+ if (SetThreadDpiAwarenessContext(new IntPtr(-4)) != IntPtr.Zero) return;
31
+ if (SetThreadDpiAwarenessContext(new IntPtr(-3)) != IntPtr.Zero) return;
32
+ } catch (EntryPointNotFoundException) {}
33
+ SetProcessDPIAware();
34
+ }
35
+ }
36
+ '@
37
+ [FolderPickerDpi]::Enable()
38
+ Add-Type -AssemblyName System.Windows.Forms
39
+ [System.Windows.Forms.Application]::EnableVisualStyles()
40
+ $d = New-Object System.Windows.Forms.FolderBrowserDialog
41
+ $d.Description = '选择包含 agile 文件夹的项目根目录'
42
+ $d.ShowNewFolderButton = $false
43
+ try {
44
+ if ($d.ShowDialog() -eq 'OK') { [Console]::Write($d.SelectedPath) }
45
+ } finally { $d.Dispose() }
46
+ `;
47
+ args = ['-NoProfile', '-STA', '-EncodedCommand', Buffer.from(script, 'utf16le').toString('base64')];
48
+ } else if (process.platform === 'darwin') {
49
+ command = 'osascript';
50
+ args = ['-e', 'try', '-e', 'POSIX path of (choose folder with prompt "选择包含 agile 的项目根目录")', '-e', 'on error number -128', '-e', 'return ""', '-e', 'end try'];
51
+ } else {
52
+ command = 'zenity';
53
+ args = ['--file-selection', '--directory', '--title=选择包含 agile 的项目根目录'];
54
+ }
55
+ try {
56
+ const { stdout } = await exec(command, args, { encoding: 'utf8', timeout: 300000, maxBuffer: 65536 });
57
+ return stdout.trim() || null;
58
+ } catch (e) {
59
+ if (process.platform === 'linux' && e.code === 1) return null;
60
+ throw new Error('无法打开系统目录选择框,请确认本机有图形桌面(Linux 需安装 zenity)。');
61
+ }
62
+ }
63
+
64
+ /**
65
+ * 故事状态单向车道。看板卡片的「下一步 / 退回」就按这条线走,
66
+ * done 是终点、cancelled 是另一条终点,两者都不许再动。
67
+ */
68
+ export const STORY_FLOW = ['draft', 'ready', 'in-progress', 'review', 'done'];
69
+
70
+ const ACTION_LABEL = { next: '推进到下一状态', prev: '退回上一状态', cancel: '标记为已取消' };
71
+
72
+ /** 按动作算出目标状态;算不出来(已是终点 / 动作不认识)返回 null */
73
+ export function statusAfter(from, action) {
74
+ if (action === 'cancel') return ['draft', 'ready', 'in-progress', 'review'].includes(from) ? 'cancelled' : null;
75
+ if (!STORY_FLOW.includes(from)) return null;
76
+ const i = STORY_FLOW.indexOf(from);
77
+ if (action === 'next') return STORY_FLOW[i + 1] ?? null;
78
+ if (action === 'prev') return STORY_FLOW[i - 1] ?? null;
79
+ return null;
80
+ }
81
+
82
+ function snapshot(root) {
83
+ const read = file => JSON.parse(readFileSync(join(root, 'agile', file + '.json'), 'utf8'));
84
+ // last-change.json 可选:还没交付过东西就没有它,看板按 null 显示空状态。
85
+ // 这里吞掉解析异常,坏文件交给 validate 报,别让整个 /api/agile 挂掉。
86
+ let lastChange = null;
87
+ try {
88
+ lastChange = read('last-change');
89
+ } catch {}
90
+ // tests.json 同样可选:没登记用例的项目就是空数组,页面给空状态。
91
+ // 坏文件交给 validate 报,这里吞掉异常,别让整个 /api/agile 挂掉。
92
+ let tests = null;
93
+ try {
94
+ tests = read('tests');
95
+ } catch {}
96
+ // diagrams.json 同样可选:没登记图例的项目就是空数组,/diagrams 页给空状态
97
+ let diagrams = null;
98
+ try {
99
+ diagrams = read('diagrams');
100
+ } catch {}
101
+ // 页面开头要说明「已提交才覆盖、未提交要合并」,所以状态跟着快照一起给出去
102
+ const { state, advice, hash, date, subject } = checkLastChange(root);
103
+ return {
104
+ backlog: read('backlog'), sprints: read('sprints'), journal: read('journal'), lastChange, tests, diagrams,
105
+ meta: { root, servedAt: new Date().toISOString(), lastChangeGit: { state, advice, hash, date, subject } },
106
+ };
107
+ }
108
+
109
+ function validate(root) {
110
+ try {
111
+ execFileSync(process.execPath, [validator, root], { encoding: 'utf8', timeout: 15000, maxBuffer: 1024 * 1024 });
112
+ } catch (e) {
113
+ throw new Error(`项目敏捷数据校验失败:${e.stdout || e.message}`);
114
+ }
115
+ }
116
+
117
+ export function createProjects({ root, store = defaultStore, pick = pickDirectory }) {
118
+ const token = randomBytes(24).toString('hex');
119
+ let recent = [];
120
+ try {
121
+ const saved = JSON.parse(readFileSync(store, 'utf8'));
122
+ if (Array.isArray(saved.recent)) recent = saved.recent.filter(x => typeof x === 'string').slice(0, 10);
123
+ } catch {}
124
+ // 启动目录优先:从哪个目录起服务,看板就打开哪个目录。
125
+ // 最近列表只是「切回上次那个项目」的入口,不许顶替启动目录——
126
+ // 否则在 A 项目里 serve,会悄悄打开上一次的 B 项目数据(JSON 看着像是 A 的,其实全错)。
127
+ const initial = realpathSync(root);
128
+ let current = initial;
129
+ recent = [...new Set([current, ...recent])].slice(0, 10);
130
+ let selecting = false;
131
+ const list = () => ({ recent: [...recent], root: current, token });
132
+ const read = (path = current) => {
133
+ if (!recent.includes(path)) throw new Error('该项目未打开,请先选择文件夹。');
134
+ return snapshot(path);
135
+ };
136
+ const open = async path => {
137
+ const canonical = realpathSync(path);
138
+ validate(canonical);
139
+ const data = snapshot(canonical);
140
+ const next = [canonical, ...recent.filter(x => x !== canonical)].slice(0, 10);
141
+ mkdirSync(dirname(store), { recursive: true });
142
+ writeFileSync(store, JSON.stringify({ recent: next }, null, 2) + '\n', { mode: 0o600 });
143
+ recent = next;
144
+ current = canonical;
145
+ return { ...list(), data };
146
+ };
147
+ const select = async () => {
148
+ if (selecting) throw new Error('目录选择框已打开,请先完成选择。');
149
+ selecting = true;
150
+ try {
151
+ const selected = await pick();
152
+ return selected ? await open(selected) : null;
153
+ } finally { selecting = false; }
154
+ };
155
+ // 写盘复用 AI 助手那套「文本补丁 + 写后校验 + 失败回滚」,
156
+ // 不另写一份:改一个状态只差一行字符,校验不过自动还原。
157
+ const tools = createAgileTools({ root: () => current });
158
+ const setStoryStatus = ({ id, action } = {}) => {
159
+ if (!id) throw new Error('缺少故事 id');
160
+ const { backlog } = snapshot(current);
161
+ const story = (backlog.stories ?? []).find(s => s.id === id);
162
+ if (!story) throw new Error(`找不到故事 ${id}`);
163
+ const from = story.status;
164
+ const to = statusAfter(from, action);
165
+ if (!to) throw new Error(`${id} 当前是 ${from},没法${ACTION_LABEL[action] ?? `执行 ${action}`}`);
166
+ const patches = [{ path: `stories.${id}.status`, value: to }];
167
+ // 取消一条还被阻塞的故事:阻塞标记要一起摘掉。
168
+ // 校验器不许「已取消却还标着 blocked」,不摘会整条改动作废。
169
+ if (to === 'cancelled' && story.blocked) patches.push({ op: 'remove', path: `stories.${id}.blocked` });
170
+ tools.patch({ file: 'backlog', patches, reason: `看板卡片操作:${from} → ${to}` });
171
+ return { id, from, to };
172
+ };
173
+ /**
174
+ * 「自动提交」:提交信息由看板按交付记录生成后整份带上来,这里只负责执行。
175
+ * 生成器只有一份(apps/board/app/utils/commit-message.js)——复制按钮与自动提交用的是同一段文本,
176
+ * 不会出现「复制的和提交的不一样」。tools/ 不 import 看板源码,发布包里也就不用带 apps/。
177
+ */
178
+ const commit = ({ message } = {}) => commitAll(current, message);
179
+ /**
180
+ * 图例产物(自包含 HTML / SVG)。**只认 id,不认路径**:
181
+ * 路径一律从索引里取,客户端传什么都不会穿越目录;即便索引被手改成 `../../etc/passwd`,
182
+ * 这里还会再核一次「必须在 agile/ 下」。
183
+ */
184
+ const readDiagram = (id, format = 'html') => {
185
+ if (!id) throw new Error('缺少图例 id');
186
+ const d = (snapshot(current).diagrams?.diagrams ?? []).find(x => x.id === id);
187
+ if (!d) throw new Error(`没有图例 ${id}(先用 node tools/diagram.mjs add 登记)`);
188
+ const rel = format === 'svg' && d.svg ? d.svg : d.file;
189
+ if (!rel) throw new Error(`图例 ${id} 没有产物路径`);
190
+ const abs = resolve(current, rel);
191
+ const base = join(current, 'agile');
192
+ if (abs !== base && !abs.startsWith(base + sep)) throw new Error(`图例 ${id} 的产物必须在 agile/ 下:${rel}`);
193
+ if (!existsSync(abs)) throw new Error(`图例 ${id} 的产物不存在:${rel}`);
194
+ return {
195
+ contentType: /\.svg$/i.test(abs) ? 'image/svg+xml; charset=utf-8' : 'text/html; charset=utf-8',
196
+ text: readFileSync(abs, 'utf8'),
197
+ };
198
+ };
199
+ return { list, read, open, select, setStoryStatus, commit, readDiagram };
200
+ }
201
+
202
+ export async function handleProjectApi(projects, { method, path, headers, body }) {
203
+ const host = headers.host || '';
204
+ const denied = () => ({ status: 403, body: { error: '仅允许本机同来源访问。' } });
205
+ if (!/^(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/.test(host)) return denied();
206
+ if (headers.origin && headers.origin !== `http://${host}`) return denied();
207
+ if (headers['sec-fetch-site'] === 'cross-site') return denied();
208
+ const url = new URL(path, `http://${host}`);
209
+ try {
210
+ if (method === 'GET' && url.pathname === '/api/projects') return { status: 200, body: projects.list() };
211
+ if (method === 'GET' && url.pathname === '/api/agile') return { status: 200, body: projects.read(url.searchParams.get('root') || undefined) };
212
+ // 交付说明的历史版本:只读,直接从 git 里取(不写文件,所以不需要 token)
213
+ if (method === 'GET' && url.pathname === '/api/agile/last-change/history') {
214
+ return { status: 200, body: listLastChangeHistory(projects.list().root) };
215
+ }
216
+ if (method === 'GET' && url.pathname === '/api/agile/last-change/revision') {
217
+ return { status: 200, body: readLastChangeAt(projects.list().root, url.searchParams.get('hash')) };
218
+ }
219
+ // 自动提交前的预览(能不能提交、会带走哪些文件):只读,同样不要 token
220
+ if (method === 'GET' && url.pathname === '/api/agile/git/status') {
221
+ return { status: 200, body: commitPreview(projects.list().root) };
222
+ }
223
+ if (method !== 'POST') return { status: 405, body: { error: '不支持的请求方法。' } };
224
+ if (headers['x-project-token'] !== projects.list().token) return denied();
225
+ // 看板卡片的退回 / 下一步 / 取消:改的是当前打开项目的 backlog.json
226
+ if (url.pathname === '/api/agile/story-status') {
227
+ return { status: 200, body: projects.setStoryStatus({ id: body?.id, action: body?.action }) };
228
+ }
229
+ // 自动提交:真跑 git add -A + git commit(不 push)
230
+ if (url.pathname === '/api/agile/git/commit') {
231
+ return { status: 200, body: projects.commit({ message: body?.message }) };
232
+ }
233
+ if (url.pathname === '/api/projects/select') return { status: 200, body: await projects.select() };
234
+ if (url.pathname === '/api/projects/open') {
235
+ if (!projects.list().recent.includes(body?.root)) throw new Error('该项目不在最近列表中,请选择文件夹。');
236
+ return { status: 200, body: await projects.open(body.root) };
237
+ }
238
+ return { status: 404, body: { error: '接口不存在。' } };
239
+ } catch (e) { return { status: 400, body: { error: String(e.message) } }; }
240
+ }