@xiaobuyu/nodesign 0.0.7 → 0.0.8

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 (353) hide show
  1. package/README.md +266 -79
  2. package/README.zh-CN.md +254 -67
  3. package/package.json +2 -2
  4. package/server/api/admin.js +7 -0
  5. package/server/api/assets/docx-page.js +5 -4
  6. package/server/api/assets/helpers.js +8 -0
  7. package/server/api/assets.js +21 -33
  8. package/server/api/board.js +29 -1
  9. package/server/api/browse.js +3 -2
  10. package/server/api/chatai.js +9 -8
  11. package/server/api/exports/cards.js +7 -6
  12. package/server/api/exports/docx-pdf.js +4 -1
  13. package/server/api/local.js +10 -8
  14. package/server/api/projects.js +19 -3
  15. package/server/api/publish.js +3 -2
  16. package/server/api/sessions-rewind.js +220 -0
  17. package/server/api/sessions.js +58 -194
  18. package/server/api/stage.js +35 -0
  19. package/server/api/turn-model-switch.js +109 -0
  20. package/server/api/turn.js +52 -88
  21. package/server/auth/middleware.js +29 -6
  22. package/server/auth/tier.js +27 -7
  23. package/server/auth/users-store.js +14 -1
  24. package/server/engine/agent/actor-trail.js +110 -0
  25. package/server/engine/agent/agent-shared.js +22 -52
  26. package/server/engine/agent/beat-state.js +61 -0
  27. package/server/engine/agent/cast.js +322 -0
  28. package/server/engine/agent/events.js +9 -2
  29. package/server/engine/agent/hooks/beat-gate.js +73 -0
  30. package/server/engine/agent/hooks/lifecycle.js +6 -2
  31. package/server/engine/agent/hooks/post-trim.js +1 -0
  32. package/server/engine/agent/hooks/pre-board-dirty.js +29 -0
  33. package/server/engine/agent/hooks/pre-defaults.js +206 -2
  34. package/server/engine/agent/hooks/pre-injectors.js +19 -2
  35. package/server/engine/agent/hooks/pre-peer-guard.js +88 -0
  36. package/server/engine/agent/hooks/pre-unknown-params.js +82 -0
  37. package/server/engine/agent/hooks/pre-workspace-scope-guard.js +33 -0
  38. package/server/engine/agent/hooks/resident-role-lifecycle.js +100 -0
  39. package/server/engine/agent/hooks/slot-alias.js +30 -0
  40. package/server/engine/agent/hooks/user-prompt-submit.js +197 -7
  41. package/server/engine/agent/hooks.js +76 -6
  42. package/server/engine/agent/isolation.js +25 -2
  43. package/server/engine/agent/memory-config.js +27 -1
  44. package/server/engine/agent/model-context.js +102 -18
  45. package/server/engine/agent/model-table.js +361 -58
  46. package/server/engine/agent/prompts/nodesign-prelude.md +566 -59
  47. package/server/engine/agent/prompts/tools/direct-edit-protocol.md +1 -1
  48. package/server/engine/agent/prompts/tools/generate-image-cookbook.md +2 -2
  49. package/server/engine/agent/prompts/tools/paint-still-cookbook.md +1 -1
  50. package/server/engine/agent/prompts/tools/site-reference.md +2 -1
  51. package/server/engine/agent/role-card.js +175 -0
  52. package/server/engine/agent/rp-mode.js +17 -0
  53. package/server/engine/agent/session-loop.js +26 -14
  54. package/server/engine/agent/stage-status.js +73 -0
  55. package/server/engine/agent/subagent-flight.js +73 -0
  56. package/server/engine/agent/system-prompts.js +41 -0
  57. package/server/engine/agent/tool-input-stream.js +165 -0
  58. package/server/engine/mcp/actor.js +59 -0
  59. package/server/engine/mcp/capability-gate.js +21 -2
  60. package/server/engine/mcp/index.js +116 -24
  61. package/server/engine/mcp/mode-profile.js +139 -0
  62. package/server/engine/mcp/server-name.js +19 -0
  63. package/server/engine/mcp/tools/artifact-session.js +2 -1
  64. package/server/engine/mcp/tools/browse-find-batch.js +76 -14
  65. package/server/engine/mcp/tools/browse.js +1 -1
  66. package/server/engine/mcp/tools/cast-role.js +167 -0
  67. package/server/engine/mcp/tools/draw-trend.js +104 -0
  68. package/server/engine/mcp/tools/edit-board-schema.js +56 -0
  69. package/server/engine/mcp/tools/edit-board.js +569 -0
  70. package/server/engine/mcp/tools/look-at-board.js +4 -3
  71. package/server/engine/mcp/tools/open-sheet.js +242 -0
  72. package/server/engine/mcp/tools/organize-board.js +19 -5
  73. package/server/engine/mcp/tools/paint-still.js +1 -1
  74. package/server/engine/mcp/tools/pin-to-board.js +78 -3
  75. package/server/engine/mcp/tools/read-board.js +109 -16
  76. package/server/engine/mcp/tools/read-tavern-json.js +87 -19
  77. package/server/engine/mcp/tools/report-issue.js +1 -0
  78. package/server/engine/mcp/tools/role-memory.js +83 -0
  79. package/server/engine/mcp/tools/roll-dice.js +82 -0
  80. package/server/engine/mcp/tools/roll-film.js +1 -1
  81. package/server/engine/mcp/tools/screenshot.js +2 -2
  82. package/server/engine/mcp/tools/set-vars.js +150 -0
  83. package/server/engine/mcp/tools/sheet-replan.js +46 -0
  84. package/server/engine/mcp/tools/web-search.js +1 -1
  85. package/server/engine/mcp/tools/write-on-board-flow.js +108 -0
  86. package/server/engine/mcp/tools/write-on-board-place.js +211 -0
  87. package/server/engine/mcp/tools/write-on-board-role-anchor.js +48 -0
  88. package/server/engine/mcp/tools/write-on-board-schema.js +125 -0
  89. package/server/engine/mcp/tools/write-on-board.js +567 -107
  90. package/server/engine/plugins/nodesign/skills/blackboard-rp/SKILL.md +215 -0
  91. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +11 -3
  92. package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +34 -4
  93. package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +20 -4
  94. package/server/engine/plugins/nodesign/skills/story-craft/SKILL.md +94 -0
  95. package/server/engine/plugins/nodesign/skills/story-import/SKILL.md +112 -0
  96. package/server/engine/plugins/nodesign/skills/story-intimacy/SKILL.md +72 -0
  97. package/server/engine/plugins/nodesign/skills/story-voice/SKILL.md +66 -0
  98. package/server/engine/plugins/nodesign/skills/story-voice/voices//345/206/267/347/241/254.md +33 -0
  99. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/203/205/346/204/237/347/233/264/347/273/231.md +42 -0
  100. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/204/217/350/257/206/346/265/201.md +42 -0
  101. package/server/engine/plugins/nodesign/skills/story-voice/voices//346/255/246/344/276/240/347/231/275/350/257/235.md +34 -0
  102. package/server/engine/plugins/nodesign/skills/story-voice/voices//347/224/237/346/264/273/346/265/201.md +35 -0
  103. package/server/engine/plugins/nodesign/skills/story-voice/voices//347/231/275/346/217/217.md +37 -0
  104. package/server/engine/plugins/nodesign/skills/story-voice/voices//350/210/236/345/217/260/345/211/247.md +32 -0
  105. package/server/engine/plugins/nodesign/skills/story-voice/voices//351/230/266/346/256/265/345/210/207/346/215/242.md +42 -0
  106. package/server/engine/runs/board-seater.js +272 -0
  107. package/server/engine/runs/board-tasklist.js +90 -48
  108. package/server/engine/runs/turn-relay.js +24 -2
  109. package/server/engine/runs/user-chalk-echo.js +44 -0
  110. package/server/index.js +2 -0
  111. package/server/lib/board-anchor.js +49 -0
  112. package/server/lib/board-dirty.js +93 -0
  113. package/server/lib/board-excerpt.js +80 -0
  114. package/server/lib/board-follow.js +111 -0
  115. package/server/lib/board-groups.js +1 -5
  116. package/server/lib/board-hero.js +3 -1
  117. package/server/lib/board-kind-sizes.js +36 -0
  118. package/server/lib/board-lanes.js +58 -0
  119. package/server/lib/board-obstacles.js +80 -0
  120. package/server/lib/board-relations.js +5 -1
  121. package/server/lib/board-sheets.js +467 -0
  122. package/server/lib/board-shelf.js +86 -0
  123. package/server/lib/board-transform.js +62 -0
  124. package/server/lib/canvas-id.js +18 -1
  125. package/server/lib/chalk-flow.js +88 -0
  126. package/server/lib/chalk-rewrite.js +63 -0
  127. package/server/lib/chalk-size-pref.js +48 -0
  128. package/server/lib/chalk.js +42 -3
  129. package/server/lib/ingress/forward-openai-chat.js +16 -4
  130. package/server/lib/ingress/openai-chat.js +23 -12
  131. package/server/lib/ingress/session-routes.js +3 -3
  132. package/server/lib/ingress/stream-watchdog.js +53 -0
  133. package/server/lib/ingress/upstream-billing.js +15 -0
  134. package/server/lib/ingress/upstream-error-hints.js +36 -0
  135. package/server/lib/json-preview.js +58 -0
  136. package/server/lib/model-ingress.js +26 -1
  137. package/server/lib/moderation.js +10 -5
  138. package/server/lib/quota.js +26 -6
  139. package/server/lib/rect.js +50 -0
  140. package/server/lib/screen.js +36 -0
  141. package/server/lib/sheet-state.js +19 -0
  142. package/server/lib/sketch-array.js +118 -0
  143. package/server/lib/sketch-hatch.js +99 -0
  144. package/server/lib/sketch-layout.js +269 -69
  145. package/server/lib/sketch-shapes.js +152 -0
  146. package/server/lib/sketch-stencils.js +170 -0
  147. package/server/lib/state-table.js +237 -0
  148. package/server/lib/state-trend.js +94 -0
  149. package/server/lib/state-triggers.js +199 -0
  150. package/server/lib/tavern-json.js +48 -0
  151. package/server/lib/user-uploads.js +88 -0
  152. package/server/projects/board-limits.js +5 -3
  153. package/server/projects/board-sanitize.js +220 -10
  154. package/server/projects/board-store.js +95 -6
  155. package/server/projects/session-jsonl.js +110 -0
  156. package/server/projects/store.js +56 -3
  157. package/server/projects/viewpoint-store.js +35 -3
  158. package/server/projects/workspace-slots.js +30 -0
  159. package/server/projects/workspace-templates.js +29 -0
  160. package/server/projects/workspace.js +16 -17
  161. package/server/runtime/capabilities.js +6 -2
  162. package/server/shared/locales.js +70 -0
  163. package/server/shared/messages-en.js +69 -0
  164. package/server/shared/messages.js +65 -0
  165. package/server/ws/broker.js +3 -0
  166. package/server/ws/index.js +43 -3
  167. package/web/README.md +1 -1
  168. package/web/dist/assets/{BrowserWindow-BoOBaIyz.js → BrowserWindow-CcaGRWz5.js} +1 -1
  169. package/web/dist/assets/DeckWindow-BIr2KGFT.js +6 -0
  170. package/web/dist/assets/{DocxWindow-B_u6aX9o.js → DocxWindow-B9KZmU5z.js} +2 -2
  171. package/web/dist/assets/{MermaidBlock-BO-46Du5.js → MermaidBlock-DrfRnz8E.js} +43 -43
  172. package/web/dist/assets/{SiteWindow-ConPmzdM.js → SiteWindow-CJOjCF7d.js} +6 -6
  173. package/web/dist/assets/{abnfDiagram-VCTEODGH-FCO9yf-n.js → abnfDiagram-VCTEODGH-CUZbQGIg.js} +1 -1
  174. package/web/dist/assets/{arc-BLcEOSRX.js → arc-C1-HSUny.js} +1 -1
  175. package/web/dist/assets/{architectureDiagram-5GKGNRK7-kxiX4TeN.js → architectureDiagram-5GKGNRK7-B5nwvu60.js} +2 -2
  176. package/web/dist/assets/{blockDiagram-NRAW4CY4-CptFksix.js → blockDiagram-NRAW4CY4-uPPuFLwR.js} +1 -1
  177. package/web/dist/assets/{c4Diagram-UCG6FXSJ-Dxmxj34F.js → c4Diagram-UCG6FXSJ-C0mKBFlz.js} +1 -1
  178. package/web/dist/assets/channel-BxpWNOGU.js +1 -0
  179. package/web/dist/assets/{chunk-2Q5K7J3B-D9inx5oo.js → chunk-2Q5K7J3B-Cs0rlZ4q.js} +1 -1
  180. package/web/dist/assets/{chunk-5VM5RSS4-DydfHlEc.js → chunk-5VM5RSS4-DtR63oYb.js} +1 -1
  181. package/web/dist/assets/{chunk-F27PBJKO-BO7GxhSv.js → chunk-F27PBJKO-B3MRdezJ.js} +1 -1
  182. package/web/dist/assets/{chunk-G27WJ6UU-BzP6YiQ_.js → chunk-G27WJ6UU-BtwntwpS.js} +1 -1
  183. package/web/dist/assets/{chunk-JWPE2WC7-CHcgcd0s.js → chunk-JWPE2WC7-BHsTe5PD.js} +1 -1
  184. package/web/dist/assets/{chunk-LCL6LL3I-D2Wg5JtX.js → chunk-LCL6LL3I-knY15Qdm.js} +1 -1
  185. package/web/dist/assets/{chunk-POPQ4Y6H-B2fCRfpM.js → chunk-POPQ4Y6H-C51tgK_I.js} +1 -1
  186. package/web/dist/assets/{chunk-SVP7TREG-JSLcsRlZ.js → chunk-SVP7TREG-CnwRZmF_.js} +1 -1
  187. package/web/dist/assets/{chunk-XXDRQBXY-BCjvaeHm.js → chunk-XXDRQBXY-BtXzVF-_.js} +1 -1
  188. package/web/dist/assets/classDiagram-DTDB5LWJ-DUEKmDfo.js +1 -0
  189. package/web/dist/assets/classDiagram-v2-JRS7N3AN-DUEKmDfo.js +1 -0
  190. package/web/dist/assets/{cose-bilkent-JH36ORCC-0uKv6oJ7.js → cose-bilkent-JH36ORCC-CzWlGP6i.js} +1 -1
  191. package/web/dist/assets/{cynefin-OW5HDTMX-Bvt-gEmA.js → cynefin-OW5HDTMX-vK64EGSv.js} +1 -1
  192. package/web/dist/assets/{cynefinDiagram-5FMLGOSQ-C9Ht02qC.js → cynefinDiagram-5FMLGOSQ-Bs3g1Gvh.js} +1 -1
  193. package/web/dist/assets/{dagre-3AP2YEHR-BfxZny4s.js → dagre-3AP2YEHR-BhgkYe8V.js} +1 -1
  194. package/web/dist/assets/{diagram-S7CK7UJ4-CrE0ZETE.js → diagram-S7CK7UJ4-C_6mvD2V.js} +1 -1
  195. package/web/dist/assets/{diagram-UQ7AKVKN-BO8VQ4Fx.js → diagram-UQ7AKVKN-qW7tssUW.js} +1 -1
  196. package/web/dist/assets/{diagram-VSXAHHWV-CHUZnR9F.js → diagram-VSXAHHWV-txJPHpvG.js} +1 -1
  197. package/web/dist/assets/{diagram-VX7I27RA-DYQAz1Vu.js → diagram-VX7I27RA-CQTMsCnL.js} +1 -1
  198. package/web/dist/assets/{diagram-Z3DM3KII-BDjivgLr.js → diagram-Z3DM3KII--cb1hoE6.js} +1 -1
  199. package/web/dist/assets/{ebnfDiagram-PWID7BFC-Bpe_QVbH.js → ebnfDiagram-PWID7BFC-Da6lDU77.js} +1 -1
  200. package/web/dist/assets/{erDiagram-SSCWMZ5O-BxhwuAu1.js → erDiagram-SSCWMZ5O-DGCMrsQn.js} +1 -1
  201. package/web/dist/assets/{flowDiagram-A5DVABFB-CCw2H08-.js → flowDiagram-A5DVABFB-GdOL_qno.js} +1 -1
  202. package/web/dist/assets/{ganttDiagram-EL5Y4UJY-DtTc7MyI.js → ganttDiagram-EL5Y4UJY-Cx1F_jBK.js} +4 -4
  203. package/web/dist/assets/{gitGraphDiagram-WWUBYQGX-BvORxMd2.js → gitGraphDiagram-WWUBYQGX-B5_6JhR3.js} +1 -1
  204. package/web/dist/assets/hand-CuvZZQGW.webp +0 -0
  205. package/web/dist/assets/index-BYKfMnmv.js +2759 -0
  206. package/web/dist/assets/index-CW7VsrVg.css +1 -0
  207. package/web/dist/assets/{infoDiagram-RXCK75RN-DMcrkypD.js → infoDiagram-RXCK75RN-CjvKpSPA.js} +1 -1
  208. package/web/dist/assets/{ishikawaDiagram-5VMMS53U-CZkTIbe-.js → ishikawaDiagram-5VMMS53U-CxLoAPhx.js} +1 -1
  209. package/web/dist/assets/{journeyDiagram-EYS64GPL-DR0XazdE.js → journeyDiagram-EYS64GPL-BfvS1upJ.js} +1 -1
  210. package/web/dist/assets/{kanban-definition-3QL26DDD-DN8VbwVu.js → kanban-definition-3QL26DDD-DKPPu0w4.js} +1 -1
  211. package/web/dist/assets/{layout-9_paCbyt.js → layout-Dhgck-Zo.js} +1 -1
  212. package/web/dist/assets/{linear-g6Ct012e.js → linear-DMSQ-aUw.js} +1 -1
  213. package/web/dist/assets/lxgw-nd-app-bold-D1pKA1bR.woff2 +0 -0
  214. package/web/dist/assets/lxgw-nd-app-regular-BDe3d26v.woff2 +0 -0
  215. package/web/dist/assets/lxgw-nd-bold-Bufvlg03.woff2 +0 -0
  216. package/web/dist/assets/lxgw-nd-regular-DQwoI4OP.woff2 +0 -0
  217. package/web/dist/assets/lxgwwenkaiscreen-subset-100-DLPe7tEt.woff2 +0 -0
  218. package/web/dist/assets/lxgwwenkaiscreen-subset-101-d3VsZKWT.woff2 +0 -0
  219. package/web/dist/assets/lxgwwenkaiscreen-subset-102-ClzJuLq6.woff2 +0 -0
  220. package/web/dist/assets/lxgwwenkaiscreen-subset-103-DuDnQ6K-.woff2 +0 -0
  221. package/web/dist/assets/lxgwwenkaiscreen-subset-104-kPGW3Eem.woff2 +0 -0
  222. package/web/dist/assets/lxgwwenkaiscreen-subset-105-BzD54h-0.woff2 +0 -0
  223. package/web/dist/assets/lxgwwenkaiscreen-subset-106-BQ7fIzlx.woff2 +0 -0
  224. package/web/dist/assets/lxgwwenkaiscreen-subset-107-D8tDfFHI.woff2 +0 -0
  225. package/web/dist/assets/lxgwwenkaiscreen-subset-108-tGhkpwep.woff2 +0 -0
  226. package/web/dist/assets/lxgwwenkaiscreen-subset-109-7SMVSrly.woff2 +0 -0
  227. package/web/dist/assets/lxgwwenkaiscreen-subset-110-_uDKrFed.woff2 +0 -0
  228. package/web/dist/assets/lxgwwenkaiscreen-subset-111-Bio825RE.woff2 +0 -0
  229. package/web/dist/assets/lxgwwenkaiscreen-subset-112-D2ee0i-C.woff2 +0 -0
  230. package/web/dist/assets/lxgwwenkaiscreen-subset-113-JPFVDrga.woff2 +0 -0
  231. package/web/dist/assets/lxgwwenkaiscreen-subset-114-BcfxYA4w.woff2 +0 -0
  232. package/web/dist/assets/lxgwwenkaiscreen-subset-115-B1HeeOsd.woff2 +0 -0
  233. package/web/dist/assets/lxgwwenkaiscreen-subset-116-hY12tXpq.woff2 +0 -0
  234. package/web/dist/assets/lxgwwenkaiscreen-subset-117-BvuZwmre.woff2 +0 -0
  235. package/web/dist/assets/lxgwwenkaiscreen-subset-118-tzvs_H7j.woff2 +0 -0
  236. package/web/dist/assets/lxgwwenkaiscreen-subset-119-J2DWuYDN.woff2 +0 -0
  237. package/web/dist/assets/lxgwwenkaiscreen-subset-21-pDubCJV5.woff2 +0 -0
  238. package/web/dist/assets/lxgwwenkaiscreen-subset-22-ChAPMpqp.woff2 +0 -0
  239. package/web/dist/assets/lxgwwenkaiscreen-subset-23-BBx3oNpT.woff2 +0 -0
  240. package/web/dist/assets/lxgwwenkaiscreen-subset-24-CYt-Oagk.woff2 +0 -0
  241. package/web/dist/assets/lxgwwenkaiscreen-subset-25-Cx3j-ZUv.woff2 +0 -0
  242. package/web/dist/assets/lxgwwenkaiscreen-subset-26-B9AfZ3LL.woff2 +0 -0
  243. package/web/dist/assets/lxgwwenkaiscreen-subset-27-Bh2s9MNE.woff2 +0 -0
  244. package/web/dist/assets/lxgwwenkaiscreen-subset-28-C02JPoKP.woff2 +0 -0
  245. package/web/dist/assets/lxgwwenkaiscreen-subset-29-T_tbUGhk.woff2 +0 -0
  246. package/web/dist/assets/lxgwwenkaiscreen-subset-30-DMfyNzhz.woff2 +0 -0
  247. package/web/dist/assets/lxgwwenkaiscreen-subset-31-DFfdzbkg.woff2 +0 -0
  248. package/web/dist/assets/lxgwwenkaiscreen-subset-32-Bs1fz2iW.woff2 +0 -0
  249. package/web/dist/assets/lxgwwenkaiscreen-subset-33-cpB96OkZ.woff2 +0 -0
  250. package/web/dist/assets/lxgwwenkaiscreen-subset-34-BhinPYWX.woff2 +0 -0
  251. package/web/dist/assets/lxgwwenkaiscreen-subset-35-D_njRQqA.woff2 +0 -0
  252. package/web/dist/assets/lxgwwenkaiscreen-subset-36-DqXlLE3t.woff2 +0 -0
  253. package/web/dist/assets/lxgwwenkaiscreen-subset-37-j5LbvMph.woff2 +0 -0
  254. package/web/dist/assets/lxgwwenkaiscreen-subset-38-oO4jWdKm.woff2 +0 -0
  255. package/web/dist/assets/lxgwwenkaiscreen-subset-39-CabsX4rw.woff2 +0 -0
  256. package/web/dist/assets/lxgwwenkaiscreen-subset-4-PatS3YHh.woff2 +0 -0
  257. package/web/dist/assets/lxgwwenkaiscreen-subset-40-yrLg3C3e.woff2 +0 -0
  258. package/web/dist/assets/lxgwwenkaiscreen-subset-41-DBXmCQhc.woff2 +0 -0
  259. package/web/dist/assets/lxgwwenkaiscreen-subset-42-Bqij_UiQ.woff2 +0 -0
  260. package/web/dist/assets/lxgwwenkaiscreen-subset-43-D3kp0Ntc.woff2 +0 -0
  261. package/web/dist/assets/lxgwwenkaiscreen-subset-44-B_BNGvK4.woff2 +0 -0
  262. package/web/dist/assets/lxgwwenkaiscreen-subset-45-CZc0cQWs.woff2 +0 -0
  263. package/web/dist/assets/lxgwwenkaiscreen-subset-46-CHQX5Kd9.woff2 +0 -0
  264. package/web/dist/assets/lxgwwenkaiscreen-subset-47-Ci8gLrHA.woff2 +0 -0
  265. package/web/dist/assets/lxgwwenkaiscreen-subset-48-run3ugD3.woff2 +0 -0
  266. package/web/dist/assets/lxgwwenkaiscreen-subset-49-DgfiUb6u.woff2 +0 -0
  267. package/web/dist/assets/lxgwwenkaiscreen-subset-5-B0CtGbX_.woff2 +0 -0
  268. package/web/dist/assets/lxgwwenkaiscreen-subset-50-CdCG-POr.woff2 +0 -0
  269. package/web/dist/assets/lxgwwenkaiscreen-subset-51-L2okeQZ2.woff2 +0 -0
  270. package/web/dist/assets/lxgwwenkaiscreen-subset-52-BZAqyGJW.woff2 +0 -0
  271. package/web/dist/assets/lxgwwenkaiscreen-subset-53-B0xWqLeU.woff2 +0 -0
  272. package/web/dist/assets/lxgwwenkaiscreen-subset-54-BZVQX-EA.woff2 +0 -0
  273. package/web/dist/assets/lxgwwenkaiscreen-subset-55-093mkIUJ.woff2 +0 -0
  274. package/web/dist/assets/lxgwwenkaiscreen-subset-56-CbhWG8G-.woff2 +0 -0
  275. package/web/dist/assets/lxgwwenkaiscreen-subset-57-pLQuvo6c.woff2 +0 -0
  276. package/web/dist/assets/lxgwwenkaiscreen-subset-58-BHhoY50x.woff2 +0 -0
  277. package/web/dist/assets/lxgwwenkaiscreen-subset-59-D1_6DuIQ.woff2 +0 -0
  278. package/web/dist/assets/lxgwwenkaiscreen-subset-6-BnAelmYU.woff2 +0 -0
  279. package/web/dist/assets/lxgwwenkaiscreen-subset-60-HDBIuLTZ.woff2 +0 -0
  280. package/web/dist/assets/lxgwwenkaiscreen-subset-61-C-c13SeU.woff2 +0 -0
  281. package/web/dist/assets/lxgwwenkaiscreen-subset-62-kHRVOhZ2.woff2 +0 -0
  282. package/web/dist/assets/lxgwwenkaiscreen-subset-63-C0MCsvFv.woff2 +0 -0
  283. package/web/dist/assets/lxgwwenkaiscreen-subset-64-DZekguCj.woff2 +0 -0
  284. package/web/dist/assets/lxgwwenkaiscreen-subset-65-CtP6c3IU.woff2 +0 -0
  285. package/web/dist/assets/lxgwwenkaiscreen-subset-66-DLVWoMSf.woff2 +0 -0
  286. package/web/dist/assets/lxgwwenkaiscreen-subset-67-BVuOnayH.woff2 +0 -0
  287. package/web/dist/assets/lxgwwenkaiscreen-subset-68-DpxQ6Mnx.woff2 +0 -0
  288. package/web/dist/assets/lxgwwenkaiscreen-subset-69-qesBxLmC.woff2 +0 -0
  289. package/web/dist/assets/lxgwwenkaiscreen-subset-70-BBt1xN7h.woff2 +0 -0
  290. package/web/dist/assets/lxgwwenkaiscreen-subset-71-BWqZacwB.woff2 +0 -0
  291. package/web/dist/assets/lxgwwenkaiscreen-subset-72-B1wP_Yst.woff2 +0 -0
  292. package/web/dist/assets/lxgwwenkaiscreen-subset-73-C99T7jNZ.woff2 +0 -0
  293. package/web/dist/assets/lxgwwenkaiscreen-subset-74-DrUTsWBe.woff2 +0 -0
  294. package/web/dist/assets/lxgwwenkaiscreen-subset-75-C4M5XXQe.woff2 +0 -0
  295. package/web/dist/assets/lxgwwenkaiscreen-subset-76-Bq8spVWF.woff2 +0 -0
  296. package/web/dist/assets/lxgwwenkaiscreen-subset-77-D9vNupE2.woff2 +0 -0
  297. package/web/dist/assets/lxgwwenkaiscreen-subset-78-D6th7Gnu.woff2 +0 -0
  298. package/web/dist/assets/lxgwwenkaiscreen-subset-79-bezM85cS.woff2 +0 -0
  299. package/web/dist/assets/lxgwwenkaiscreen-subset-80-DD8pnhG2.woff2 +0 -0
  300. package/web/dist/assets/lxgwwenkaiscreen-subset-81-N6jtSyBs.woff2 +0 -0
  301. package/web/dist/assets/lxgwwenkaiscreen-subset-82-D6c180bZ.woff2 +0 -0
  302. package/web/dist/assets/lxgwwenkaiscreen-subset-83-em17AadM.woff2 +0 -0
  303. package/web/dist/assets/lxgwwenkaiscreen-subset-84-CJ8gdrMK.woff2 +0 -0
  304. package/web/dist/assets/lxgwwenkaiscreen-subset-85-9PPGO1eB.woff2 +0 -0
  305. package/web/dist/assets/lxgwwenkaiscreen-subset-86-CjWPf-u5.woff2 +0 -0
  306. package/web/dist/assets/lxgwwenkaiscreen-subset-87-8D_X_yOC.woff2 +0 -0
  307. package/web/dist/assets/lxgwwenkaiscreen-subset-88-9vtKFPBx.woff2 +0 -0
  308. package/web/dist/assets/lxgwwenkaiscreen-subset-89-BhE5jAKX.woff2 +0 -0
  309. package/web/dist/assets/lxgwwenkaiscreen-subset-90-D2Npfm5E.woff2 +0 -0
  310. package/web/dist/assets/lxgwwenkaiscreen-subset-91-C6Ft1-Eu.woff2 +0 -0
  311. package/web/dist/assets/lxgwwenkaiscreen-subset-97-CaExyQP9.woff2 +0 -0
  312. package/web/dist/assets/lxgwwenkaiscreen-subset-98-CPoVbsUd.woff2 +0 -0
  313. package/web/dist/assets/lxgwwenkaiscreen-subset-99-ZUQZGVgH.woff2 +0 -0
  314. package/web/dist/assets/{mindmap-definition-FBJOCRG2-BGt4omYC.js → mindmap-definition-FBJOCRG2-CalfL_Kn.js} +1 -1
  315. package/web/dist/assets/{pegDiagram-XKGWAZYB-uSfS-PBE.js → pegDiagram-XKGWAZYB-D9mVWz7l.js} +1 -1
  316. package/web/dist/assets/pending-edit-apply-kwMFWsUe.js +31 -0
  317. package/web/dist/assets/{pieDiagram-E7YTZNPT-DpeI935H.js → pieDiagram-E7YTZNPT-DELzbZNS.js} +1 -1
  318. package/web/dist/assets/{quadrantDiagram-AXDQQJYC-B7IuNvWa.js → quadrantDiagram-AXDQQJYC-TZqp4mfA.js} +1 -1
  319. package/web/dist/assets/{railroadDiagram-O6MQD6OU-ARzYpPQI.js → railroadDiagram-O6MQD6OU-DxX59f0i.js} +1 -1
  320. package/web/dist/assets/{requirementDiagram-EFPCY7ZU-Cj4upkAo.js → requirementDiagram-EFPCY7ZU-DFdsJLIe.js} +1 -1
  321. package/web/dist/assets/{rotate-cw-CXJcyHWk.js → rotate-cw-62liz7LX.js} +1 -1
  322. package/web/dist/assets/{sankeyDiagram-P5KCCOFB-OaVwSnLP.js → sankeyDiagram-P5KCCOFB-ByYcd3fG.js} +1 -1
  323. package/web/dist/assets/{sequenceDiagram-WJ2MYXX4-1yrUbq4t.js → sequenceDiagram-WJ2MYXX4-Bj-ml1jS.js} +1 -1
  324. package/web/dist/assets/{sizeCapture-X5ZJPWSS-nYH5p9D6.js → sizeCapture-X5ZJPWSS-KhsXThyD.js} +1 -1
  325. package/web/dist/assets/{square-dashed-mouse-pointer-CTtTVKoH.js → square-dashed-mouse-pointer-D_VrqVpy.js} +1 -1
  326. package/web/dist/assets/{stateDiagram-HBIQ2CUA-Dj5HvyNq.js → stateDiagram-HBIQ2CUA-BJlpHfWU.js} +1 -1
  327. package/web/dist/assets/stateDiagram-v2-4QOOHH4V-CR2kYAH2.js +1 -0
  328. package/web/dist/assets/{swimlanes-XN3QIQJK-umeT0HpB.js → swimlanes-XN3QIQJK-DUB9OJVs.js} +1 -1
  329. package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BitOxnG2.js +8 -0
  330. package/web/dist/assets/{timeline-definition-24CTP7MA-MqnQfHKB.js → timeline-definition-24CTP7MA-C03NX82A.js} +1 -1
  331. package/web/dist/assets/{vennDiagram-4TSXK5OY-b2liyZra.js → vennDiagram-4TSXK5OY-B9pjzngp.js} +1 -1
  332. package/web/dist/assets/{wardleyDiagram-VM6X3IG4-QMK-zNh1.js → wardleyDiagram-VM6X3IG4-UqWq_mcT.js} +1 -1
  333. package/web/dist/assets/{xychartDiagram-S5SC5T6Z-D5Mc6tfA.js → xychartDiagram-S5SC5T6Z-D2pDKtOv.js} +1 -1
  334. package/web/dist/index.html +2 -2
  335. package/server/engine/mcp/tools/arrange-on-board.js +0 -126
  336. package/server/engine/mcp/tools/create-on-board.js +0 -152
  337. package/server/engine/mcp/tools/edit-sketch.js +0 -167
  338. package/server/engine/mcp/tools/relate-on-board.js +0 -150
  339. package/server/engine/mcp/tools/sketch-on-board.js +0 -345
  340. package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +0 -235
  341. package/server/engine/plugins/nodesign/skills/rp-craft/patterns//346/274/224/345/207/272/351/241/265-/346/234/200/345/260/217/345/256/236/347/216/260.html +0 -78
  342. package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +0 -268
  343. package/web/dist/assets/DeckWindow-Bg3rgkvi.js +0 -11
  344. package/web/dist/assets/channel-DFyiuGL0.js +0 -1
  345. package/web/dist/assets/classDiagram-DTDB5LWJ-Aw1f8z-b.js +0 -1
  346. package/web/dist/assets/classDiagram-v2-JRS7N3AN-Aw1f8z-b.js +0 -1
  347. package/web/dist/assets/index-C0KUf6Wd.css +0 -1
  348. package/web/dist/assets/index-DGG5wIZs.js +0 -2118
  349. package/web/dist/assets/lxgw-nd-bold-DbRfiTgo.woff2 +0 -0
  350. package/web/dist/assets/lxgw-nd-regular-CpMw9Ehg.woff2 +0 -0
  351. package/web/dist/assets/pending-edit-apply-uP4uvrOD.js +0 -31
  352. package/web/dist/assets/stateDiagram-v2-4QOOHH4V-kJ1m_zJT.js +0 -1
  353. package/web/dist/assets/swimlanesDiagram-VK2B7HYN-DPSkMbbl.js +0 -8
