termdock 1.4.309 → 1.4.311
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.en.md +3 -1
- package/README.md +3 -1
- package/THIRD_PARTY_NOTICES.md +30 -0
- package/dist/client/assets/{App-ggIA_xqR.js → App-ChS2-lkz.js} +62 -55
- package/dist/client/assets/App-ChS2-lkz.js.br +0 -0
- package/dist/client/assets/App-ChS2-lkz.js.gz +0 -0
- package/dist/client/assets/{ArchitecturePanel-Cmpmp6FS.css → ArchitecturePanel-B_BDrrfR.css} +1 -1
- package/dist/client/assets/ArchitecturePanel-B_BDrrfR.css.br +0 -0
- package/dist/client/assets/ArchitecturePanel-B_BDrrfR.css.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-JtjyyJoW.js +9 -0
- package/dist/client/assets/ArchitecturePanel-JtjyyJoW.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-JtjyyJoW.js.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-D1Puc7B3.js → ChangeWalkthroughPanel-BlQ5-VPp.js} +2 -2
- package/dist/client/assets/ChangeWalkthroughPanel-BlQ5-VPp.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BlQ5-VPp.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-aFLAMqDr.js +1 -0
- package/dist/client/assets/CollaborationMainWorkspace-aFLAMqDr.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-aFLAMqDr.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-BDQMRhU9.js +2 -0
- package/dist/client/assets/{ComputerControlView-D5Lw9sNA.js → ComputerControlView-TAKDbBUl.js} +2 -2
- package/dist/client/assets/ComputerControlView-TAKDbBUl.js.br +0 -0
- package/dist/client/assets/ComputerControlView-TAKDbBUl.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-DYJrGfiG.js → DagPlayground-CR4uDFOx.js} +1 -1
- package/dist/client/assets/DagPlayground-CR4uDFOx.js.br +0 -0
- package/dist/client/assets/DagPlayground-CR4uDFOx.js.gz +0 -0
- package/dist/client/assets/{DiffLab-5zdcFW3z.js → DiffLab-BqeHMDOy.js} +1 -1
- package/dist/client/assets/DiffLab-BqeHMDOy.js.br +0 -0
- package/dist/client/assets/DiffLab-BqeHMDOy.js.gz +0 -0
- package/dist/client/assets/{DiffReview-BBBC_NjV.js → DiffReview-DqrOcRfP.js} +1 -1
- package/dist/client/assets/DiffReview-DqrOcRfP.js.br +0 -0
- package/dist/client/assets/DiffReview-DqrOcRfP.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-CbhosFSB.js → DiffReviewLab-CEYpbrL6.js} +2 -2
- package/dist/client/assets/DiffReviewLab-CEYpbrL6.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-CEYpbrL6.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-BuQwFQ4V.js → DiffViewer-F-uQSmhG.js} +8 -8
- package/dist/client/assets/DiffViewer-F-uQSmhG.js.br +0 -0
- package/dist/client/assets/DiffViewer-F-uQSmhG.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-nO2VmqdP.js → ModelPreview-KmXFkgij.js} +1 -1
- package/dist/client/assets/ModelPreview-KmXFkgij.js.br +0 -0
- package/dist/client/assets/ModelPreview-KmXFkgij.js.gz +0 -0
- package/dist/client/assets/RightSidebar-VMdXpo4h.js +1677 -0
- package/dist/client/assets/RightSidebar-VMdXpo4h.js.br +0 -0
- package/dist/client/assets/RightSidebar-VMdXpo4h.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-yi0K8GDG.js → abnfDiagram-VCTEODGH-CAMk1C0N.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-CAMk1C0N.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-CAMk1C0N.js.gz +0 -0
- package/dist/client/assets/{app-B6FdOF6K.js → app-CHxLSuGQ.js} +4 -4
- package/dist/client/assets/app-CHxLSuGQ.js.br +0 -0
- package/dist/client/assets/app-CHxLSuGQ.js.gz +0 -0
- package/dist/client/assets/{arc-_nLI_-XZ.js → arc-CYG_-1vC.js} +1 -1
- package/dist/client/assets/arc-CYG_-1vC.js.br +0 -0
- package/dist/client/assets/arc-CYG_-1vC.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-CikrA78O.js → architectureDiagram-5GKGNRK7-BqHzezLR.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-BqHzezLR.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-BqHzezLR.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-CWJq6qJ7.js → blockDiagram-I7D4REHJ-D6GWRO_Z.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-D6GWRO_Z.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-D6GWRO_Z.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-gzPzLGvs.js → c4Diagram-7LVT6UL2-CutmGIGb.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-CutmGIGb.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-CutmGIGb.js.gz +0 -0
- package/dist/client/assets/channel-B3BKM2gg.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-CfX9l18E.js → chunk-2Q5K7J3B-DwiUpGFu.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-JSV1yrXM.js → chunk-5VM5RSS4-C35gLPSH.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-CQ0LXeap.js → chunk-F27PBJKO-CNptP19P.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-CNptP19P.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-CNptP19P.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-CJOEPkK9.js → chunk-IMKFNOWR-DHdpB9A0.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-DHdpB9A0.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-DHdpB9A0.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-Dpp7Xu_E.js → chunk-JWPE2WC7-CrKSOzdE.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-9hDa1b8z.js → chunk-POPQ4Y6H-CiZGLRSk.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-BskkG9gg.js → chunk-SVP7TREG-B70pX8OB.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-B70pX8OB.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-B70pX8OB.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-DyzuP-9I.js → chunk-TICWLB2K-Ba1mI99F.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-Ba1mI99F.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-Ba1mI99F.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-C31_1i6z.js → chunk-XXDRQBXY-DmKIl18h.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-CS4V_MQS.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-CS4V_MQS.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-Bm-ESDOc.js → cose-bilkent-JH36ORCC-C-NMDUHW.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-C-NMDUHW.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-C-NMDUHW.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-DbFWAPvX.js → cynefin-OW5HDTMX-CZgEc1ab.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-CZgEc1ab.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-CZgEc1ab.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-Ca0IKRdH.js → cynefinDiagram-5FMLGOSQ-CHQeSNm2.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CHQeSNm2.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CHQeSNm2.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-D_5uEMjJ.js → dagre-GXQ25YYZ-B_jkMdPS.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-B_jkMdPS.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-B_jkMdPS.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-DWVpndRP.js → diagram-S7CK7UJ4-nv6Lnsrp.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-nv6Lnsrp.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-nv6Lnsrp.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-DmYTya3w.js → diagram-UQ7AKVKN-BZf2fzaA.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-BZf2fzaA.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BZf2fzaA.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-BEA6kqNj.js → diagram-VSXAHHWV-CxL0_BH8.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-CxL0_BH8.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CxL0_BH8.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-CU9vP0P0.js → diagram-VX7I27RA-Dm9MqpIE.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-Dm9MqpIE.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-Dm9MqpIE.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-B_GvE2g-.js → diagram-Z3DM3KII-BIfTa7P5.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-BIfTa7P5.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-BIfTa7P5.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-BLrPQuLE.js → ebnfDiagram-PWID7BFC-D4XhCDFS.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-D4XhCDFS.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-D4XhCDFS.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-DlXgoyZ-.js → erDiagram-RLTQ6QDP-Dxd48dXC.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-Dxd48dXC.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-Dxd48dXC.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-fPHWxEDZ.js → flowDiagram-HODETNUW-BXBMsEmE.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-BXBMsEmE.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-BXBMsEmE.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-B8MOv6ug.js → ganttDiagram-EL5Y4UJY-CBfDd-KZ.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CBfDd-KZ.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CBfDd-KZ.js.gz +0 -0
- package/dist/client/assets/{gestureArbiter-JW8vcVRm.js → gestureArbiter-DXLmNGli.js} +1 -1
- package/dist/client/assets/gestureArbiter-DXLmNGli.js.br +0 -0
- package/dist/client/assets/gestureArbiter-DXLmNGli.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-lH9gSoFx.js → gitGraphDiagram-WWUBYQGX-DtLM68fN.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DtLM68fN.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DtLM68fN.js.gz +0 -0
- package/dist/client/assets/index-KuPaZyuZ.css +1 -0
- package/dist/client/assets/index-KuPaZyuZ.css.br +0 -0
- package/dist/client/assets/{index-CNY7CTVa.css.gz → index-KuPaZyuZ.css.gz} +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-CSG29Ty_.js → infoDiagram-27XIBGKW-FRhqdJvW.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-CcwSE5qV.js → ishikawaDiagram-5VMMS53U-DAHBvAZ-.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-DAHBvAZ-.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-DAHBvAZ-.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-CXFUeP2c.js → journeyDiagram-3NMN7TZE-CVuWuL3Z.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CVuWuL3Z.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CVuWuL3Z.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-KEHwljd2.js → kanban-definition-UXKFOSKX-ByQSUVJV.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-ByQSUVJV.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-ByQSUVJV.js.gz +0 -0
- package/dist/client/assets/{layout-DqYx0MnO.js → layout-Bb3Nb1c4.js} +1 -1
- package/dist/client/assets/layout-Bb3Nb1c4.js.br +0 -0
- package/dist/client/assets/layout-Bb3Nb1c4.js.gz +0 -0
- package/dist/client/assets/{linear-BnG3XaJW.js → linear-DHMMQlOo.js} +1 -1
- package/dist/client/assets/linear-DHMMQlOo.js.br +0 -0
- package/dist/client/assets/linear-DHMMQlOo.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-Ce00rdM6.js → mermaid.core-BYfniK0H.js} +5 -5
- package/dist/client/assets/mermaid.core-BYfniK0H.js.br +0 -0
- package/dist/client/assets/mermaid.core-BYfniK0H.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-D0hOW4SY.js → mindmap-definition-YA3MSWOX-EDLD5AyH.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-EDLD5AyH.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-EDLD5AyH.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-DCrtirwT.js +2 -0
- package/dist/client/assets/passwordBootstrap-DCrtirwT.js.br +0 -0
- package/dist/client/assets/{passwordBootstrap-Bncb8qF_.js.gz → passwordBootstrap-DCrtirwT.js.gz} +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-UKp9t91x.js → pegDiagram-XKGWAZYB-BHYhkqJN.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-BHYhkqJN.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BHYhkqJN.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-Dif97wbH.js → pieDiagram-E7YTZNPT-BUIJlswC.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-BUIJlswC.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-BUIJlswC.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-BPyHtH91.js → quadrantDiagram-AXDQQJYC-D8Im-7yC.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-D8Im-7yC.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-D8Im-7yC.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-Wleq9LmS.js → railroadDiagram-O6MQD6OU-D7n6z2G1.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-D7n6z2G1.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-D7n6z2G1.js.gz +0 -0
- package/dist/client/assets/{rdpSession-CobFMSUs.js → rdpSession-DJlNIbVk.js} +1 -1
- package/dist/client/assets/rdpSession-DJlNIbVk.js.br +0 -0
- package/dist/client/assets/rdpSession-DJlNIbVk.js.gz +0 -0
- package/dist/client/assets/{referenceLongPress-bZ0xp-v2.js → referenceLongPress-DFSrssSr.js} +1 -1
- package/dist/client/assets/referenceLongPress-DFSrssSr.js.br +0 -0
- package/dist/client/assets/referenceLongPress-DFSrssSr.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-BL4pp0Ta.js → requirementDiagram-BXWQKSXE-Ki0rR-F7.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-Ki0rR-F7.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-Ki0rR-F7.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-CV1cKx37.js → sankeyDiagram-P5KCCOFB-36NmMqsW.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-36NmMqsW.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-36NmMqsW.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-BF2InLwV.js → sequenceDiagram-WJ2MYXX4-5vsPuFyO.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-5vsPuFyO.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-5vsPuFyO.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-CldUyStR.js → sizeCapture-INFHLROL-DbAulwBg.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-DbAulwBg.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-DbAulwBg.js.gz +0 -0
- package/dist/client/assets/{sparkles-Cg-yPdE2.js → sparkles-CapLTKAs.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-CByehbEd.js → stateDiagram-D77RDMKH-Bgwe0g5S.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-Bgwe0g5S.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-Bgwe0g5S.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-Bg6S3Zm0.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH--1_7YTL4.js → swimlanes-42K2YHIH-COGAWe-g.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-COGAWe-g.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-COGAWe-g.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DwBBOocG.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-CXfXJFK6.js → timeline-definition-24CTP7MA-C4gt5z68.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-C4gt5z68.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-C4gt5z68.js.gz +0 -0
- package/dist/client/assets/{triangle-alert-CbMRJ6sx.js → triangle-alert-xPHw25jD.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-byr6UQpL.js → vennDiagram-4TSXK5OY-CIEXh9oD.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-CIEXh9oD.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-CIEXh9oD.js.gz +0 -0
- package/dist/client/assets/{videoCompression-ivJAy00d.js → videoCompression-CUkCS2-q.js} +1 -1
- package/dist/client/assets/videoCompression-CUkCS2-q.js.br +0 -0
- package/dist/client/assets/videoCompression-CUkCS2-q.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-FJduk-7s.js → wardleyDiagram-VM6X3IG4-gQhcW1vd.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-gQhcW1vd.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-gQhcW1vd.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-Ct1fBPFS.js → xychartDiagram-S5SC5T6Z-BmIcdejz.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BmIcdejz.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BmIcdejz.js.gz +0 -0
- package/dist/client/index.html +2 -2
- package/dist/client/index.html.br +0 -0
- package/dist/client/index.html.gz +0 -0
- package/dist/client/sw.js +1 -1
- package/dist/client/sw.js.br +0 -0
- package/dist/client/sw.js.gz +0 -0
- package/dist/server/agent/collaborationCli.js +6 -4
- package/dist/server/agent/collaborationDeliveryWorker.js +3 -97
- package/dist/server/agent/collaborationTmuxDelivery.js +23 -51
- package/dist/server/agent/integrationCli.js +23 -0
- package/dist/server/agent/integrationServer.js +7 -0
- package/dist/server/agent/integrationSessions.js +24 -1
- package/dist/server/agent/promptDelivery.js +20 -7
- package/dist/server/agent/sessionLifecycle.js +2 -2
- package/dist/server/architecture/cli.js +320 -0
- package/dist/server/architecture/document.js +328 -0
- package/dist/server/architecture/documentDiff.js +39 -0
- package/dist/server/architecture/fieldMutation.js +52 -0
- package/dist/server/architecture/flowchart.js +62 -0
- package/dist/server/architecture/mermaidDiff.js +85 -0
- package/dist/server/architecture/routes.js +117 -0
- package/dist/server/architecture/store.js +502 -0
- package/dist/server/cli.js +7 -1
- package/dist/server/entry.js +2 -0
- package/dist/server/federation/accessPolicy.js +9 -1
- package/dist/server/routes/terminal.js +8 -12
- package/docs/integrations.md +45 -1
- package/package.json +3 -1
- package/runtime-manifest.json +4 -4
- package/dist/client/assets/App-ggIA_xqR.js.br +0 -0
- package/dist/client/assets/App-ggIA_xqR.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-Cmpmp6FS.css.br +0 -0
- package/dist/client/assets/ArchitecturePanel-Cmpmp6FS.css.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-CoBspW_C.js +0 -12
- package/dist/client/assets/ArchitecturePanel-CoBspW_C.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-CoBspW_C.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-D1Puc7B3.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-D1Puc7B3.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-BPyK5Xs8.js +0 -1
- package/dist/client/assets/CollaborationMainWorkspace-BPyK5Xs8.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-BPyK5Xs8.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-CTSJJqze.js +0 -2
- package/dist/client/assets/ComputerControlView-D5Lw9sNA.js.br +0 -0
- package/dist/client/assets/ComputerControlView-D5Lw9sNA.js.gz +0 -0
- package/dist/client/assets/DagPlayground-DYJrGfiG.js.br +0 -0
- package/dist/client/assets/DagPlayground-DYJrGfiG.js.gz +0 -0
- package/dist/client/assets/DiffLab-5zdcFW3z.js.br +0 -0
- package/dist/client/assets/DiffLab-5zdcFW3z.js.gz +0 -0
- package/dist/client/assets/DiffReview-BBBC_NjV.js.br +0 -0
- package/dist/client/assets/DiffReview-BBBC_NjV.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-CbhosFSB.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-CbhosFSB.js.gz +0 -0
- package/dist/client/assets/DiffViewer-BuQwFQ4V.js.br +0 -0
- package/dist/client/assets/DiffViewer-BuQwFQ4V.js.gz +0 -0
- package/dist/client/assets/ModelPreview-nO2VmqdP.js.br +0 -0
- package/dist/client/assets/ModelPreview-nO2VmqdP.js.gz +0 -0
- package/dist/client/assets/RightSidebar-BoNzpS2K.js +0 -1677
- package/dist/client/assets/RightSidebar-BoNzpS2K.js.br +0 -0
- package/dist/client/assets/RightSidebar-BoNzpS2K.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-yi0K8GDG.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-yi0K8GDG.js.gz +0 -0
- package/dist/client/assets/app-B6FdOF6K.js.br +0 -0
- package/dist/client/assets/app-B6FdOF6K.js.gz +0 -0
- package/dist/client/assets/arc-_nLI_-XZ.js.br +0 -0
- package/dist/client/assets/arc-_nLI_-XZ.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CikrA78O.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CikrA78O.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-CWJq6qJ7.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-CWJq6qJ7.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-gzPzLGvs.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-gzPzLGvs.js.gz +0 -0
- package/dist/client/assets/channel-BYsFmOmF.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-CQ0LXeap.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-CQ0LXeap.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CJOEPkK9.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CJOEPkK9.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-BskkG9gg.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-BskkG9gg.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-DyzuP-9I.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-DyzuP-9I.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-BnmZv6Ns.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-BnmZv6Ns.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-Bm-ESDOc.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-Bm-ESDOc.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-DbFWAPvX.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-DbFWAPvX.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Ca0IKRdH.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Ca0IKRdH.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-D_5uEMjJ.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-D_5uEMjJ.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DWVpndRP.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DWVpndRP.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-DmYTya3w.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-DmYTya3w.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-BEA6kqNj.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-BEA6kqNj.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CU9vP0P0.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CU9vP0P0.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-B_GvE2g-.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-B_GvE2g-.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BLrPQuLE.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BLrPQuLE.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DlXgoyZ-.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DlXgoyZ-.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-fPHWxEDZ.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-fPHWxEDZ.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-B8MOv6ug.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-B8MOv6ug.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-JW8vcVRm.js.br +0 -0
- package/dist/client/assets/gestureArbiter-JW8vcVRm.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-lH9gSoFx.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-lH9gSoFx.js.gz +0 -0
- package/dist/client/assets/index-CNY7CTVa.css +0 -1
- package/dist/client/assets/index-CNY7CTVa.css.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CcwSE5qV.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CcwSE5qV.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CXFUeP2c.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CXFUeP2c.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-KEHwljd2.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-KEHwljd2.js.gz +0 -0
- package/dist/client/assets/layout-DqYx0MnO.js.br +0 -0
- package/dist/client/assets/layout-DqYx0MnO.js.gz +0 -0
- package/dist/client/assets/linear-BnG3XaJW.js.br +0 -0
- package/dist/client/assets/linear-BnG3XaJW.js.gz +0 -0
- package/dist/client/assets/mermaid.core-Ce00rdM6.js.br +0 -0
- package/dist/client/assets/mermaid.core-Ce00rdM6.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-D0hOW4SY.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-D0hOW4SY.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-Bncb8qF_.js +0 -2
- package/dist/client/assets/passwordBootstrap-Bncb8qF_.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-UKp9t91x.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-UKp9t91x.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-Dif97wbH.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-Dif97wbH.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BPyHtH91.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BPyHtH91.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-Wleq9LmS.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-Wleq9LmS.js.gz +0 -0
- package/dist/client/assets/rdpSession-CobFMSUs.js.br +0 -0
- package/dist/client/assets/rdpSession-CobFMSUs.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-bZ0xp-v2.js.br +0 -0
- package/dist/client/assets/referenceLongPress-bZ0xp-v2.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BL4pp0Ta.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BL4pp0Ta.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CV1cKx37.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CV1cKx37.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BF2InLwV.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BF2InLwV.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CldUyStR.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CldUyStR.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-CByehbEd.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-CByehbEd.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CTcrJ-w0.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH--1_7YTL4.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH--1_7YTL4.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-RSMV9AhO.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-CXfXJFK6.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-CXfXJFK6.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-byr6UQpL.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-byr6UQpL.js.gz +0 -0
- package/dist/client/assets/videoCompression-ivJAy00d.js.br +0 -0
- package/dist/client/assets/videoCompression-ivJAy00d.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-FJduk-7s.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-FJduk-7s.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-Ct1fBPFS.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-Ct1fBPFS.js.gz +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { pathValidator } from '../utils/pathValidator.js';
|
|
3
|
+
import { ARCHITECTURE_FIELDS, ARCHITECTURE_FILE } from './document.js';
|
|
4
|
+
import { ArchitectureConflict, ArchitectureStore } from './store.js';
|
|
5
|
+
import { updateArchitectureField } from './fieldMutation.js';
|
|
6
|
+
/** Mounted below /api/terminal, after its existing auth, CSRF and encrypted transport. */
|
|
7
|
+
export function architectureRoutes() {
|
|
8
|
+
const router = Router();
|
|
9
|
+
router.use(async (req, res, next) => {
|
|
10
|
+
try {
|
|
11
|
+
const requested = req.method === 'GET' ? req.query.root : req.body?.root;
|
|
12
|
+
if (typeof requested !== 'string') {
|
|
13
|
+
res.status(400).json({ error: 'Project root is required' });
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const root = await (req.pathValidator ?? pathValidator).validateAsync(requested);
|
|
17
|
+
res.locals.architectureStore = new ArchitectureStore(root);
|
|
18
|
+
next();
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
res.status(403).json({ error: error instanceof Error ? error.message : 'Project cannot be accessed' });
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
const store = (res) => res.locals.architectureStore;
|
|
25
|
+
router.get('/documents', async (_req, res, next) => { try {
|
|
26
|
+
res.json({ files: await store(res).list() });
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
next(error);
|
|
30
|
+
} });
|
|
31
|
+
router.get('/document', async (req, res, next) => { try {
|
|
32
|
+
res.json(await store(res).read(typeof req.query.file === 'string' ? req.query.file : ARCHITECTURE_FILE));
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
next(error);
|
|
36
|
+
} });
|
|
37
|
+
router.put('/document', async (req, res, next) => { try {
|
|
38
|
+
res.json(await store(res).save(req.body.file ?? ARCHITECTURE_FILE, req.body.document, req.body.revision));
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
next(error);
|
|
42
|
+
} });
|
|
43
|
+
router.get('/history', async (req, res, next) => { try {
|
|
44
|
+
res.json({ versions: await store(res).history(String(req.query.file ?? ARCHITECTURE_FILE)) });
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
next(error);
|
|
48
|
+
} });
|
|
49
|
+
router.get('/version', async (req, res, next) => { try {
|
|
50
|
+
res.json({ document: await store(res).historical(String(req.query.file), String(req.query.id)) });
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
next(error);
|
|
54
|
+
} });
|
|
55
|
+
router.get('/bundle', async (req, res, next) => {
|
|
56
|
+
try {
|
|
57
|
+
const file = String(req.query.file ?? ARCHITECTURE_FILE), storage = store(res);
|
|
58
|
+
const loaded = await storage.read(file);
|
|
59
|
+
if (!loaded.document) {
|
|
60
|
+
res.status(404).json({ error: 'Architecture document not found' });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
res.json(file === '.omm' ? await storage.exportOmmBundle() : await storage.exportOmmBundle(loaded.document));
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
next(error);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
router.put('/bundle', async (req, res, next) => { try {
|
|
70
|
+
await store(res).importOmmBundle(req.body.bundle, req.body.revision);
|
|
71
|
+
res.json(await store(res).read('.omm'));
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
next(error);
|
|
75
|
+
} });
|
|
76
|
+
router.put('/field', async (req, res, next) => {
|
|
77
|
+
try {
|
|
78
|
+
const { file = ARCHITECTURE_FILE, perspective, node, field, value, revision } = req.body;
|
|
79
|
+
if (!ARCHITECTURE_FIELDS.includes(field) || typeof value !== 'string' || value.length > 100_000) {
|
|
80
|
+
res.status(400).json({ error: 'Invalid architecture field' });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const storage = store(res);
|
|
84
|
+
if (file === '.omm') {
|
|
85
|
+
const loaded = await storage.read('.omm'), view = loaded.document?.perspectives.find(view => view.id === perspective);
|
|
86
|
+
const target = node ? view?.nodes.find(item => item.id === node) : view;
|
|
87
|
+
if (!view || !target) {
|
|
88
|
+
res.status(404).json({ error: 'Architecture element not found' });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Resolve opaque viewer IDs back to .omm paths through the store's stable IDs.
|
|
92
|
+
const { createHash } = await import('node:crypto');
|
|
93
|
+
const element = node ? ('documentPath' in target && typeof target.documentPath === 'string' && target.documentPath.startsWith(`${view.id}/`) && `e_${createHash('sha256').update(target.documentPath).digest('hex').slice(0, 20)}` === node ? target.documentPath : undefined) : view.id;
|
|
94
|
+
if (!element) {
|
|
95
|
+
res.status(404).json({ error: 'Architecture element has no document yet' });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
res.json(await storage.writeOmmField(element, field, value, revision));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const loaded = await storage.read(file);
|
|
102
|
+
if (!loaded.document) {
|
|
103
|
+
res.status(404).json({ error: 'Architecture document not found' });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const document = updateArchitectureField(loaded.document, perspective, node, field, value);
|
|
107
|
+
res.json(await storage.save(file, document, revision));
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
next(error);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
router.use((error, _req, res, _next) => {
|
|
114
|
+
res.status(error instanceof ArchitectureConflict ? 409 : 400).json({ error: error instanceof Error ? error.message : 'Architecture operation failed' });
|
|
115
|
+
});
|
|
116
|
+
return router;
|
|
117
|
+
}
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
5
|
+
import { execFile } from 'node:child_process';
|
|
6
|
+
import { promisify } from 'node:util';
|
|
7
|
+
import YAML from 'yaml';
|
|
8
|
+
import { ARCHITECTURE_FIELDS, ARCHITECTURE_FILE, ARCHITECTURE_DIRECTORY, analysisFile, architectureScope, isArchitectureFile, isArchitectureSourcePath, parseArchitecture } from './document.js';
|
|
9
|
+
import { parseFlowchart } from './flowchart.js';
|
|
10
|
+
import { diffArchitecture } from './documentDiff.js';
|
|
11
|
+
const runFile = promisify(execFile);
|
|
12
|
+
const digest = (value) => createHash('sha256').update(value).digest('hex');
|
|
13
|
+
const missing = (error) => error?.code === 'ENOENT';
|
|
14
|
+
const safeName = (name) => /^[a-zA-Z][a-zA-Z0-9_-]{0,79}$/.test(name);
|
|
15
|
+
const names = Object.fromEntries(ARCHITECTURE_FIELDS.map(field => [field, field === 'diagram' ? 'diagram.mmd' : `${field}.md`]));
|
|
16
|
+
export class ArchitectureConflict extends Error {
|
|
17
|
+
}
|
|
18
|
+
/** Bound all document IO to real project directories; refuse symlinks in document trees. */
|
|
19
|
+
export class ArchitectureStore {
|
|
20
|
+
root;
|
|
21
|
+
constructor(root) {
|
|
22
|
+
this.root = root;
|
|
23
|
+
}
|
|
24
|
+
async bounded(relative, create = false) {
|
|
25
|
+
if (!relative || relative.startsWith('/') || /[\\\x00-\x1f]/.test(relative) || relative.split('/').some(part => !part || part === '.' || part === '..'))
|
|
26
|
+
throw new Error('Unsafe architecture path');
|
|
27
|
+
const root = await fs.realpath(this.root);
|
|
28
|
+
let current = root;
|
|
29
|
+
const parts = relative.split('/');
|
|
30
|
+
for (let index = 0; index < parts.length; index++) {
|
|
31
|
+
current = path.join(current, parts[index]);
|
|
32
|
+
try {
|
|
33
|
+
const stat = await fs.lstat(current);
|
|
34
|
+
if (stat.isSymbolicLink())
|
|
35
|
+
throw new Error('Architecture paths cannot contain symlinks');
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (!missing(error))
|
|
39
|
+
throw error;
|
|
40
|
+
if (create && index < parts.length - 1)
|
|
41
|
+
await fs.mkdir(current).catch(async (error) => { if (error.code !== 'EEXIST' || !(await fs.lstat(current)).isDirectory())
|
|
42
|
+
throw error; });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return current;
|
|
46
|
+
}
|
|
47
|
+
async text(relative) {
|
|
48
|
+
const target = await this.bounded(relative);
|
|
49
|
+
try {
|
|
50
|
+
const stat = await fs.stat(target);
|
|
51
|
+
if (!stat.isFile() || stat.size > 5_000_000)
|
|
52
|
+
throw new Error('Architecture file exceeds the read limit');
|
|
53
|
+
return await fs.readFile(target, 'utf8');
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
if (missing(error))
|
|
57
|
+
return null;
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async atomic(relative, content) {
|
|
62
|
+
const target = await this.bounded(relative, true), temp = `${target}.${randomUUID()}.tmp`;
|
|
63
|
+
try {
|
|
64
|
+
await fs.writeFile(temp, content, { flag: 'wx', mode: 0o600 });
|
|
65
|
+
await fs.rename(temp, target);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await fs.rm(temp, { force: true });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async locked(operation) {
|
|
72
|
+
const lock = await this.bounded('.termdock/architecture.lock', true);
|
|
73
|
+
let handle;
|
|
74
|
+
const deadline = Date.now() + 10_000;
|
|
75
|
+
while (!handle) {
|
|
76
|
+
try {
|
|
77
|
+
handle = await fs.open(lock, 'wx', 0o600);
|
|
78
|
+
await handle.writeFile(JSON.stringify({ pid: process.pid, time: Date.now() }));
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (error.code !== 'EEXIST')
|
|
82
|
+
throw error;
|
|
83
|
+
if (Date.now() >= deadline)
|
|
84
|
+
throw new ArchitectureConflict('Another architecture update is in progress');
|
|
85
|
+
try {
|
|
86
|
+
const owner = JSON.parse(await fs.readFile(lock, 'utf8'));
|
|
87
|
+
try {
|
|
88
|
+
process.kill(owner.pid, 0);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (error.code === 'ESRCH') {
|
|
92
|
+
await fs.rm(lock, { force: true });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch { /* A newly created lock may not yet contain its owner. */ }
|
|
98
|
+
await new Promise(resolve => setTimeout(resolve, 30));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
return await operation();
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
await handle.close();
|
|
106
|
+
await fs.rm(lock, { force: true });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async list() {
|
|
110
|
+
const result = [];
|
|
111
|
+
if (await this.text(ARCHITECTURE_FILE))
|
|
112
|
+
result.push(ARCHITECTURE_FILE);
|
|
113
|
+
const folder = await this.bounded(ARCHITECTURE_DIRECTORY);
|
|
114
|
+
try {
|
|
115
|
+
for (const entry of await fs.readdir(folder, { withFileTypes: true }))
|
|
116
|
+
if (entry.isFile() && isArchitectureFile(`${ARCHITECTURE_DIRECTORY}/${entry.name}`))
|
|
117
|
+
result.push(`${ARCHITECTURE_DIRECTORY}/${entry.name}`);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
if (!missing(error))
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
if ((await fs.readdir(await this.bounded('.omm'), { withFileTypes: true })).some(entry => entry.isDirectory() && safeName(entry.name)))
|
|
125
|
+
result.push('.omm');
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
if (!missing(error))
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
return result.sort();
|
|
132
|
+
}
|
|
133
|
+
async read(file = ARCHITECTURE_FILE) {
|
|
134
|
+
if (!isArchitectureFile(file))
|
|
135
|
+
throw new Error('Invalid architecture document');
|
|
136
|
+
if (file === '.omm')
|
|
137
|
+
return this.readOmm();
|
|
138
|
+
const content = await this.text(file);
|
|
139
|
+
return { document: content ? parseArchitecture(content) : null, revision: content ? digest(content) : null };
|
|
140
|
+
}
|
|
141
|
+
async git() {
|
|
142
|
+
try {
|
|
143
|
+
const [{ stdout: gitCommit }, { stdout: gitBranch }] = await Promise.all([runFile('git', ['rev-parse', '--short', 'HEAD'], { cwd: this.root, timeout: 2000 }), runFile('git', ['branch', '--show-current'], { cwd: this.root, timeout: 2000 })]);
|
|
144
|
+
return { gitCommit: gitCommit.trim(), gitBranch: gitBranch.trim() };
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return {};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
historyDirectory(file) { return `.termdock/architecture-history/${digest(file).slice(0, 24)}`; }
|
|
151
|
+
async archive(file, previous) {
|
|
152
|
+
if (!previous.document)
|
|
153
|
+
return;
|
|
154
|
+
const folder = this.historyDirectory(file);
|
|
155
|
+
const id = `${Date.now()}-${randomUUID()}`;
|
|
156
|
+
await this.atomic(`${folder}/${id}.json`, JSON.stringify(previous.document, null, 2));
|
|
157
|
+
const entries = (await fs.readdir(await this.bounded(folder))).filter(name => /^\d+-[a-f0-9-]+\.json$/.test(name)).sort().reverse();
|
|
158
|
+
for (const old of entries.slice(30))
|
|
159
|
+
await fs.unlink(await this.bounded(`${folder}/${old}`));
|
|
160
|
+
}
|
|
161
|
+
async history(file) {
|
|
162
|
+
if (!isArchitectureFile(file))
|
|
163
|
+
throw new Error('Invalid architecture document');
|
|
164
|
+
const current = await this.read(file);
|
|
165
|
+
if (!current.document)
|
|
166
|
+
return [];
|
|
167
|
+
const folder = this.historyDirectory(file);
|
|
168
|
+
let entries;
|
|
169
|
+
try {
|
|
170
|
+
entries = (await fs.readdir(await this.bounded(folder))).filter(name => /^\d+-[a-f0-9-]+\.json$/.test(name)).sort().reverse().slice(0, 30);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (missing(error))
|
|
174
|
+
return [];
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
return Promise.all(entries.map(async (name) => {
|
|
178
|
+
const previous = parseArchitecture((await this.text(`${folder}/${name}`)));
|
|
179
|
+
return { id: name.slice(0, -5), savedAt: previous.generatedAt, changes: diffArchitecture(previous, current.document) };
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
async historical(file, id) {
|
|
183
|
+
if (!isArchitectureFile(file) || !/^\d+-[a-f0-9-]+$/.test(id))
|
|
184
|
+
throw new Error('Invalid architecture version');
|
|
185
|
+
const content = await this.text(`${this.historyDirectory(file)}/${id}.json`);
|
|
186
|
+
if (!content)
|
|
187
|
+
throw new Error('Architecture version not found');
|
|
188
|
+
return parseArchitecture(content);
|
|
189
|
+
}
|
|
190
|
+
async save(file, input, expectedRevision) {
|
|
191
|
+
if (!isArchitectureFile(file) || file === '.omm')
|
|
192
|
+
throw new Error('Use field writes for .omm documents');
|
|
193
|
+
const document = parseArchitecture(JSON.stringify(input));
|
|
194
|
+
if (document.analysis && analysisFile(document.analysis) !== file)
|
|
195
|
+
throw new Error('Architecture scope does not match its file');
|
|
196
|
+
return this.locked(async () => {
|
|
197
|
+
const previous = await this.read(file);
|
|
198
|
+
if (expectedRevision !== undefined && previous.revision !== expectedRevision)
|
|
199
|
+
throw new ArchitectureConflict('Architecture changed; reload before saving');
|
|
200
|
+
const changes = diffArchitecture(previous.document, document);
|
|
201
|
+
if (previous.document && !changes.length && ['summary', 'analysis', 'details', 'origin'].every(key => JSON.stringify(previous.document[key]) === JSON.stringify(document[key])))
|
|
202
|
+
return previous;
|
|
203
|
+
await this.archive(file, previous);
|
|
204
|
+
const now = new Date().toISOString(), git = await this.git();
|
|
205
|
+
document.generatedAt = now;
|
|
206
|
+
document.meta = { ...previous.document?.meta, created: previous.document?.meta?.created ?? now, updated: now, updateCount: (previous.document?.meta?.updateCount ?? 0) + 1, ...git };
|
|
207
|
+
for (const view of document.perspectives) {
|
|
208
|
+
const oldView = previous.document?.perspectives.find(old => old.id === view.id);
|
|
209
|
+
view.meta = !oldView || changes.some(change => change.perspective === view.id) ? { created: oldView?.meta?.created ?? now, updated: now, updateCount: (oldView?.meta?.updateCount ?? 0) + 1, ...git, ...(oldView?.diagram ? { previousDiagram: oldView.diagram } : {}) } : oldView.meta;
|
|
210
|
+
for (const node of view.nodes) {
|
|
211
|
+
const old = oldView?.nodes.find(old => old.id === node.id);
|
|
212
|
+
if (!old || changes.some(change => change.perspective === view.id && change.node === node.id))
|
|
213
|
+
node.meta = { created: old?.meta?.created ?? now, updated: now, updateCount: (old?.meta?.updateCount ?? 0) + 1, ...git, ...(old?.diagram ? { previousDiagram: old.diagram } : {}) };
|
|
214
|
+
else
|
|
215
|
+
node.meta = old.meta;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const content = JSON.stringify(document, null, 2);
|
|
219
|
+
await this.atomic(file, content);
|
|
220
|
+
return { document, revision: digest(content) };
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
ommPath(element) {
|
|
224
|
+
if (!element || element.split('/').length > 12 || !element.split('/').every(safeName))
|
|
225
|
+
throw new Error('Invalid .omm element path');
|
|
226
|
+
return `.omm/${element}`;
|
|
227
|
+
}
|
|
228
|
+
async ommElements() {
|
|
229
|
+
const result = [];
|
|
230
|
+
const walk = async (relative, depth) => {
|
|
231
|
+
if (depth > 12 || result.length > 2400)
|
|
232
|
+
throw new Error('.omm tree exceeds the architecture limit');
|
|
233
|
+
let entries;
|
|
234
|
+
try {
|
|
235
|
+
entries = await fs.readdir(await this.bounded(relative), { withFileTypes: true });
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
if (missing(error))
|
|
239
|
+
return;
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
243
|
+
if (!safeName(entry.name))
|
|
244
|
+
continue;
|
|
245
|
+
if (entry.isSymbolicLink())
|
|
246
|
+
throw new Error('.omm tree cannot contain symlinks');
|
|
247
|
+
if (entry.isDirectory()) {
|
|
248
|
+
const next = `${relative}/${entry.name}`;
|
|
249
|
+
result.push(next.slice(5));
|
|
250
|
+
await walk(next, depth + 1);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
await walk('.omm', 0);
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
async readOmmFields(element) {
|
|
258
|
+
const base = this.ommPath(element), fields = {};
|
|
259
|
+
for (const field of ARCHITECTURE_FIELDS) {
|
|
260
|
+
const value = await this.text(`${base}/${names[field]}`);
|
|
261
|
+
if (value !== null)
|
|
262
|
+
fields[field] = value;
|
|
263
|
+
}
|
|
264
|
+
const raw = await this.text(`${base}/meta.yaml`);
|
|
265
|
+
if (!raw)
|
|
266
|
+
return fields;
|
|
267
|
+
const m = YAML.parse(raw, { maxAliasCount: 10 });
|
|
268
|
+
const meta = {};
|
|
269
|
+
for (const [from, to] of [['created', 'created'], ['updated', 'updated'], ['git_commit', 'gitCommit'], ['git_branch', 'gitBranch'], ['prev_diagram', 'previousDiagram']])
|
|
270
|
+
if (typeof m?.[from] === 'string')
|
|
271
|
+
meta[to] = m[from];
|
|
272
|
+
if (typeof m?.update_count === 'number' && Number.isSafeInteger(m.update_count) && m.update_count >= 0)
|
|
273
|
+
meta.updateCount = m.update_count;
|
|
274
|
+
return { ...fields, meta };
|
|
275
|
+
}
|
|
276
|
+
async validateOmmMerge(files) {
|
|
277
|
+
const temporary = await fs.mkdtemp(path.join(os.tmpdir(), 'termdock-omm-import-'));
|
|
278
|
+
try {
|
|
279
|
+
const stage = new ArchitectureStore(temporary), merged = { ...(await this.exportOmmBundle()).files, ...files };
|
|
280
|
+
for (const [name, value] of Object.entries(merged))
|
|
281
|
+
await stage.atomic(`.omm/${name}`, value);
|
|
282
|
+
const result = await stage.readOmm();
|
|
283
|
+
if (!result.document)
|
|
284
|
+
throw new Error('Bundle contains no architecture perspectives');
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
await fs.rm(temporary, { recursive: true, force: true });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async writeOmmField(element, field, content, expectedRevision) {
|
|
291
|
+
if (!ARCHITECTURE_FIELDS.includes(field) || typeof content !== 'string' || content.length > 100_000)
|
|
292
|
+
throw new Error('Invalid architecture field');
|
|
293
|
+
const base = this.ommPath(element);
|
|
294
|
+
if (field === 'diagram') {
|
|
295
|
+
const issues = parseFlowchart(content).issues.filter(issue => !issue.startsWith('Connection needs'));
|
|
296
|
+
if (issues.length)
|
|
297
|
+
throw new Error(issues.join('\n'));
|
|
298
|
+
}
|
|
299
|
+
return this.locked(async () => {
|
|
300
|
+
const previous = await this.readOmm();
|
|
301
|
+
if (expectedRevision !== undefined && previous.revision !== expectedRevision)
|
|
302
|
+
throw new ArchitectureConflict('Architecture changed; reload before saving');
|
|
303
|
+
const old = await this.readOmmFields(element);
|
|
304
|
+
if (old[field] === content)
|
|
305
|
+
return previous;
|
|
306
|
+
await this.validateOmmMerge({ [`${element}/${names[field]}`]: content });
|
|
307
|
+
await this.archive('.omm', previous);
|
|
308
|
+
const now = new Date().toISOString();
|
|
309
|
+
const git = await this.git();
|
|
310
|
+
// Field writes preserve the upstream file layout and metadata convention.
|
|
311
|
+
await this.atomic(`${base}/${names[field]}`, content);
|
|
312
|
+
const meta = { created: old.meta?.created ?? now, updated: now, update_count: (old.meta?.updateCount ?? 0) + 1,
|
|
313
|
+
last_field: field, git_commit: git.gitCommit, git_branch: git.gitBranch,
|
|
314
|
+
...(field === 'diagram' && old.diagram ? { prev_diagram: old.diagram } : old.meta?.previousDiagram ? { prev_diagram: old.meta.previousDiagram } : {}) };
|
|
315
|
+
await this.atomic(`${base}/meta.yaml`, YAML.stringify(meta));
|
|
316
|
+
return this.readOmm();
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
async deleteOmmElement(element) { const target = this.ommPath(element); await this.locked(async () => { await this.archive('.omm', await this.readOmm()); await fs.rm(await this.bounded(target), { recursive: true, force: false }); }); }
|
|
320
|
+
async readOmm() {
|
|
321
|
+
const elements = await this.ommElements();
|
|
322
|
+
if (!elements.length)
|
|
323
|
+
return { document: null, revision: null };
|
|
324
|
+
const data = new Map();
|
|
325
|
+
let total = 0;
|
|
326
|
+
for (let offset = 0; offset < elements.length; offset += 16) {
|
|
327
|
+
for (const [element, fields] of await Promise.all(elements.slice(offset, offset + 16).map(async (element) => [element, await this.readOmmFields(element)]))) {
|
|
328
|
+
total += Buffer.byteLength(JSON.stringify(fields), 'utf8');
|
|
329
|
+
if (total > 5_000_000)
|
|
330
|
+
throw new Error('.omm documentation exceeds the read limit');
|
|
331
|
+
data.set(element, fields);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const perspectives = [];
|
|
335
|
+
const uid = (element) => `e_${digest(element).slice(0, 20)}`;
|
|
336
|
+
const label = (value) => value.replace(/-/g, ' ').replace(/^\w/, ch => ch.toUpperCase());
|
|
337
|
+
const links = [];
|
|
338
|
+
for (const perspective of elements.filter(element => !element.includes('/'))) {
|
|
339
|
+
const fields = data.get(perspective);
|
|
340
|
+
const nodes = new Map(), edges = [];
|
|
341
|
+
const add = async (element, parentId, diagramLabel) => {
|
|
342
|
+
const old = nodes.get(element);
|
|
343
|
+
if (old)
|
|
344
|
+
return old;
|
|
345
|
+
const entry = data.get(element) ?? {};
|
|
346
|
+
const lines = (diagramLabel ?? '').split('\n');
|
|
347
|
+
const files = [];
|
|
348
|
+
for (const line of lines.slice(1)) {
|
|
349
|
+
const relative = line.trim();
|
|
350
|
+
if (isArchitectureSourcePath(relative)) {
|
|
351
|
+
try {
|
|
352
|
+
if ((await fs.stat(await this.bounded(relative))).isFile())
|
|
353
|
+
files.push({ path: relative });
|
|
354
|
+
}
|
|
355
|
+
catch { /* Documentation can reference a directory or a removed file. */ }
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const node = { id: uid(element), documentPath: element, title: lines[0]?.replace(/^@/, '') || label(element.split('/').pop()), summary: (entry.description ?? '').replace(/[#*`]/g, '').split(/\n\s*\n/)[0].slice(0, 600), files, ...entry, ...(parentId ? { parentId } : {}) };
|
|
359
|
+
nodes.set(element, node);
|
|
360
|
+
const graph = parseFlowchart(entry.diagram ?? '');
|
|
361
|
+
for (const child of graph.nodes) {
|
|
362
|
+
const childElement = `${element}/${child.id}`, childNode = await add(childElement, node.id, child.label);
|
|
363
|
+
childNode.role = child.role;
|
|
364
|
+
if (child.ref)
|
|
365
|
+
links.push({ node: childNode, target: child.ref });
|
|
366
|
+
}
|
|
367
|
+
for (const edge of graph.edges)
|
|
368
|
+
edges.push({ from: uid(`${element}/${edge.from}`), to: uid(`${element}/${edge.to}`), label: edge.label });
|
|
369
|
+
// Include documented children even if an old diagram has not mentioned them yet.
|
|
370
|
+
for (const child of elements.filter(child => child.startsWith(`${element}/`) && child.slice(element.length + 1).indexOf('/') === -1))
|
|
371
|
+
await add(child, node.id);
|
|
372
|
+
return node;
|
|
373
|
+
};
|
|
374
|
+
const graph = parseFlowchart(fields.diagram ?? '');
|
|
375
|
+
for (const child of graph.nodes) {
|
|
376
|
+
const node = await add(`${perspective}/${child.id}`, undefined, child.label);
|
|
377
|
+
node.role = child.role;
|
|
378
|
+
if (child.ref)
|
|
379
|
+
links.push({ node, target: child.ref });
|
|
380
|
+
}
|
|
381
|
+
for (const child of elements.filter(child => child.startsWith(`${perspective}/`) && child.split('/').length === 2))
|
|
382
|
+
await add(child);
|
|
383
|
+
for (const edge of graph.edges)
|
|
384
|
+
edges.push({ from: uid(`${perspective}/${edge.from}`), to: uid(`${perspective}/${edge.to}`), label: edge.label });
|
|
385
|
+
if (!nodes.size)
|
|
386
|
+
nodes.set(perspective, { id: uid(perspective), title: label(perspective), summary: fields.description ?? '', files: [], ...fields });
|
|
387
|
+
perspectives.push({ id: perspective, title: label(perspective), summary: (fields.description ?? '').split(/\n\s*\n/)[0].slice(0, 600), nodes: [...nodes.values()], edges, ...fields });
|
|
388
|
+
}
|
|
389
|
+
for (const link of links)
|
|
390
|
+
if (perspectives.some(view => view.id === link.target))
|
|
391
|
+
link.node.references = [...(link.node.references ?? []), { perspective: link.target }];
|
|
392
|
+
const generatedAt = [...data.values()].map(value => value.meta?.updated).filter((value) => !!value && Number.isFinite(Date.parse(value))).sort().pop() ?? new Date(0).toISOString();
|
|
393
|
+
const document = parseArchitecture(JSON.stringify({ version: 1, origin: 'omm', generatedAt, summary: `${path.basename(this.root)} architecture`, perspectives }));
|
|
394
|
+
const config = await this.text('.omm/config.yaml');
|
|
395
|
+
return { document, revision: digest(JSON.stringify([...data]) + (config ?? '')) };
|
|
396
|
+
}
|
|
397
|
+
async config(language) {
|
|
398
|
+
const raw = await this.text('.omm/config.yaml');
|
|
399
|
+
const config = raw ? YAML.parse(raw, { maxAliasCount: 10 }) : { version: '0.1.0', language: 'en' };
|
|
400
|
+
if (!config || typeof config !== 'object' || Array.isArray(config))
|
|
401
|
+
throw new Error('Invalid .omm configuration');
|
|
402
|
+
if (language !== undefined) {
|
|
403
|
+
if (!/^[a-z]{2}(?:-[A-Z]{2})?$/.test(language))
|
|
404
|
+
throw new Error('Invalid language');
|
|
405
|
+
config.language = language;
|
|
406
|
+
await this.locked(() => this.atomic('.omm/config.yaml', YAML.stringify(config)));
|
|
407
|
+
}
|
|
408
|
+
return { language: typeof config?.language === 'string' ? config.language : 'en' };
|
|
409
|
+
}
|
|
410
|
+
async exportOmmBundle(document) {
|
|
411
|
+
const files = {};
|
|
412
|
+
if (!document) {
|
|
413
|
+
for (const element of await this.ommElements()) {
|
|
414
|
+
for (const name of [...Object.values(names), 'meta.yaml']) {
|
|
415
|
+
const value = await this.text(`.omm/${element}/${name}`);
|
|
416
|
+
if (value !== null)
|
|
417
|
+
files[`${element}/${name}`] = value;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
const config = await this.text('.omm/config.yaml');
|
|
421
|
+
if (config)
|
|
422
|
+
files['config.yaml'] = config;
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
files['config.yaml'] = YAML.stringify({ version: '0.1.0' });
|
|
426
|
+
for (const view of document.perspectives) {
|
|
427
|
+
const elementPath = (node) => {
|
|
428
|
+
const ids = [], visited = new Set();
|
|
429
|
+
while (node) {
|
|
430
|
+
if (visited.has(node.id))
|
|
431
|
+
throw new Error('Cyclic architecture');
|
|
432
|
+
visited.add(node.id);
|
|
433
|
+
ids.unshift(node.id);
|
|
434
|
+
node = view.nodes.find(parent => parent.id === node.parentId);
|
|
435
|
+
}
|
|
436
|
+
return [view.id, ...ids].join('/');
|
|
437
|
+
};
|
|
438
|
+
for (const item of [view, ...view.nodes]) {
|
|
439
|
+
const node = item === view ? undefined : item, base = elementPath(node), scope = architectureScope(view, node?.id);
|
|
440
|
+
for (const field of ARCHITECTURE_FIELDS) {
|
|
441
|
+
const value = item[field] ?? (field === 'description' ? item.summary : undefined);
|
|
442
|
+
if (value !== undefined && field !== 'diagram')
|
|
443
|
+
files[`${base}/${names[field]}`] = value;
|
|
444
|
+
}
|
|
445
|
+
if (scope.nodes.length) {
|
|
446
|
+
const label = (value) => value.replace(/["<>|`\\\r\n]/g, ' ');
|
|
447
|
+
files[`${base}/diagram.mmd`] = ['graph LR', ...scope.nodes.map(child => `${child.id}["${child.references?.[0]?.perspective ? `@${child.references[0].perspective}` : label(child.title)}${child.files.length ? `\\n${child.files.map(file => file.path).join('\\n')}` : ''}"]`), ...scope.edges.map(edge => `${edge.from} -->|"${label(edge.label)}"| ${edge.to}`), ...scope.nodes.filter(child => child.role).map(child => `class ${child.id} ${child.role}`)].join('\n');
|
|
448
|
+
}
|
|
449
|
+
else if (item.diagram)
|
|
450
|
+
files[`${base}/diagram.mmd`] = item.diagram;
|
|
451
|
+
const meta = item.meta ?? { created: document.generatedAt, updated: document.generatedAt, updateCount: 0 };
|
|
452
|
+
files[`${base}/meta.yaml`] = YAML.stringify({ created: meta.created, updated: meta.updated, update_count: meta.updateCount, git_commit: meta.gitCommit, git_branch: meta.gitBranch, prev_diagram: meta.previousDiagram });
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (Buffer.byteLength(JSON.stringify(files), 'utf8') > 5_000_000)
|
|
457
|
+
throw new Error('Export exceeds the architecture limit');
|
|
458
|
+
return { format: 'termdock-omm-bundle', version: 1, files };
|
|
459
|
+
}
|
|
460
|
+
async importOmmBundle(input, expectedRevision) {
|
|
461
|
+
const bundle = input;
|
|
462
|
+
if (bundle?.format !== 'termdock-omm-bundle' || bundle.version !== 1 || !bundle.files || typeof bundle.files !== 'object' || Array.isArray(bundle.files))
|
|
463
|
+
throw new Error('Invalid .omm bundle');
|
|
464
|
+
const files = Object.entries(bundle.files);
|
|
465
|
+
if (!files.length || files.length > 16_801 || Buffer.byteLength(JSON.stringify(input), 'utf8') > 5_000_000)
|
|
466
|
+
throw new Error('Invalid .omm bundle size');
|
|
467
|
+
for (const [name, value] of files) {
|
|
468
|
+
const parts = name.split('/'), filename = parts.pop();
|
|
469
|
+
if (!(name === 'config.yaml' || (parts.length > 0 && parts.length <= 12 && parts.every(safeName) && [...Object.values(names), 'meta.yaml'].includes(filename))) || typeof value !== 'string' || value.length > 100_000)
|
|
470
|
+
throw new Error(`Unsafe .omm bundle file: ${name}`);
|
|
471
|
+
await this.bounded(`.omm/${name}`);
|
|
472
|
+
if (filename?.endsWith('.yaml'))
|
|
473
|
+
YAML.parse(value, { maxAliasCount: 10 });
|
|
474
|
+
}
|
|
475
|
+
await this.locked(async () => {
|
|
476
|
+
const previous = await this.readOmm();
|
|
477
|
+
if (expectedRevision !== undefined && previous.revision !== expectedRevision)
|
|
478
|
+
throw new ArchitectureConflict('Architecture changed; reload before importing');
|
|
479
|
+
// Validate the merged tree in isolation before touching the project's documents.
|
|
480
|
+
await this.validateOmmMerge(Object.fromEntries(files));
|
|
481
|
+
await this.archive('.omm', previous);
|
|
482
|
+
// A bundle merges explicit files, preserving unrelated existing perspectives.
|
|
483
|
+
// Every destination is checked before the first write; no business files are touched.
|
|
484
|
+
const undo = [];
|
|
485
|
+
try {
|
|
486
|
+
for (const [name, value] of files) {
|
|
487
|
+
undo.push([name, await this.text(`.omm/${name}`)]);
|
|
488
|
+
await this.atomic(`.omm/${name}`, value);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
for (const [name, value] of undo.reverse()) {
|
|
493
|
+
if (value === null)
|
|
494
|
+
await fs.rm(await this.bounded(`.omm/${name}`), { force: true });
|
|
495
|
+
else
|
|
496
|
+
await this.atomic(`.omm/${name}`, value);
|
|
497
|
+
}
|
|
498
|
+
throw error;
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
package/dist/server/cli.js
CHANGED
|
@@ -10,6 +10,10 @@ import { resolve } from 'path';
|
|
|
10
10
|
// dotenv 不会覆盖已存在的环境变量,因此系统环境变量和命令行参数优先级最高
|
|
11
11
|
loadDotenv({ path: resolve(os.homedir(), '.termdock', '.env'), quiet: true });
|
|
12
12
|
loadDotenv({ path: resolve(process.cwd(), '.env'), quiet: true });
|
|
13
|
+
if (['architecture', 'arch'].includes(process.argv[2] ?? '')) {
|
|
14
|
+
const { runArchitectureCli } = await import('./architecture/cli.js');
|
|
15
|
+
process.exit(await runArchitectureCli(process.argv.slice(3)));
|
|
16
|
+
}
|
|
13
17
|
// Federation commands run before normal server/terminal CLI initialization.
|
|
14
18
|
if (process.argv.slice(2).some(arg => arg === '--federation-pairing' || arg === '--federation-relay')) {
|
|
15
19
|
const { runFederationCli } = await import('./federation/cli.js');
|
|
@@ -199,6 +203,8 @@ Options:
|
|
|
199
203
|
-h, --help Show this help message
|
|
200
204
|
|
|
201
205
|
Short commands:
|
|
206
|
+
architecture Analyze, browse and edit recursive project architecture
|
|
207
|
+
(see td architecture --help)
|
|
202
208
|
agent-plugin [--json]
|
|
203
209
|
Explain how an Agent integrates itself. JSON mode is
|
|
204
210
|
machine-readable and intended for Agent consumption.
|
|
@@ -2112,7 +2118,7 @@ function runPluginInitJson() {
|
|
|
2112
2118
|
collaboration: {
|
|
2113
2119
|
capabilities: 'Optional routing hints shown beside this Agent in collaboration groups.',
|
|
2114
2120
|
universalTransport: 'Every detected Agent receives durable inbox messages through its PTY and can use td collab when it has shell access.',
|
|
2115
|
-
hooks: 'Hooks
|
|
2121
|
+
hooks: 'Hooks publish declared lifecycle/status events; collaboration does not wait for an idle phase and does not require hooks.',
|
|
2116
2122
|
},
|
|
2117
2123
|
security: {
|
|
2118
2124
|
source: 'Only public HTTPS repository roots on approved Git hosts or explicit local paths are accepted; URL credentials are rejected.',
|
package/dist/server/entry.js
CHANGED
|
@@ -38,6 +38,7 @@ import { exitWithStartupFailure } from './utils/startupFailure.js';
|
|
|
38
38
|
import { androidRecordings } from './android/recording.js';
|
|
39
39
|
import { stopAllScrcpySessions } from './android/scrcpy.js';
|
|
40
40
|
import filesystemRoutes from './routes/filesystem.js';
|
|
41
|
+
import { architectureRoutes } from './architecture/routes.js';
|
|
41
42
|
import authRoutes from './routes/auth.js';
|
|
42
43
|
import notificationRoutes from './routes/notifications.js';
|
|
43
44
|
import { createOnboardingRouter } from './routes/onboarding.js';
|
|
@@ -346,6 +347,7 @@ export function createApp(options = {}) {
|
|
|
346
347
|
app.use('/api/terminal', terminalRoutes);
|
|
347
348
|
// 文件系统路由(继承 /api/terminal 上的 auth + CSRF 保护)
|
|
348
349
|
app.use('/api/terminal/fs', filesystemRoutes);
|
|
350
|
+
app.use('/api/terminal/architecture', architectureRoutes());
|
|
349
351
|
// 本机 Android 设备桥接(adb/scrcpy)。经加密隧道时按业务 API 处理,
|
|
350
352
|
// 仅全权服务授权可达;投屏 WS 在 federation runtime 中按 full-service 校验。
|
|
351
353
|
app.use('/api/android', requireAuth({ bypass: isTrustedLocalCliRequest }));
|