@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.
- package/README.md +266 -79
- package/README.zh-CN.md +254 -67
- package/package.json +2 -2
- package/server/api/admin.js +7 -0
- package/server/api/assets/docx-page.js +5 -4
- package/server/api/assets/helpers.js +8 -0
- package/server/api/assets.js +21 -33
- package/server/api/board.js +29 -1
- package/server/api/browse.js +3 -2
- package/server/api/chatai.js +9 -8
- package/server/api/exports/cards.js +7 -6
- package/server/api/exports/docx-pdf.js +4 -1
- package/server/api/local.js +10 -8
- package/server/api/projects.js +19 -3
- package/server/api/publish.js +3 -2
- package/server/api/sessions-rewind.js +220 -0
- package/server/api/sessions.js +58 -194
- package/server/api/stage.js +35 -0
- package/server/api/turn-model-switch.js +109 -0
- package/server/api/turn.js +52 -88
- package/server/auth/middleware.js +29 -6
- package/server/auth/tier.js +27 -7
- package/server/auth/users-store.js +14 -1
- package/server/engine/agent/actor-trail.js +110 -0
- package/server/engine/agent/agent-shared.js +22 -52
- package/server/engine/agent/beat-state.js +61 -0
- package/server/engine/agent/cast.js +322 -0
- package/server/engine/agent/events.js +9 -2
- package/server/engine/agent/hooks/beat-gate.js +73 -0
- package/server/engine/agent/hooks/lifecycle.js +6 -2
- package/server/engine/agent/hooks/post-trim.js +1 -0
- package/server/engine/agent/hooks/pre-board-dirty.js +29 -0
- package/server/engine/agent/hooks/pre-defaults.js +206 -2
- package/server/engine/agent/hooks/pre-injectors.js +19 -2
- package/server/engine/agent/hooks/pre-peer-guard.js +88 -0
- package/server/engine/agent/hooks/pre-unknown-params.js +82 -0
- package/server/engine/agent/hooks/pre-workspace-scope-guard.js +33 -0
- package/server/engine/agent/hooks/resident-role-lifecycle.js +100 -0
- package/server/engine/agent/hooks/slot-alias.js +30 -0
- package/server/engine/agent/hooks/user-prompt-submit.js +197 -7
- package/server/engine/agent/hooks.js +76 -6
- package/server/engine/agent/isolation.js +25 -2
- package/server/engine/agent/memory-config.js +27 -1
- package/server/engine/agent/model-context.js +102 -18
- package/server/engine/agent/model-table.js +361 -58
- package/server/engine/agent/prompts/nodesign-prelude.md +566 -59
- package/server/engine/agent/prompts/tools/direct-edit-protocol.md +1 -1
- package/server/engine/agent/prompts/tools/generate-image-cookbook.md +2 -2
- package/server/engine/agent/prompts/tools/paint-still-cookbook.md +1 -1
- package/server/engine/agent/prompts/tools/site-reference.md +2 -1
- package/server/engine/agent/role-card.js +175 -0
- package/server/engine/agent/rp-mode.js +17 -0
- package/server/engine/agent/session-loop.js +26 -14
- package/server/engine/agent/stage-status.js +73 -0
- package/server/engine/agent/subagent-flight.js +73 -0
- package/server/engine/agent/system-prompts.js +41 -0
- package/server/engine/agent/tool-input-stream.js +165 -0
- package/server/engine/mcp/actor.js +59 -0
- package/server/engine/mcp/capability-gate.js +21 -2
- package/server/engine/mcp/index.js +116 -24
- package/server/engine/mcp/mode-profile.js +139 -0
- package/server/engine/mcp/server-name.js +19 -0
- package/server/engine/mcp/tools/artifact-session.js +2 -1
- package/server/engine/mcp/tools/browse-find-batch.js +76 -14
- package/server/engine/mcp/tools/browse.js +1 -1
- package/server/engine/mcp/tools/cast-role.js +167 -0
- package/server/engine/mcp/tools/draw-trend.js +104 -0
- package/server/engine/mcp/tools/edit-board-schema.js +56 -0
- package/server/engine/mcp/tools/edit-board.js +569 -0
- package/server/engine/mcp/tools/look-at-board.js +4 -3
- package/server/engine/mcp/tools/open-sheet.js +242 -0
- package/server/engine/mcp/tools/organize-board.js +19 -5
- package/server/engine/mcp/tools/paint-still.js +1 -1
- package/server/engine/mcp/tools/pin-to-board.js +78 -3
- package/server/engine/mcp/tools/read-board.js +109 -16
- package/server/engine/mcp/tools/read-tavern-json.js +87 -19
- package/server/engine/mcp/tools/report-issue.js +1 -0
- package/server/engine/mcp/tools/role-memory.js +83 -0
- package/server/engine/mcp/tools/roll-dice.js +82 -0
- package/server/engine/mcp/tools/roll-film.js +1 -1
- package/server/engine/mcp/tools/screenshot.js +2 -2
- package/server/engine/mcp/tools/set-vars.js +150 -0
- package/server/engine/mcp/tools/sheet-replan.js +46 -0
- package/server/engine/mcp/tools/web-search.js +1 -1
- package/server/engine/mcp/tools/write-on-board-flow.js +108 -0
- package/server/engine/mcp/tools/write-on-board-place.js +211 -0
- package/server/engine/mcp/tools/write-on-board-role-anchor.js +48 -0
- package/server/engine/mcp/tools/write-on-board-schema.js +125 -0
- package/server/engine/mcp/tools/write-on-board.js +567 -107
- package/server/engine/plugins/nodesign/skills/blackboard-rp/SKILL.md +215 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +11 -3
- package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +34 -4
- package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +20 -4
- package/server/engine/plugins/nodesign/skills/story-craft/SKILL.md +94 -0
- package/server/engine/plugins/nodesign/skills/story-import/SKILL.md +112 -0
- package/server/engine/plugins/nodesign/skills/story-intimacy/SKILL.md +72 -0
- package/server/engine/plugins/nodesign/skills/story-voice/SKILL.md +66 -0
- package/server/engine/plugins/nodesign/skills/story-voice/voices//345/206/267/347/241/254.md +33 -0
- package/server/engine/plugins/nodesign/skills/story-voice/voices//346/203/205/346/204/237/347/233/264/347/273/231.md +42 -0
- package/server/engine/plugins/nodesign/skills/story-voice/voices//346/204/217/350/257/206/346/265/201.md +42 -0
- package/server/engine/plugins/nodesign/skills/story-voice/voices//346/255/246/344/276/240/347/231/275/350/257/235.md +34 -0
- package/server/engine/plugins/nodesign/skills/story-voice/voices//347/224/237/346/264/273/346/265/201.md +35 -0
- package/server/engine/plugins/nodesign/skills/story-voice/voices//347/231/275/346/217/217.md +37 -0
- package/server/engine/plugins/nodesign/skills/story-voice/voices//350/210/236/345/217/260/345/211/247.md +32 -0
- package/server/engine/plugins/nodesign/skills/story-voice/voices//351/230/266/346/256/265/345/210/207/346/215/242.md +42 -0
- package/server/engine/runs/board-seater.js +272 -0
- package/server/engine/runs/board-tasklist.js +90 -48
- package/server/engine/runs/turn-relay.js +24 -2
- package/server/engine/runs/user-chalk-echo.js +44 -0
- package/server/index.js +2 -0
- package/server/lib/board-anchor.js +49 -0
- package/server/lib/board-dirty.js +93 -0
- package/server/lib/board-excerpt.js +80 -0
- package/server/lib/board-follow.js +111 -0
- package/server/lib/board-groups.js +1 -5
- package/server/lib/board-hero.js +3 -1
- package/server/lib/board-kind-sizes.js +36 -0
- package/server/lib/board-lanes.js +58 -0
- package/server/lib/board-obstacles.js +80 -0
- package/server/lib/board-relations.js +5 -1
- package/server/lib/board-sheets.js +467 -0
- package/server/lib/board-shelf.js +86 -0
- package/server/lib/board-transform.js +62 -0
- package/server/lib/canvas-id.js +18 -1
- package/server/lib/chalk-flow.js +88 -0
- package/server/lib/chalk-rewrite.js +63 -0
- package/server/lib/chalk-size-pref.js +48 -0
- package/server/lib/chalk.js +42 -3
- package/server/lib/ingress/forward-openai-chat.js +16 -4
- package/server/lib/ingress/openai-chat.js +23 -12
- package/server/lib/ingress/session-routes.js +3 -3
- package/server/lib/ingress/stream-watchdog.js +53 -0
- package/server/lib/ingress/upstream-billing.js +15 -0
- package/server/lib/ingress/upstream-error-hints.js +36 -0
- package/server/lib/json-preview.js +58 -0
- package/server/lib/model-ingress.js +26 -1
- package/server/lib/moderation.js +10 -5
- package/server/lib/quota.js +26 -6
- package/server/lib/rect.js +50 -0
- package/server/lib/screen.js +36 -0
- package/server/lib/sheet-state.js +19 -0
- package/server/lib/sketch-array.js +118 -0
- package/server/lib/sketch-hatch.js +99 -0
- package/server/lib/sketch-layout.js +269 -69
- package/server/lib/sketch-shapes.js +152 -0
- package/server/lib/sketch-stencils.js +170 -0
- package/server/lib/state-table.js +237 -0
- package/server/lib/state-trend.js +94 -0
- package/server/lib/state-triggers.js +199 -0
- package/server/lib/tavern-json.js +48 -0
- package/server/lib/user-uploads.js +88 -0
- package/server/projects/board-limits.js +5 -3
- package/server/projects/board-sanitize.js +220 -10
- package/server/projects/board-store.js +95 -6
- package/server/projects/session-jsonl.js +110 -0
- package/server/projects/store.js +56 -3
- package/server/projects/viewpoint-store.js +35 -3
- package/server/projects/workspace-slots.js +30 -0
- package/server/projects/workspace-templates.js +29 -0
- package/server/projects/workspace.js +16 -17
- package/server/runtime/capabilities.js +6 -2
- package/server/shared/locales.js +70 -0
- package/server/shared/messages-en.js +69 -0
- package/server/shared/messages.js +65 -0
- package/server/ws/broker.js +3 -0
- package/server/ws/index.js +43 -3
- package/web/README.md +1 -1
- package/web/dist/assets/{BrowserWindow-BoOBaIyz.js → BrowserWindow-CcaGRWz5.js} +1 -1
- package/web/dist/assets/DeckWindow-BIr2KGFT.js +6 -0
- package/web/dist/assets/{DocxWindow-B_u6aX9o.js → DocxWindow-B9KZmU5z.js} +2 -2
- package/web/dist/assets/{MermaidBlock-BO-46Du5.js → MermaidBlock-DrfRnz8E.js} +43 -43
- package/web/dist/assets/{SiteWindow-ConPmzdM.js → SiteWindow-CJOjCF7d.js} +6 -6
- package/web/dist/assets/{abnfDiagram-VCTEODGH-FCO9yf-n.js → abnfDiagram-VCTEODGH-CUZbQGIg.js} +1 -1
- package/web/dist/assets/{arc-BLcEOSRX.js → arc-C1-HSUny.js} +1 -1
- package/web/dist/assets/{architectureDiagram-5GKGNRK7-kxiX4TeN.js → architectureDiagram-5GKGNRK7-B5nwvu60.js} +2 -2
- package/web/dist/assets/{blockDiagram-NRAW4CY4-CptFksix.js → blockDiagram-NRAW4CY4-uPPuFLwR.js} +1 -1
- package/web/dist/assets/{c4Diagram-UCG6FXSJ-Dxmxj34F.js → c4Diagram-UCG6FXSJ-C0mKBFlz.js} +1 -1
- package/web/dist/assets/channel-BxpWNOGU.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-D9inx5oo.js → chunk-2Q5K7J3B-Cs0rlZ4q.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-DydfHlEc.js → chunk-5VM5RSS4-DtR63oYb.js} +1 -1
- package/web/dist/assets/{chunk-F27PBJKO-BO7GxhSv.js → chunk-F27PBJKO-B3MRdezJ.js} +1 -1
- package/web/dist/assets/{chunk-G27WJ6UU-BzP6YiQ_.js → chunk-G27WJ6UU-BtwntwpS.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-CHcgcd0s.js → chunk-JWPE2WC7-BHsTe5PD.js} +1 -1
- package/web/dist/assets/{chunk-LCL6LL3I-D2Wg5JtX.js → chunk-LCL6LL3I-knY15Qdm.js} +1 -1
- package/web/dist/assets/{chunk-POPQ4Y6H-B2fCRfpM.js → chunk-POPQ4Y6H-C51tgK_I.js} +1 -1
- package/web/dist/assets/{chunk-SVP7TREG-JSLcsRlZ.js → chunk-SVP7TREG-CnwRZmF_.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-BCjvaeHm.js → chunk-XXDRQBXY-BtXzVF-_.js} +1 -1
- package/web/dist/assets/classDiagram-DTDB5LWJ-DUEKmDfo.js +1 -0
- package/web/dist/assets/classDiagram-v2-JRS7N3AN-DUEKmDfo.js +1 -0
- package/web/dist/assets/{cose-bilkent-JH36ORCC-0uKv6oJ7.js → cose-bilkent-JH36ORCC-CzWlGP6i.js} +1 -1
- package/web/dist/assets/{cynefin-OW5HDTMX-Bvt-gEmA.js → cynefin-OW5HDTMX-vK64EGSv.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-5FMLGOSQ-C9Ht02qC.js → cynefinDiagram-5FMLGOSQ-Bs3g1Gvh.js} +1 -1
- package/web/dist/assets/{dagre-3AP2YEHR-BfxZny4s.js → dagre-3AP2YEHR-BhgkYe8V.js} +1 -1
- package/web/dist/assets/{diagram-S7CK7UJ4-CrE0ZETE.js → diagram-S7CK7UJ4-C_6mvD2V.js} +1 -1
- package/web/dist/assets/{diagram-UQ7AKVKN-BO8VQ4Fx.js → diagram-UQ7AKVKN-qW7tssUW.js} +1 -1
- package/web/dist/assets/{diagram-VSXAHHWV-CHUZnR9F.js → diagram-VSXAHHWV-txJPHpvG.js} +1 -1
- package/web/dist/assets/{diagram-VX7I27RA-DYQAz1Vu.js → diagram-VX7I27RA-CQTMsCnL.js} +1 -1
- package/web/dist/assets/{diagram-Z3DM3KII-BDjivgLr.js → diagram-Z3DM3KII--cb1hoE6.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-PWID7BFC-Bpe_QVbH.js → ebnfDiagram-PWID7BFC-Da6lDU77.js} +1 -1
- package/web/dist/assets/{erDiagram-SSCWMZ5O-BxhwuAu1.js → erDiagram-SSCWMZ5O-DGCMrsQn.js} +1 -1
- package/web/dist/assets/{flowDiagram-A5DVABFB-CCw2H08-.js → flowDiagram-A5DVABFB-GdOL_qno.js} +1 -1
- package/web/dist/assets/{ganttDiagram-EL5Y4UJY-DtTc7MyI.js → ganttDiagram-EL5Y4UJY-Cx1F_jBK.js} +4 -4
- package/web/dist/assets/{gitGraphDiagram-WWUBYQGX-BvORxMd2.js → gitGraphDiagram-WWUBYQGX-B5_6JhR3.js} +1 -1
- package/web/dist/assets/hand-CuvZZQGW.webp +0 -0
- package/web/dist/assets/index-BYKfMnmv.js +2759 -0
- package/web/dist/assets/index-CW7VsrVg.css +1 -0
- package/web/dist/assets/{infoDiagram-RXCK75RN-DMcrkypD.js → infoDiagram-RXCK75RN-CjvKpSPA.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-5VMMS53U-CZkTIbe-.js → ishikawaDiagram-5VMMS53U-CxLoAPhx.js} +1 -1
- package/web/dist/assets/{journeyDiagram-EYS64GPL-DR0XazdE.js → journeyDiagram-EYS64GPL-BfvS1upJ.js} +1 -1
- package/web/dist/assets/{kanban-definition-3QL26DDD-DN8VbwVu.js → kanban-definition-3QL26DDD-DKPPu0w4.js} +1 -1
- package/web/dist/assets/{layout-9_paCbyt.js → layout-Dhgck-Zo.js} +1 -1
- package/web/dist/assets/{linear-g6Ct012e.js → linear-DMSQ-aUw.js} +1 -1
- package/web/dist/assets/lxgw-nd-app-bold-D1pKA1bR.woff2 +0 -0
- package/web/dist/assets/lxgw-nd-app-regular-BDe3d26v.woff2 +0 -0
- package/web/dist/assets/lxgw-nd-bold-Bufvlg03.woff2 +0 -0
- package/web/dist/assets/lxgw-nd-regular-DQwoI4OP.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-100-DLPe7tEt.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-101-d3VsZKWT.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-102-ClzJuLq6.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-103-DuDnQ6K-.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-104-kPGW3Eem.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-105-BzD54h-0.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-106-BQ7fIzlx.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-107-D8tDfFHI.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-108-tGhkpwep.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-109-7SMVSrly.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-110-_uDKrFed.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-111-Bio825RE.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-112-D2ee0i-C.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-113-JPFVDrga.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-114-BcfxYA4w.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-115-B1HeeOsd.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-116-hY12tXpq.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-117-BvuZwmre.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-118-tzvs_H7j.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-119-J2DWuYDN.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-21-pDubCJV5.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-22-ChAPMpqp.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-23-BBx3oNpT.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-24-CYt-Oagk.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-25-Cx3j-ZUv.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-26-B9AfZ3LL.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-27-Bh2s9MNE.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-28-C02JPoKP.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-29-T_tbUGhk.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-30-DMfyNzhz.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-31-DFfdzbkg.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-32-Bs1fz2iW.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-33-cpB96OkZ.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-34-BhinPYWX.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-35-D_njRQqA.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-36-DqXlLE3t.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-37-j5LbvMph.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-38-oO4jWdKm.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-39-CabsX4rw.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-4-PatS3YHh.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-40-yrLg3C3e.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-41-DBXmCQhc.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-42-Bqij_UiQ.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-43-D3kp0Ntc.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-44-B_BNGvK4.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-45-CZc0cQWs.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-46-CHQX5Kd9.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-47-Ci8gLrHA.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-48-run3ugD3.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-49-DgfiUb6u.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-5-B0CtGbX_.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-50-CdCG-POr.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-51-L2okeQZ2.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-52-BZAqyGJW.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-53-B0xWqLeU.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-54-BZVQX-EA.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-55-093mkIUJ.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-56-CbhWG8G-.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-57-pLQuvo6c.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-58-BHhoY50x.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-59-D1_6DuIQ.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-6-BnAelmYU.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-60-HDBIuLTZ.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-61-C-c13SeU.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-62-kHRVOhZ2.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-63-C0MCsvFv.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-64-DZekguCj.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-65-CtP6c3IU.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-66-DLVWoMSf.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-67-BVuOnayH.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-68-DpxQ6Mnx.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-69-qesBxLmC.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-70-BBt1xN7h.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-71-BWqZacwB.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-72-B1wP_Yst.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-73-C99T7jNZ.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-74-DrUTsWBe.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-75-C4M5XXQe.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-76-Bq8spVWF.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-77-D9vNupE2.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-78-D6th7Gnu.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-79-bezM85cS.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-80-DD8pnhG2.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-81-N6jtSyBs.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-82-D6c180bZ.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-83-em17AadM.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-84-CJ8gdrMK.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-85-9PPGO1eB.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-86-CjWPf-u5.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-87-8D_X_yOC.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-88-9vtKFPBx.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-89-BhE5jAKX.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-90-D2Npfm5E.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-91-C6Ft1-Eu.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-97-CaExyQP9.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-98-CPoVbsUd.woff2 +0 -0
- package/web/dist/assets/lxgwwenkaiscreen-subset-99-ZUQZGVgH.woff2 +0 -0
- package/web/dist/assets/{mindmap-definition-FBJOCRG2-BGt4omYC.js → mindmap-definition-FBJOCRG2-CalfL_Kn.js} +1 -1
- package/web/dist/assets/{pegDiagram-XKGWAZYB-uSfS-PBE.js → pegDiagram-XKGWAZYB-D9mVWz7l.js} +1 -1
- package/web/dist/assets/pending-edit-apply-kwMFWsUe.js +31 -0
- package/web/dist/assets/{pieDiagram-E7YTZNPT-DpeI935H.js → pieDiagram-E7YTZNPT-DELzbZNS.js} +1 -1
- package/web/dist/assets/{quadrantDiagram-AXDQQJYC-B7IuNvWa.js → quadrantDiagram-AXDQQJYC-TZqp4mfA.js} +1 -1
- package/web/dist/assets/{railroadDiagram-O6MQD6OU-ARzYpPQI.js → railroadDiagram-O6MQD6OU-DxX59f0i.js} +1 -1
- package/web/dist/assets/{requirementDiagram-EFPCY7ZU-Cj4upkAo.js → requirementDiagram-EFPCY7ZU-DFdsJLIe.js} +1 -1
- package/web/dist/assets/{rotate-cw-CXJcyHWk.js → rotate-cw-62liz7LX.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-P5KCCOFB-OaVwSnLP.js → sankeyDiagram-P5KCCOFB-ByYcd3fG.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-WJ2MYXX4-1yrUbq4t.js → sequenceDiagram-WJ2MYXX4-Bj-ml1jS.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-nYH5p9D6.js → sizeCapture-X5ZJPWSS-KhsXThyD.js} +1 -1
- package/web/dist/assets/{square-dashed-mouse-pointer-CTtTVKoH.js → square-dashed-mouse-pointer-D_VrqVpy.js} +1 -1
- package/web/dist/assets/{stateDiagram-HBIQ2CUA-Dj5HvyNq.js → stateDiagram-HBIQ2CUA-BJlpHfWU.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-4QOOHH4V-CR2kYAH2.js +1 -0
- package/web/dist/assets/{swimlanes-XN3QIQJK-umeT0HpB.js → swimlanes-XN3QIQJK-DUB9OJVs.js} +1 -1
- package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BitOxnG2.js +8 -0
- package/web/dist/assets/{timeline-definition-24CTP7MA-MqnQfHKB.js → timeline-definition-24CTP7MA-C03NX82A.js} +1 -1
- package/web/dist/assets/{vennDiagram-4TSXK5OY-b2liyZra.js → vennDiagram-4TSXK5OY-B9pjzngp.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-VM6X3IG4-QMK-zNh1.js → wardleyDiagram-VM6X3IG4-UqWq_mcT.js} +1 -1
- package/web/dist/assets/{xychartDiagram-S5SC5T6Z-D5Mc6tfA.js → xychartDiagram-S5SC5T6Z-D2pDKtOv.js} +1 -1
- package/web/dist/index.html +2 -2
- package/server/engine/mcp/tools/arrange-on-board.js +0 -126
- package/server/engine/mcp/tools/create-on-board.js +0 -152
- package/server/engine/mcp/tools/edit-sketch.js +0 -167
- package/server/engine/mcp/tools/relate-on-board.js +0 -150
- package/server/engine/mcp/tools/sketch-on-board.js +0 -345
- package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +0 -235
- 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
- package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +0 -268
- package/web/dist/assets/DeckWindow-Bg3rgkvi.js +0 -11
- package/web/dist/assets/channel-DFyiuGL0.js +0 -1
- package/web/dist/assets/classDiagram-DTDB5LWJ-Aw1f8z-b.js +0 -1
- package/web/dist/assets/classDiagram-v2-JRS7N3AN-Aw1f8z-b.js +0 -1
- package/web/dist/assets/index-C0KUf6Wd.css +0 -1
- package/web/dist/assets/index-DGG5wIZs.js +0 -2118
- package/web/dist/assets/lxgw-nd-bold-DbRfiTgo.woff2 +0 -0
- package/web/dist/assets/lxgw-nd-regular-CpMw9Ehg.woff2 +0 -0
- package/web/dist/assets/pending-edit-apply-uP4uvrOD.js +0 -31
- package/web/dist/assets/stateDiagram-v2-4QOOHH4V-kJ1m_zJT.js +0 -1
- 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
|
+
}
|