@@ -0,0 +1,170 @@
1
+ /**
2
+ * server/lib/sketch-stencils.js —— 图形词汇表(2026-08-30 画图能力线刀①)
3
+ *
4
+ * 参数化简笔画:agent 说「这里一座房子、两格宽」,机器展开成手工调好的 path。
5
+ * 跟落位那条线同一个哲学 —— **语义归 agent,几何外包**:画什么、放哪、多大
6
+ * 它定,一笔一画的坐标不用它算。
7
+ *
8
+ * ## 词表纪律
9
+ *
10
+ * 词表就是 agent 的词汇量(风格命名那条经验):几十个精选 > 无限自由。入选判据
11
+ * 是「侦探板 / 跑团 / 示意图里真会反复用到」。每个 stencil 是 0..100 规范框里的
12
+ * 一组笔画(多条 d),使用时缩放到目标尺寸、逐笔过手绘抖动 —— 跟整个画图族
13
+ * 同一支笔。新增一个词 = 加一条数据,代码零改动。
14
+ *
15
+ * ⚠️ 笔画坐标手工调过、**逐个看过渲染**(scripts 探针)。改的时候别信脑内想象,
16
+ * 渲染出来看 —— 画错的 path 比没有更坏。
17
+ */
18
+
19
+ /** 每个词:strokes = 0..100 框内的笔画(大写绝对 M/L/Q/C/Z);aspect = 高/宽 */
20
+ export const STENCILS = {
21
+ person: {
22
+ aspect: 1.6,
23
+ strokes: [
24
+ 'M50,4 C60,4 66,11 66,20 C66,29 60,36 50,36 C40,36 34,29 34,20 C34,11 40,4 50,4 Z',
25
+ 'M50,36 L50,64',
26
+ 'M22,52 L50,42 L78,52',
27
+ 'M50,64 L32,98',
28
+ 'M50,64 L68,98',
29
+ ],
30
+ },
31
+ house: {
32
+ aspect: 1,
33
+ strokes: [
34
+ 'M14,48 L50,14 L86,48',
35
+ 'M22,48 L22,92 L78,92 L78,48',
36
+ 'M44,92 L44,66 L58,66 L58,92',
37
+ ],
38
+ },
39
+ tree: {
40
+ aspect: 1.3,
41
+ strokes: [
42
+ 'M50,96 L50,58',
43
+ 'M50,58 C28,62 16,46 28,34 C26,16 52,8 60,22 C78,20 84,42 68,48 C66,58 56,62 50,58 Z',
44
+ ],
45
+ },
46
+ pine: {
47
+ aspect: 1.5,
48
+ strokes: [
49
+ 'M50,4 L30,36 L42,36 L24,64 L38,64 L18,92 L82,92 L62,64 L76,64 L58,36 L70,36 Z',
50
+ 'M50,92 L50,100',
51
+ ],
52
+ },
53
+ mountain: {
54
+ aspect: 0.6,
55
+ strokes: [
56
+ 'M2,96 L34,28 L50,58 L66,10 L98,96',
57
+ 'M58,34 L66,22 L74,36',
58
+ ],
59
+ },
60
+ boat: {
61
+ aspect: 0.9,
62
+ strokes: [
63
+ 'M18,74 L82,74 L70,92 L30,92 Z',
64
+ 'M52,74 L52,12',
65
+ 'M52,16 L84,68 L52,68 Z',
66
+ 'M48,24 L24,68 L48,68 Z',
67
+ ],
68
+ },
69
+ flag: {
70
+ aspect: 1.4,
71
+ strokes: [
72
+ 'M32,4 L32,98',
73
+ 'M32,8 Q54,16 74,10 L74,34 Q54,40 32,32 Z',
74
+ ],
75
+ },
76
+ star: {
77
+ aspect: 1,
78
+ strokes: [
79
+ 'M50,6 L60,36 L92,37 L66,56 L76,88 L50,69 L24,88 L34,56 L8,37 L40,36 Z',
80
+ ],
81
+ },
82
+ moon: {
83
+ aspect: 1.2,
84
+ strokes: [
85
+ 'M62,6 C36,14 24,42 34,66 C41,83 55,93 70,92 C50,84 40,62 46,42 C50,28 55,14 62,6 Z',
86
+ ],
87
+ },
88
+ sun: {
89
+ aspect: 1,
90
+ strokes: [
91
+ 'M50,28 C63,28 72,37 72,50 C72,63 63,72 50,72 C37,72 28,63 28,50 C28,37 37,28 50,28 Z',
92
+ 'M50,2 L50,16 M50,84 L50,98 M2,50 L16,50 M84,50 L98,50',
93
+ 'M17,17 L27,27 M73,73 L83,83 M83,17 L73,27 M27,73 L17,83',
94
+ ],
95
+ },
96
+ cloud: {
97
+ aspect: 0.55,
98
+ strokes: [
99
+ 'M24,78 C6,78 6,54 24,54 C24,32 52,26 60,42 C70,26 94,36 90,54 C104,58 100,78 84,78 Z',
100
+ ],
101
+ },
102
+ scroll: {
103
+ aspect: 1.2,
104
+ strokes: [
105
+ 'M18,18 C18,10 82,10 82,18 C82,26 18,26 18,18 Z',
106
+ 'M24,26 L24,80 M76,26 L76,80',
107
+ 'M18,86 C18,78 82,78 82,86 C82,94 18,94 18,86 Z',
108
+ 'M36,42 L64,42',
109
+ 'M36,54 L64,54',
110
+ 'M36,66 L58,66',
111
+ ],
112
+ },
113
+ sword: {
114
+ aspect: 1.6,
115
+ strokes: [
116
+ 'M50,2 L43,60 L50,70 L57,60 Z',
117
+ 'M34,62 L66,62',
118
+ 'M50,70 L50,92',
119
+ 'M44,95 L56,95',
120
+ ],
121
+ },
122
+ dice: {
123
+ aspect: 1,
124
+ strokes: [
125
+ 'M20,20 L80,20 L80,80 L20,80 Z',
126
+ 'M35,33 C38,33 38,37 35,37 C32,37 32,33 35,33 Z',
127
+ 'M50,48 C53,48 53,52 50,52 C47,52 47,48 50,48 Z',
128
+ 'M65,63 C68,63 68,67 65,67 C62,67 62,63 65,63 Z',
129
+ ],
130
+ },
131
+ heart: {
132
+ aspect: 0.95,
133
+ strokes: [
134
+ 'M50,88 C16,58 22,20 50,36 C78,20 84,58 50,88 Z',
135
+ ],
136
+ },
137
+ door: {
138
+ aspect: 1.6,
139
+ strokes: [
140
+ 'M26,96 L26,26 C26,10 74,10 74,26 L74,96',
141
+ 'M26,96 L74,96',
142
+ 'M62,56 C65,56 65,60 62,60 C59,60 59,56 62,56 Z',
143
+ ],
144
+ },
145
+ };
146
+
147
+ export const STENCIL_NAMES = Object.keys(STENCILS);
148
+
149
+ /**
150
+ * 展开一个词:缩放到 w×h(像素),返回未抖动的笔画(抖动由调用方按 seed 加 ——
151
+ * 阵列的每份要各自的抖)。
152
+ * @returns {{ strokes: string[], w:number, h:number }}
153
+ */
154
+ export function stencilStrokes(name, wPx, hPx = null) {
155
+ const st = STENCILS[name];
156
+ if (!st) return null;
157
+ const w = Math.max(8, wPx);
158
+ const h = Math.max(8, hPx || w * st.aspect);
159
+ // ⚠️ 奇偶计数器**每条笔画各自数**:跨笔画累计的话,一条 d 的数字个数为奇数
160
+ // 就把后面所有笔画的 x/y 错位成 y/x
161
+ return { strokes: st.strokes.map((d) => scaleD(d, w / 100, h / 100)), w, h };
162
+ }
163
+
164
+ function scaleD(d, sx, sy) {
165
+ let k = 0;
166
+ return d.replace(/-?\d*\.?\d+/g, (n) => {
167
+ const v = Number(n) * ((k++ % 2 === 0) ? sx : sy);
168
+ return String(Math.round(v * 10) / 10);
169
+ }).replace(/,/g, ' ');
170
+ }
@@ -0,0 +1,237 @@
1
+ /**
2
+ * server/lib/state-table.js —— 状态表:板上那张 `| 键 | 值 |` 的解析与改写
3
+ * (2026-08-30)
4
+ *
5
+ * ## 为什么真相是一张 markdown 表,不是一份 JSON
6
+ *
7
+ * 演出里"会变、要记得"的东西(好感度 / 时间 / 线索 / 体力)此前住在状态板的正文里,
8
+ * 是一段自由文本,每拍靠 agent 整卡重写。想让程序参与(下一轮提醒它、将来按条件
9
+ * 触发)就得有结构。
10
+ *
11
+ * 结构可以另起一份存储(`.nd/vars.json` 之类),但那条路被两件事否掉:
12
+ * - `.nd/` 在 gitignore 里(workspace-templates.js)。存那儿 = 用户在画布上看不见、
13
+ * git 里没历史、`revertWorkspace` 回滚正文却不回滚它 →「旧故事配新数值」,
14
+ * 而且没有任何东西会报这个错。
15
+ * - 另起一份 = 同一件事两份真相。状态板的字**已经**在 `notes/板书/` 里、已经进 git、
16
+ * 已经每轮 commit。再存一份就是这条线上翻过最多次车的那种账。
17
+ *
18
+ * 所以:**真相仍是那条板书文件,只是约定它正文里有一张固定格式的表。**
19
+ * 服务端只读解析出派生值喂每轮状态块;`set_vars` 做的是"精确改表里那一格",
20
+ * 不是重写整张卡。文件内容 = 显示内容 = agent Read 到的内容,三者逐字相同。
21
+ *
22
+ * ## 定位判据:tag,不是文件名、不是隐形标记
23
+ *
24
+ * ⛔ **不能用藏在 frontmatter 里的自造键**:`renderChalk` 只回写白名单键,
25
+ * 一次 `edit_board set_text` 就把它整行丢掉,而且不报错。
26
+ * ⛔ **不能用固定文件名**(`状态表.md` 那种):chalk 目录的文件名是时间戳,
27
+ * `recentChalk` 和首页摘句都拿"文件名倒序"当"最近"判据 —— 一个 CJK 开头的
28
+ * 固定名在字典序里排在所有数字时间戳之后,倒序后永远第一,
29
+ * 结果是**首页每个演出项目的卡常年显示「| 键 | 值 |」**。
30
+ * ✅ 用 `tag: 状态表`(专用 tag,跟状态板那个**组** tag 分开)。tag 是少数能双向
31
+ * 往返的字段(parseChalk 读、renderChalk 写、set_text 保留),改名搬位都不影响。
32
+ */
33
+
34
+ import path from 'node:path';
35
+ import { promises as fs } from 'node:fs';
36
+ import { CHALK_DIR, parseChalk } from './chalk.js';
37
+
38
+ /** 状态表专用 tag。⚠️ 跟 blackboard-rp 的「状态板」组 tag 是两个东西:
39
+ * 那个是一**组**卡(PC卡/场景/明骰表)的组名,这个专指承载键值表的那一条。 */
40
+ export const STATE_TABLE_TAG = '状态表';
41
+
42
+ /** 值的上限:一格里塞长文是把表当笔记本用,那是板书正文的活 */
43
+ export const VALUE_MAX = 120;
44
+ /** 键的上限与字符集:键要能当标识符用(将来触发器按它寻址),别收任意文本 */
45
+ export const KEY_MAX = 40;
46
+ const KEY_RE = /^[\p{L}\p{N}_][\p{L}\p{N}_·-]{0,39}$/u;
47
+
48
+ /**
49
+ * 剥掉围栏块再找表。
50
+ * 板书里最常见的围栏是 ```nd:controls(控件声明),里面完全可能出现竖线。
51
+ * 判据抄 board-excerpt.js 的 excerptOf —— 那儿已经为同一个理由剥过一次。
52
+ */
53
+ export function stripFences(body) {
54
+ return String(body || '')
55
+ .replace(/^[ \t]*```[\s\S]*?^[ \t]*```[ \t]*$|^[ \t]*```[\s\S]*$/gm, '');
56
+ }
57
+
58
+ const SEP_RE = /^\s*\|?[\s:-]*-[-\s:|]*\|?\s*$/; // | --- | --- |
59
+ const HEAD_RE = /^\s*\|\s*键\s*\|\s*值\s*\|\s*$/;
60
+ const FENCE_RE = /^[ \t]*```/;
61
+
62
+ /**
63
+ * 逐行标出「这一行在围栏里吗」。
64
+ *
65
+ * ⚠️ 不能用 stripFences 之后的**文本**去比对(第一版就是这么写的,当场翻车):
66
+ * 围栏里那行 `| 键 | 值 |` 和真表头**逐字相同**,按内容判membership 两行都算"活着",
67
+ * 于是一张表被数成两张。判据必须落在**行号**上,不是行的内容。
68
+ * 未闭合的围栏一路吃到末尾(跟 board-excerpt 的取舍一致:宁可少认,不能把源码当数据)。
69
+ */
70
+ function fenceMask(lines) {
71
+ const mask = new Array(lines.length).fill(false);
72
+ let open = false;
73
+ for (let i = 0; i < lines.length; i += 1) {
74
+ if (FENCE_RE.test(lines[i])) { mask[i] = true; open = !open; continue; }
75
+ mask[i] = open;
76
+ }
77
+ return mask;
78
+ }
79
+
80
+ function cells(line) {
81
+ return line.replace(/^\s*\|/, '').replace(/\|\s*$/, '').split('|').map((c) => c.trim());
82
+ }
83
+
84
+ /**
85
+ * 从正文里解析状态表。
86
+ *
87
+ * @returns {{ ok:true, start:number, end:number, rows:Array<{key,value}> }
88
+ * | { ok:false, error:string }}
89
+ * start/end 是表在**原始行数组**里的下标区间(含表头与分隔行),改写时按它回填。
90
+ * ok:false 的两种:没有表、或者有歧义(多张表 / 重复键)—— 一律**大声**,
91
+ * 不 fail-soft 成空表。空表和"读不懂"是两件事,混在一起就是那条最贵的教训。
92
+ */
93
+ export function parseStateTable(body) {
94
+ const raw = String(body || '').split(/\r?\n/);
95
+ const inFence = fenceMask(raw);
96
+ const heads = [];
97
+ for (let i = 0; i < raw.length; i += 1) {
98
+ if (!HEAD_RE.test(raw[i])) continue;
99
+ if (inFence[i]) continue; // 表头在围栏里 → 不算表
100
+ heads.push(i);
101
+ }
102
+ if (!heads.length) return { ok: false, error: 'no-table' };
103
+ if (heads.length > 1) {
104
+ return { ok: false, error: `这条板书里有 ${heads.length} 张「| 键 | 值 |」表 —— 状态表只能有一张,另一张挪到别的板书上去(明骰表用别的表头)。` };
105
+ }
106
+ const start = heads[0];
107
+ if (!SEP_RE.test(raw[start + 1] || '')) {
108
+ return { ok: false, error: '「| 键 | 值 |」下面缺分隔行(`|---|---|`),这不是一张合法的 markdown 表。' };
109
+ }
110
+ const rows = []; const seen = new Map();
111
+ let end = start + 1;
112
+ for (let i = start + 2; i < raw.length; i += 1) {
113
+ const line = raw[i];
114
+ if (!/^\s*\|/.test(line)) break; // 表到此为止
115
+ const c = cells(line);
116
+ if (c.length < 2) break;
117
+ const key = c[0]; const value = c[1];
118
+ if (!key) { end = i; continue; } // 空键行:留着但不算数据
119
+ if (seen.has(key)) {
120
+ return { ok: false, error: `表里「${key}」出现了两次(第 ${seen.get(key) + 1} 行和第 ${i + 1} 行)—— 键必须唯一,先把重复的那行删掉。` };
121
+ }
122
+ seen.set(key, i);
123
+ rows.push({ key, value });
124
+ end = i;
125
+ }
126
+ return { ok: true, start, end, rows };
127
+ }
128
+
129
+ /** 值清洗:压一行、去掉会把表撑破的字符。钳了要说(返回 clamped 让调用方如实报) */
130
+ export function sanitizeValue(v) {
131
+ const src = String(v ?? '');
132
+ const s = src
133
+ // 控制字符(含换行、制表、行分隔符)先抹平:一格里塞多行会把表拆散,
134
+ // 而且拆散之后解析器只会少读几行,不会报错 —— 这类静默降级要堵在写口。
135
+ .replace(/[\u0000-\u001f\u007f\u2028\u2029]/g, ' ')
136
+ .replace(/\|/g, '/') // 竖线会当场炸表
137
+ .replace(/\s+/g, ' ')
138
+ .trim();
139
+ return { value: s.slice(0, VALUE_MAX), clamped: s.length > VALUE_MAX || src.includes('|') };
140
+ }
141
+
142
+ export function validateKey(k) {
143
+ const s = String(k ?? '').trim();
144
+ if (!s) return '键是空的';
145
+ if (s.length > KEY_MAX) return `键「${s.slice(0, 12)}…」超过 ${KEY_MAX} 字`;
146
+ if (!KEY_RE.test(s)) return `键「${s}」含不能用的字符(只收字母数字、中文、下划线、连字符、间隔号,且不能以连字符开头)`;
147
+ return null;
148
+ }
149
+
150
+ /** 把一张表渲染回 markdown 行(列宽不对齐 —— 对齐了 diff 会整片变) */
151
+ export function renderRows(rows) {
152
+ return ['| 键 | 值 |', '| --- | --- |', ...rows.map((r) => `| ${r.key} | ${r.value} |`)];
153
+ }
154
+
155
+ /**
156
+ * 在正文里改/加若干个键,返回新正文与实际发生的变化。
157
+ * 表不存在或看不懂时**抛** —— 调用方负责把话说清楚。
158
+ */
159
+ export function applyVars(body, vars) {
160
+ const parsed = parseStateTable(body);
161
+ if (!parsed.ok) {
162
+ const err = new Error(parsed.error === 'no-table'
163
+ ? '这条板书里没有「| 键 | 值 |」表。'
164
+ : parsed.error);
165
+ err.code = parsed.error === 'no-table' ? 'NO_TABLE' : 'BAD_TABLE';
166
+ throw err;
167
+ }
168
+ const rows = parsed.rows.map((r) => ({ ...r }));
169
+ const changed = []; const added = []; const clamped = [];
170
+ for (const [rawKey, rawVal] of Object.entries(vars)) {
171
+ const key = String(rawKey).trim();
172
+ const bad = validateKey(key);
173
+ if (bad) { const e = new Error(bad); e.code = 'BAD_KEY'; throw e; }
174
+ const { value, clamped: cl } = sanitizeValue(rawVal);
175
+ if (cl) clamped.push(key);
176
+ const hit = rows.find((r) => r.key === key);
177
+ if (hit) { if (hit.value !== value) changed.push({ key, from: hit.value, to: value }); hit.value = value; }
178
+ else { rows.push({ key, value }); added.push({ key, value }); }
179
+ }
180
+ const lines = String(body).split(/\r?\n/);
181
+ const next = [...lines.slice(0, parsed.start), ...renderRows(rows), ...lines.slice(parsed.end + 1)];
182
+ return { body: next.join('\n'), rows, changed, added, clamped };
183
+ }
184
+
185
+ /**
186
+ * 在工作区里找那条状态表板书。
187
+ * @returns {{ found:true, rel, abs, raw, body, chalk }
188
+ * | { found:false, reason:'none' }
189
+ * | { found:false, reason:'multiple', rels:string[] }}
190
+ */
191
+ export async function findStateTable(sharedRoot) {
192
+ const dir = path.join(sharedRoot, CHALK_DIR);
193
+ let names;
194
+ try {
195
+ names = (await fs.readdir(dir)).filter((n) => n.endsWith('.md') && !n.startsWith('.'));
196
+ } catch { return { found: false, reason: 'none' }; }
197
+ const hits = [];
198
+ for (const n of names) {
199
+ let raw;
200
+ try { raw = await fs.readFile(path.join(dir, n), 'utf8'); } catch { continue; }
201
+ const { body, chalk } = parseChalk(raw);
202
+ if (chalk?.tag === STATE_TABLE_TAG) {
203
+ hits.push({ rel: `${CHALK_DIR}/${n}`, abs: path.join(dir, n), raw, body, chalk });
204
+ }
205
+ }
206
+ if (!hits.length) return { found: false, reason: 'none' };
207
+ if (hits.length > 1) return { found: false, reason: 'multiple', rels: hits.map((h) => h.rel) };
208
+ return { found: true, ...hits[0] };
209
+ }
210
+
211
+ /**
212
+ * 给每轮状态块用的读侧。**永远返回一个可渲染的结果**,但「读不懂」和「没有」
213
+ * 是两种不同的返回 —— 上一轮还解析得出、这一轮解析不出,调用方要能报警。
214
+ * @returns {{ state:'none' } | { state:'ok', rel, rows, body } | { state:'broken', rel?, why }}
215
+ */
216
+ export async function readStateVars(sharedRoot) {
217
+ const f = await findStateTable(sharedRoot);
218
+ if (!f.found) {
219
+ if (f.reason === 'multiple') {
220
+ return { state: 'broken', why: `板上有 ${f.rels.length} 条 tag 是「${STATE_TABLE_TAG}」的板书(${f.rels.join('、')})—— 只能有一条。` };
221
+ }
222
+ return { state: 'none' };
223
+ }
224
+ const parsed = parseStateTable(f.body);
225
+ if (!parsed.ok) {
226
+ return {
227
+ state: 'broken',
228
+ rel: f.rel,
229
+ why: parsed.error === 'no-table'
230
+ ? `${f.rel} 挂着「${STATE_TABLE_TAG}」的 tag,但正文里找不到「| 键 | 值 |」表。`
231
+ : parsed.error,
232
+ };
233
+ }
234
+ // body 一起带回:触发器的声明(```nd:triggers 围栏)就住在这条板书的正文里,
235
+ // 调用方拿它去解析,省一次读盘也省一次"两边读的是不是同一份"的疑虑。
236
+ return { state: 'ok', rel: f.rel, rows: parsed.rows, body: f.body };
237
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * server/lib/state-trend.js —— 状态表的历史序列(2026-08-30 画图能力线·活图第一块)
3
+ *
4
+ * 状态表住在一条板书文件里、板书文件进 git、工作区**每回合落一个 commit**
5
+ * (session-loop turn commit)—— 所以「好感度随拍数怎么走」的序列是**现成的**,
6
+ * 不用第二份存储不用记账,git 就是时间轴。这是别家没有的弹药。
7
+ *
8
+ * 读侧纪律与状态表同款:拿不到就大声说拿不到(没有表 / 历史不足 / 值不是数字),
9
+ * 不画一根瞎编的线。git 操作全只读(log/show),不进写 mutex。
10
+ */
11
+
12
+ import { execFile } from 'node:child_process';
13
+ import { promisify } from 'node:util';
14
+ import { findStateTable, parseStateTable } from './state-table.js';
15
+ import { parseChalk } from './chalk.js';
16
+
17
+ const run = promisify(execFile);
18
+ const MAX_POINTS = 80;
19
+
20
+ /** 值里抠数字:`5`→5、`8/10`→8、`≈3.5`→3.5;抠不出返回 null */
21
+ export function numOf(value) {
22
+ const m = /-?\d+(?:\.\d+)?/.exec(String(value ?? ''));
23
+ return m ? Number(m[0]) : null;
24
+ }
25
+
26
+ /**
27
+ * 某个键的历史序列(旧→新,末尾是**盘上现值**——本回合改了还没 commit 的也算数)。
28
+ * @returns {{ ok:true, points:number[], rel:string } |
29
+ * { ok:false, why:string, rel?:string, points?:number[] }}
30
+ */
31
+ export async function trendSeries(sharedRoot, key) {
32
+ const f = await findStateTable(sharedRoot);
33
+ if (!f.found) return { ok: false, why: f.reason === 'multiple' ? '有两条挂着状态表 tag 的板书,先合成一条' : '板上还没有状态表(tag: 状态表 的那条板书)' };
34
+ const rel = f.rel;
35
+
36
+ let hashes = [];
37
+ try {
38
+ const { stdout } = await run('git', ['log', '--format=%H', '--reverse', '--follow', '--', rel], { cwd: sharedRoot });
39
+ hashes = stdout.trim().split('\n').filter(Boolean);
40
+ } catch { /* 没有 git 历史就只有现值 */ }
41
+ if (hashes.length > MAX_POINTS) hashes = hashes.slice(hashes.length - MAX_POINTS);
42
+
43
+ const points = [];
44
+ for (const h of hashes) {
45
+ try {
46
+ const { stdout } = await run('git', ['show', `${h}:${rel}`], { cwd: sharedRoot });
47
+ const rows = parseStateTable(parseChalk(stdout).body || '');
48
+ if (!rows.ok) continue;
49
+ const row = rows.rows.find((r) => r.key === key);
50
+ if (!row) continue;
51
+ const v = numOf(row.value);
52
+ if (v === null) continue;
53
+ // 同值连点不压缩:横盘也是信息(十拍没动 = 一条平线)
54
+ points.push(v);
55
+ } catch { /* 某个版本读不出就跳过 */ }
56
+ }
57
+
58
+ // 盘上现值收尾(本回合 set_vars 改过、还没 commit)
59
+ const { readStateVars } = await import('./state-table.js');
60
+ const live = await readStateVars(sharedRoot);
61
+ if (live.state === 'ok') {
62
+ const row = live.rows.find((r) => r.key === key);
63
+ const v = row ? numOf(row.value) : null;
64
+ if (v !== null && (points.length === 0 || v !== points[points.length - 1])) points.push(v);
65
+ if (row && v === null) return { ok: false, rel, why: `「${key}」的现值是「${row.value}」,抠不出数字 —— 趋势线只画得了数` };
66
+ if (!row) return { ok: false, rel, why: `状态表里没有「${key}」这个键` };
67
+ }
68
+ if (points.length < 2) {
69
+ return { ok: false, rel, points, why: `「${key}」目前只有 ${points.length} 个数据点(历史随每回合的 git commit 长出来)—— 至少两点才有线,过几拍再画` };
70
+ }
71
+ return { ok: true, points, rel };
72
+ }
73
+
74
+ /**
75
+ * 序列 → 手绘折线的**几何**(局部像素;抖动由调用方加,跟全族同一支笔)。
76
+ * @returns {{ w,h, lineD:string, baselineD:string, dotD:string, min:number, max:number }}
77
+ */
78
+ export function trendGeometry(points, { w = 336, h = 120 } = {}) {
79
+ const P = 8;
80
+ const min = Math.min(...points); const max = Math.max(...points);
81
+ const span = (max - min) || 1;
82
+ const iw = w - P * 2; const ih = h - P * 2;
83
+ const xy = points.map((v, i) => ({
84
+ x: P + (points.length === 1 ? iw / 2 : (i / (points.length - 1)) * iw),
85
+ y: P + ih - ((v - min) / span) * ih,
86
+ }));
87
+ const f = (n) => Math.round(n * 10) / 10;
88
+ const lineD = xy.map((p, i) => `${i ? 'L' : 'M'} ${f(p.x)} ${f(p.y)}`).join(' ');
89
+ const last = xy[xy.length - 1];
90
+ // 端点小圈(现值在哪)
91
+ const r = 3;
92
+ const dotD = `M ${f(last.x - r)} ${f(last.y)} C ${f(last.x - r)} ${f(last.y - r * 1.4)} ${f(last.x + r)} ${f(last.y - r * 1.4)} ${f(last.x + r)} ${f(last.y)} C ${f(last.x + r)} ${f(last.y + r * 1.4)} ${f(last.x - r)} ${f(last.y + r * 1.4)} ${f(last.x - r)} ${f(last.y)}`;
93
+ return { w, h, lineD, baselineD: `M ${P} ${h - P} L ${w - P} ${h - P}`, dotD, min, max };
94
+ }