synapto-hub 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. synapto_hub-0.1.0/.gitignore +10 -0
  2. synapto_hub-0.1.0/LICENSE +21 -0
  3. synapto_hub-0.1.0/PKG-INFO +125 -0
  4. synapto_hub-0.1.0/README.md +97 -0
  5. synapto_hub-0.1.0/pyproject.toml +65 -0
  6. synapto_hub-0.1.0/src/synapto/__init__.py +1 -0
  7. synapto_hub-0.1.0/src/synapto/bundle/__init__.py +1 -0
  8. synapto_hub-0.1.0/src/synapto/bundle/decisions.py +86 -0
  9. synapto_hub-0.1.0/src/synapto/bundle/models.py +346 -0
  10. synapto_hub-0.1.0/src/synapto/bundle/validator.py +553 -0
  11. synapto_hub-0.1.0/src/synapto/cli.py +287 -0
  12. synapto_hub-0.1.0/src/synapto/environment.py +150 -0
  13. synapto_hub-0.1.0/src/synapto/server/__init__.py +1 -0
  14. synapto_hub-0.1.0/src/synapto/server/app.py +480 -0
  15. synapto_hub-0.1.0/src/synapto/server/decide.py +120 -0
  16. synapto_hub-0.1.0/src/synapto/server/kernels.py +472 -0
  17. synapto_hub-0.1.0/src/synapto/server/lessons.py +148 -0
  18. synapto_hub-0.1.0/src/synapto/server/notebook.py +195 -0
  19. synapto_hub-0.1.0/src/synapto/server/testrunner.py +130 -0
  20. synapto_hub-0.1.0/src/synapto/store.py +370 -0
  21. synapto_hub-0.1.0/src/synapto/web/assets/abnfDiagram-VCTEODGH-BzgVtRmZ.js +1 -0
  22. synapto_hub-0.1.0/src/synapto/web/assets/arc-DFmK96o1.js +1 -0
  23. synapto_hub-0.1.0/src/synapto/web/assets/architecture-7GRP2DOG-zrp1H6CK.js +1 -0
  24. synapto_hub-0.1.0/src/synapto/web/assets/architectureDiagram-5GKGNRK7-BuosZEl4.js +36 -0
  25. synapto_hub-0.1.0/src/synapto/web/assets/array-BifhSqXX.js +1 -0
  26. synapto_hub-0.1.0/src/synapto/web/assets/blockDiagram-I7D4REHJ-CudPrqYF.js +129 -0
  27. synapto_hub-0.1.0/src/synapto/web/assets/c4Diagram-7LVT6UL2-Bx9Z0loF.js +38 -0
  28. synapto_hub-0.1.0/src/synapto/web/assets/channel-BTnE_K1L.js +1 -0
  29. synapto_hub-0.1.0/src/synapto/web/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  30. synapto_hub-0.1.0/src/synapto/web/assets/chunk-4HAMMTFA-Dz7u9Mh-.js +62 -0
  31. synapto_hub-0.1.0/src/synapto/web/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  32. synapto_hub-0.1.0/src/synapto/web/assets/chunk-75Z2AOVW-OWQ1EWdD.js +2 -0
  33. synapto_hub-0.1.0/src/synapto/web/assets/chunk-DU6HZSFF-BZ1HqZRP.js +127 -0
  34. synapto_hub-0.1.0/src/synapto/web/assets/chunk-F27PBJKO-Ccq5rdp_.js +1 -0
  35. synapto_hub-0.1.0/src/synapto/web/assets/chunk-FOHPRMQF-DzxwRta7.js +161 -0
  36. synapto_hub-0.1.0/src/synapto/web/assets/chunk-GMAD6QVW-hqv2YP-U.js +72 -0
  37. synapto_hub-0.1.0/src/synapto/web/assets/chunk-GVQU2GXP-BKNvYVBm.js +1 -0
  38. synapto_hub-0.1.0/src/synapto/web/assets/chunk-IMKFNOWR-Bnq6oZC5.js +231 -0
  39. synapto_hub-0.1.0/src/synapto/web/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  40. synapto_hub-0.1.0/src/synapto/web/assets/chunk-L3NEJ4N5-5DkzRzcD.js +1 -0
  41. synapto_hub-0.1.0/src/synapto/web/assets/chunk-OSK3NFVY-Dgs8_itk.js +10 -0
  42. synapto_hub-0.1.0/src/synapto/web/assets/chunk-P2QGCYS3-BnDhWDjc.js +1 -0
  43. synapto_hub-0.1.0/src/synapto/web/assets/chunk-POPQ4Y6H-CPRdMj-E.js +1 -0
  44. synapto_hub-0.1.0/src/synapto/web/assets/chunk-PWAF6VOD-_mML4Cuz.js +1 -0
  45. synapto_hub-0.1.0/src/synapto/web/assets/chunk-SHT3W25Y-Bxo-Luh8.js +168 -0
  46. synapto_hub-0.1.0/src/synapto/web/assets/chunk-SVP7TREG-CjpZezPs.js +88 -0
  47. synapto_hub-0.1.0/src/synapto/web/assets/chunk-TICWLB2K-B1wqeAp7.js +206 -0
  48. synapto_hub-0.1.0/src/synapto/web/assets/chunk-XXDRQBXY-D6dcpnCk.js +1 -0
  49. synapto_hub-0.1.0/src/synapto/web/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  50. synapto_hub-0.1.0/src/synapto/web/assets/classDiagram-ZZMXUADV-CwKhe2OP.js +1 -0
  51. synapto_hub-0.1.0/src/synapto/web/assets/classDiagram-v2-VYDZK3BY-CwKhe2OP.js +1 -0
  52. synapto_hub-0.1.0/src/synapto/web/assets/cose-bilkent-JH36ORCC-sM2bRXS2.js +1 -0
  53. synapto_hub-0.1.0/src/synapto/web/assets/cynefin-OW5HDTMX-C4-KIuR1.js +1 -0
  54. synapto_hub-0.1.0/src/synapto/web/assets/cynefinDiagram-5FMLGOSQ-Dhh1dIM1.js +62 -0
  55. synapto_hub-0.1.0/src/synapto/web/assets/cytoscape.esm-Yq6u8L66.js +321 -0
  56. synapto_hub-0.1.0/src/synapto/web/assets/dagre-C-rnJpZF.js +1 -0
  57. synapto_hub-0.1.0/src/synapto/web/assets/dagre-GXQ25YYZ-C49ZzWYh.js +4 -0
  58. synapto_hub-0.1.0/src/synapto/web/assets/defaultLocale-BFoDCU3G.js +1 -0
  59. synapto_hub-0.1.0/src/synapto/web/assets/diagram-S7CK7UJ4-D9nA4BRW.js +30 -0
  60. synapto_hub-0.1.0/src/synapto/web/assets/diagram-UQ7AKVKN-BJxJAwtj.js +41 -0
  61. synapto_hub-0.1.0/src/synapto/web/assets/diagram-VSXAHHWV-ClFLSDld.js +3 -0
  62. synapto_hub-0.1.0/src/synapto/web/assets/diagram-VX7I27RA-DN55aHJQ.js +24 -0
  63. synapto_hub-0.1.0/src/synapto/web/assets/diagram-Z3DM3KII-Dpm1Nkf_.js +24 -0
  64. synapto_hub-0.1.0/src/synapto/web/assets/dist-C9N7pTDR.js +1 -0
  65. synapto_hub-0.1.0/src/synapto/web/assets/ebnfDiagram-PWID7BFC-DYKaLaoe.js +1 -0
  66. synapto_hub-0.1.0/src/synapto/web/assets/erDiagram-RLTQ6QDP-86iSEqmb.js +99 -0
  67. synapto_hub-0.1.0/src/synapto/web/assets/eventmodeling-NTZA5JFV-hYD5h-3e.js +1 -0
  68. synapto_hub-0.1.0/src/synapto/web/assets/flowDiagram-HODETNUW-CHqQ1Ofw.js +1 -0
  69. synapto_hub-0.1.0/src/synapto/web/assets/ganttDiagram-EL5Y4UJY-BO-2pK_a.js +292 -0
  70. synapto_hub-0.1.0/src/synapto/web/assets/gitGraph-4MIJSDKK-Dckmqhtr.js +1 -0
  71. synapto_hub-0.1.0/src/synapto/web/assets/gitGraphDiagram-WWUBYQGX-CjM7tH7V.js +106 -0
  72. synapto_hub-0.1.0/src/synapto/web/assets/graphlib-DS17s2tU.js +1 -0
  73. synapto_hub-0.1.0/src/synapto/web/assets/index-B7xe8VDD.js +67 -0
  74. synapto_hub-0.1.0/src/synapto/web/assets/index-DrdFz88Z.css +1 -0
  75. synapto_hub-0.1.0/src/synapto/web/assets/info-A6RAGUB7-Sp5Hh0xb.js +1 -0
  76. synapto_hub-0.1.0/src/synapto/web/assets/infoDiagram-27XIBGKW-B5bXC6xW.js +2 -0
  77. synapto_hub-0.1.0/src/synapto/web/assets/init-C-OQMol4.js +1 -0
  78. synapto_hub-0.1.0/src/synapto/web/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
  79. synapto_hub-0.1.0/src/synapto/web/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
  80. synapto_hub-0.1.0/src/synapto/web/assets/inter-cyrillic-500-normal-BasfLYem.woff2 +0 -0
  81. synapto_hub-0.1.0/src/synapto/web/assets/inter-cyrillic-500-normal-CxZf_p3X.woff +0 -0
  82. synapto_hub-0.1.0/src/synapto/web/assets/inter-cyrillic-600-normal-4D_pXhcN.woff +0 -0
  83. synapto_hub-0.1.0/src/synapto/web/assets/inter-cyrillic-600-normal-CWCymEST.woff2 +0 -0
  84. synapto_hub-0.1.0/src/synapto/web/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
  85. synapto_hub-0.1.0/src/synapto/web/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
  86. synapto_hub-0.1.0/src/synapto/web/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2 +0 -0
  87. synapto_hub-0.1.0/src/synapto/web/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff +0 -0
  88. synapto_hub-0.1.0/src/synapto/web/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff +0 -0
  89. synapto_hub-0.1.0/src/synapto/web/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2 +0 -0
  90. synapto_hub-0.1.0/src/synapto/web/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
  91. synapto_hub-0.1.0/src/synapto/web/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
  92. synapto_hub-0.1.0/src/synapto/web/assets/inter-greek-500-normal-BIZE56-Y.woff2 +0 -0
  93. synapto_hub-0.1.0/src/synapto/web/assets/inter-greek-500-normal-Xzm54t5V.woff +0 -0
  94. synapto_hub-0.1.0/src/synapto/web/assets/inter-greek-600-normal-BZpKdvQh.woff +0 -0
  95. synapto_hub-0.1.0/src/synapto/web/assets/inter-greek-600-normal-plRanbMR.woff2 +0 -0
  96. synapto_hub-0.1.0/src/synapto/web/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
  97. synapto_hub-0.1.0/src/synapto/web/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
  98. synapto_hub-0.1.0/src/synapto/web/assets/inter-greek-ext-500-normal-2j5mBUwD.woff +0 -0
  99. synapto_hub-0.1.0/src/synapto/web/assets/inter-greek-ext-500-normal-C4iEst2y.woff2 +0 -0
  100. synapto_hub-0.1.0/src/synapto/web/assets/inter-greek-ext-600-normal-B8X0CLgF.woff +0 -0
  101. synapto_hub-0.1.0/src/synapto/web/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2 +0 -0
  102. synapto_hub-0.1.0/src/synapto/web/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
  103. synapto_hub-0.1.0/src/synapto/web/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
  104. synapto_hub-0.1.0/src/synapto/web/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
  105. synapto_hub-0.1.0/src/synapto/web/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
  106. synapto_hub-0.1.0/src/synapto/web/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
  107. synapto_hub-0.1.0/src/synapto/web/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
  108. synapto_hub-0.1.0/src/synapto/web/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
  109. synapto_hub-0.1.0/src/synapto/web/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
  110. synapto_hub-0.1.0/src/synapto/web/assets/inter-latin-ext-500-normal-BxGbmqWO.woff +0 -0
  111. synapto_hub-0.1.0/src/synapto/web/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2 +0 -0
  112. synapto_hub-0.1.0/src/synapto/web/assets/inter-latin-ext-600-normal-CIVaiw4L.woff +0 -0
  113. synapto_hub-0.1.0/src/synapto/web/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2 +0 -0
  114. synapto_hub-0.1.0/src/synapto/web/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
  115. synapto_hub-0.1.0/src/synapto/web/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
  116. synapto_hub-0.1.0/src/synapto/web/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
  117. synapto_hub-0.1.0/src/synapto/web/assets/inter-vietnamese-500-normal-mJboJaSs.woff +0 -0
  118. synapto_hub-0.1.0/src/synapto/web/assets/inter-vietnamese-600-normal-BuLX-rYi.woff +0 -0
  119. synapto_hub-0.1.0/src/synapto/web/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
  120. synapto_hub-0.1.0/src/synapto/web/assets/ishikawaDiagram-5VMMS53U-DlLxyIRL.js +70 -0
  121. synapto_hub-0.1.0/src/synapto/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  122. synapto_hub-0.1.0/src/synapto/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  123. synapto_hub-0.1.0/src/synapto/web/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
  124. synapto_hub-0.1.0/src/synapto/web/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
  125. synapto_hub-0.1.0/src/synapto/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  126. synapto_hub-0.1.0/src/synapto/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  127. synapto_hub-0.1.0/src/synapto/web/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
  128. synapto_hub-0.1.0/src/synapto/web/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
  129. synapto_hub-0.1.0/src/synapto/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  130. synapto_hub-0.1.0/src/synapto/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  131. synapto_hub-0.1.0/src/synapto/web/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  132. synapto_hub-0.1.0/src/synapto/web/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  133. synapto_hub-0.1.0/src/synapto/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  134. synapto_hub-0.1.0/src/synapto/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  135. synapto_hub-0.1.0/src/synapto/web/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
  136. synapto_hub-0.1.0/src/synapto/web/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
  137. synapto_hub-0.1.0/src/synapto/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  138. synapto_hub-0.1.0/src/synapto/web/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
  139. synapto_hub-0.1.0/src/synapto/web/assets/journeyDiagram-3NMN7TZE-Cn7GmHUK.js +139 -0
  140. synapto_hub-0.1.0/src/synapto/web/assets/kanban-definition-UXKFOSKX-CB63Fceu.js +89 -0
  141. synapto_hub-0.1.0/src/synapto/web/assets/katex-ZlcWpGUi.js +257 -0
  142. synapto_hub-0.1.0/src/synapto/web/assets/line-aNxvPf-1.js +1 -0
  143. synapto_hub-0.1.0/src/synapto/web/assets/linear-CBnI93jZ.js +1 -0
  144. synapto_hub-0.1.0/src/synapto/web/assets/mermaid-parser.core-N0YHHR1_.js +7 -0
  145. synapto_hub-0.1.0/src/synapto/web/assets/mermaid.core-DBmdoC-V.js +44 -0
  146. synapto_hub-0.1.0/src/synapto/web/assets/mindmap-definition-YA3MSWOX-BbJAJ-59.js +96 -0
  147. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-cyrillic-500-normal-CyGtXmN9.woff +0 -0
  148. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-cyrillic-500-normal-T0SG181k.woff2 +0 -0
  149. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-cyrillic-600-normal-CQEPC0hM.woff2 +0 -0
  150. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-cyrillic-600-normal-DUglwBrH.woff +0 -0
  151. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-cyrillic-ext-500-normal-11xBT7e1.woff2 +0 -0
  152. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-cyrillic-ext-500-normal-DOzfAZ45.woff +0 -0
  153. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-cyrillic-ext-600-normal-BtBW-rpm.woff2 +0 -0
  154. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-cyrillic-ext-600-normal-wReYPmz2.woff +0 -0
  155. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-latin-500-normal-DRFEGfly.woff2 +0 -0
  156. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-latin-500-normal-Dok2oTci.woff +0 -0
  157. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-latin-600-normal-CdhFl4lI.woff +0 -0
  158. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-latin-600-normal-UVxSCcoG.woff2 +0 -0
  159. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-latin-ext-500-normal-BKtbrd6n.woff2 +0 -0
  160. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-latin-ext-500-normal-DWPqqZgs.woff +0 -0
  161. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-latin-ext-600-normal-CSDhkhgS.woff +0 -0
  162. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-latin-ext-600-normal-DSkTqI9L.woff2 +0 -0
  163. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-vietnamese-500-normal-DpeZlV_K.woff +0 -0
  164. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-vietnamese-500-normal-NT-t8RG1.woff2 +0 -0
  165. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-vietnamese-600-normal-DKe6qT_E.woff2 +0 -0
  166. synapto_hub-0.1.0/src/synapto/web/assets/montserrat-vietnamese-600-normal-SJ-HTWuM.woff +0 -0
  167. synapto_hub-0.1.0/src/synapto/web/assets/ordinal-BDEzSJ7C.js +1 -0
  168. synapto_hub-0.1.0/src/synapto/web/assets/packet-AYTQ26CC-DB9KOyQ4.js +1 -0
  169. synapto_hub-0.1.0/src/synapto/web/assets/path-fybaL0A-.js +1 -0
  170. synapto_hub-0.1.0/src/synapto/web/assets/pegDiagram-XKGWAZYB-DXlT0oJF.js +1 -0
  171. synapto_hub-0.1.0/src/synapto/web/assets/pie-WAS4IAKB-sUrEgbC4.js +1 -0
  172. synapto_hub-0.1.0/src/synapto/web/assets/pieDiagram-E7YTZNPT-Dt-p3aTR.js +39 -0
  173. synapto_hub-0.1.0/src/synapto/web/assets/plotly.min-C0VrWA2L.js +3526 -0
  174. synapto_hub-0.1.0/src/synapto/web/assets/quadrantDiagram-AXDQQJYC-CVhdtPco.js +7 -0
  175. synapto_hub-0.1.0/src/synapto/web/assets/radar-RG4KPBEZ-DXQf618d.js +1 -0
  176. synapto_hub-0.1.0/src/synapto/web/assets/railroad-74A4TZTK-D1T4x4-K.js +1 -0
  177. synapto_hub-0.1.0/src/synapto/web/assets/railroad-abnf-HS5TGJTU-CErj6ZIe.js +1 -0
  178. synapto_hub-0.1.0/src/synapto/web/assets/railroad-ebnf-LZEXJU2U-DEPob5Ru.js +1 -0
  179. synapto_hub-0.1.0/src/synapto/web/assets/railroad-peg-WCYAUIDC-CH7u0Yg6.js +1 -0
  180. synapto_hub-0.1.0/src/synapto/web/assets/railroadDiagram-O6MQD6OU-BDfEX1zC.js +1 -0
  181. synapto_hub-0.1.0/src/synapto/web/assets/requirementDiagram-BXWQKSXE-0PWnZ_PF.js +84 -0
  182. synapto_hub-0.1.0/src/synapto/web/assets/rough.esm-Dy-Kn_BL.js +1 -0
  183. synapto_hub-0.1.0/src/synapto/web/assets/sankeyDiagram-P5KCCOFB-CzmXAlwy.js +40 -0
  184. synapto_hub-0.1.0/src/synapto/web/assets/sequenceDiagram-WJ2MYXX4-C4kwKBBc.js +162 -0
  185. synapto_hub-0.1.0/src/synapto/web/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  186. synapto_hub-0.1.0/src/synapto/web/assets/src-DYcutCTp.js +1 -0
  187. synapto_hub-0.1.0/src/synapto/web/assets/stateDiagram-D77RDMKH-DbpLbOeP.js +1 -0
  188. synapto_hub-0.1.0/src/synapto/web/assets/stateDiagram-v2-MP3YSRHH-DNx3ipBZ.js +1 -0
  189. synapto_hub-0.1.0/src/synapto/web/assets/swimlanes-42K2YHIH-DFi8XeS0.js +1 -0
  190. synapto_hub-0.1.0/src/synapto/web/assets/swimlanesDiagram-VR7AAH4N-D1QzFjWS.js +8 -0
  191. synapto_hub-0.1.0/src/synapto/web/assets/timeline-definition-24CTP7MA-CRE5CG6g.js +120 -0
  192. synapto_hub-0.1.0/src/synapto/web/assets/treeView-Q6P3EWNA-DT2EnW9L.js +1 -0
  193. synapto_hub-0.1.0/src/synapto/web/assets/treemap-WGGIJYW6-BCPoUUJE.js +1 -0
  194. synapto_hub-0.1.0/src/synapto/web/assets/vennDiagram-4TSXK5OY-CE-jGbnB.js +34 -0
  195. synapto_hub-0.1.0/src/synapto/web/assets/wardley-WFR3VGLG-CTplXEkW.js +1 -0
  196. synapto_hub-0.1.0/src/synapto/web/assets/wardleyDiagram-VM6X3IG4-C_tavznA.js +78 -0
  197. synapto_hub-0.1.0/src/synapto/web/assets/xychartDiagram-S5SC5T6Z-B4LGptT4.js +7 -0
  198. synapto_hub-0.1.0/src/synapto/web/index.html +17 -0
  199. synapto_hub-0.1.0/src/synapto/web/synapto-logo-lockup.png +0 -0
  200. synapto_hub-0.1.0/src/synapto/web/synapto-mark.png +0 -0
  201. synapto_hub-0.1.0/tests/conftest.py +69 -0
  202. synapto_hub-0.1.0/tests/example/bundle/decisions.md +7 -0
  203. synapto_hub-0.1.0/tests/example/bundle/diagrams/grid.svg +11 -0
  204. synapto_hub-0.1.0/tests/example/bundle/exercises/01-voxel-key/exercise.json +12 -0
  205. synapto_hub-0.1.0/tests/example/bundle/exercises/01-voxel-key/solution.py +13 -0
  206. synapto_hub-0.1.0/tests/example/bundle/exercises/01-voxel-key/stub.py +10 -0
  207. synapto_hub-0.1.0/tests/example/bundle/exercises/01-voxel-key/test_exercise.py +20 -0
  208. synapto_hub-0.1.0/tests/example/bundle/exercises/02-downsample/exercise.json +12 -0
  209. synapto_hub-0.1.0/tests/example/bundle/exercises/02-downsample/solution.py +17 -0
  210. synapto_hub-0.1.0/tests/example/bundle/exercises/02-downsample/stub.py +10 -0
  211. synapto_hub-0.1.0/tests/example/bundle/exercises/02-downsample/test_exercise.py +20 -0
  212. synapto_hub-0.1.0/tests/example/bundle/explanation.md +39 -0
  213. synapto_hub-0.1.0/tests/example/bundle/fixtures/points.xyz +11 -0
  214. synapto_hub-0.1.0/tests/example/bundle/lesson.json +42 -0
  215. synapto_hub-0.1.0/tests/example/bundle/notebook.ipynb +157 -0
  216. synapto_hub-0.1.0/tests/example/bundle/quiz.json +71 -0
  217. synapto_hub-0.1.0/tests/example/decision/explanation.md +13 -0
  218. synapto_hub-0.1.0/tests/example/decision/lesson.json +14 -0
  219. synapto_hub-0.1.0/tests/example/decision/options.json +43 -0
  220. synapto_hub-0.1.0/tests/example/decision/quiz.json +104 -0
  221. synapto_hub-0.1.0/tests/example/project/src/gridsnap/__init__.py +1 -0
  222. synapto_hub-0.1.0/tests/example/project/src/gridsnap/snap.py +45 -0
  223. synapto_hub-0.1.0/tests/test_bundle.py +351 -0
  224. synapto_hub-0.1.0/tests/test_decide.py +245 -0
  225. synapto_hub-0.1.0/tests/test_decisions.py +66 -0
  226. synapto_hub-0.1.0/tests/test_environment.py +54 -0
  227. synapto_hub-0.1.0/tests/test_exercises.py +142 -0
  228. synapto_hub-0.1.0/tests/test_kernels.py +184 -0
  229. synapto_hub-0.1.0/tests/test_notebook.py +252 -0
  230. synapto_hub-0.1.0/tests/test_server.py +234 -0
  231. synapto_hub-0.1.0/tests/test_store.py +107 -0
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ .pytest_cache/
5
+ dist/
6
+ build/
7
+ *.egg-info/
8
+
9
+ # Built web UI (npm run build in web/)
10
+ src/synapto/web/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rick-scheper
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.5
2
+ Name: synapto-hub
3
+ Version: 0.1.0
4
+ Summary: Turn what your coding agent just built into an interactive lesson.
5
+ Project-URL: Homepage, https://github.com/rick-scheper/synapto
6
+ Project-URL: Issues, https://github.com/rick-scheper/synapto/issues
7
+ Author: rick-scheper
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: claude-code,coding-agents,jupyter,learning,lessons
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Web Environment
13
+ Classifier: Framework :: FastAPI
14
+ Classifier: Framework :: Jupyter
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Education
18
+ Classifier: Topic :: Software Development
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: fastapi>=0.115
21
+ Requires-Dist: jupyter-client>=8.6
22
+ Requires-Dist: nbformat>=5.10
23
+ Requires-Dist: pydantic>=2.7
24
+ Requires-Dist: typer>=0.12
25
+ Requires-Dist: uvicorn>=0.30
26
+ Requires-Dist: websockets>=13
27
+ Description-Content-Type: text/markdown
28
+
29
+ <p align="center">
30
+ <img src="https://raw.githubusercontent.com/rick-scheper/synapto/main/docs/design/design-system/assets/logos/synapto-logo-lockup.png" alt="Synapto" width="360">
31
+ </p>
32
+
33
+ **Understand the code your coding agent writes.**
34
+
35
+ After Claude Code has built something, run `/debrief`. The agent writes a lesson about exactly what it just built and publishes it to Synapto, a learning hub that runs on your own machine. In the lesson you can:
36
+
37
+ - **Explain:** read a walkthrough of the build, with diagrams.
38
+ - **Decisions:** review the architectural choices the agent made, and the alternatives it rejected.
39
+ - **Notebook:** run and edit the built code, split into functions. It runs in your project's own venv, on fixtures or on your real data.
40
+ - **Quiz:** check that you understood it.
41
+ - **Rebuild:** rewrite the key functions from a stub until the tests pass.
42
+
43
+ <!-- Demo GIF: record docs/demo.gif (see "Recording the demo" below), then uncomment:
44
+ <p align="center"><img src="https://raw.githubusercontent.com/rick-scheper/synapto/main/docs/demo.gif" alt="Running /debrief and opening the lesson" width="800"></p>
45
+ -->
46
+
47
+ ## Install
48
+
49
+ You need [uv](https://docs.astral.sh/uv/), Python ≥ 3.11 and [Claude Code](https://claude.com/claude-code).
50
+
51
+ ```sh
52
+ uv tool install synapto-hub
53
+ ```
54
+
55
+ This installs the `synapto` command. Next, add the Claude Code plugin, which provides the `/debrief` and `/decide` skills. Run these inside Claude Code:
56
+
57
+ ```
58
+ /plugin marketplace add rick-scheper/synapto
59
+ /plugin install synapto@synapto
60
+ ```
61
+
62
+ Your project's venv needs `ipykernel` and `pytest`. `synapto doctor` checks for them and prints the install command if one is missing. The skill runs this check for you.
63
+
64
+ ## Use it
65
+
66
+ 1. In your project, let the agent build something.
67
+ 2. Type `/debrief`. The agent asks which parts you want: explanation, decisions, notebook, quiz and rebuild exercises. To skip that question, name them: `/debrief only explain,quiz`. To narrow the scope, use `/debrief since a1b2c3d` or `/debrief src/pkg/module.py`.
68
+ 3. The agent writes the lesson, checks it with `synapto validate` and publishes it with `synapto publish`. It then gives you the lesson URL.
69
+ 4. Start the hub with `synapto serve --open`. It serves on http://127.0.0.1:8765.
70
+
71
+ ### Before you build: `/decide`
72
+
73
+ Facing a choice before any code exists, such as which database to use? Type `/decide "which database fits this project?"`, optionally followed by the candidates you have in mind. The agent researches the strongest candidates and publishes a **decision lesson**: an explanation of the problem space, the options compared on criteria that matter for your project, and a quiz.
74
+
75
+ - `/decide guided "…"`: the lesson leads you to the agent's recommendation.
76
+ - `/decide open "…"`: you make the call. Pick an option on the lesson's Options tab and write down why, then run `/decide review <lesson id>`. The agent challenges your reasoning in Claude Code, and the hub then shows its verdict next to the recommendation it wrote down *before* you chose. At the end it offers to record the decision as an ADR in your project.
77
+
78
+ | Command | What it does |
79
+ |---|---|
80
+ | `synapto serve [--open] [--port N] [--reload]` | Start the hub on 127.0.0.1; `--reload` restarts a hub that's already running |
81
+ | `synapto doctor [--python PATH]` | Check that a project interpreter can run lessons |
82
+ | `synapto validate <bundle>` | Check a lesson bundle: it runs the notebook, and checks that the tests pass on the solution and fail on the stub |
83
+ | `synapto publish <bundle> [--force]` | Validate a bundle and add it to your library |
84
+ | `synapto decision show <id>` / `synapto decision verdict <id> <file>` | Read and store the review of an open decision (used by `/decide review`) |
85
+ | `synapto remove <id> [--yes]` | Delete a lesson and your progress in it (or use **Delete lesson** in the hub) |
86
+
87
+ Lessons and your progress live in `~/.synapto` (override with `SYNAPTO_HOME`).
88
+
89
+ ## Scope and safety
90
+
91
+ Synapto v1 supports Python projects with Claude Code, for a single local user. Lesson code runs **with your own permissions** in your project's interpreter, with no sandbox. Lessons are generated from your own code. The server binds only to `127.0.0.1` and rejects cross-site and DNS-rebinding requests. See [spec §11](docs/spec.md#11-security-model).
92
+
93
+ ## Development
94
+
95
+ ```sh
96
+ uv sync # Python deps + dev tools
97
+ uv run pytest
98
+ claude --plugin-dir ./plugin # run from a project to try the skill from this checkout
99
+ uv tool install --editable . # puts this checkout's `synapto` on PATH
100
+ ```
101
+
102
+ The UI is a React + Vite app in `web/` ([ADR-0005](docs/adr/0005-frontend-react-vite.md)). Its design reference is in [docs/design/](docs/design/README.md). You only need Node to work on the UI:
103
+
104
+ ```sh
105
+ cd web && npm install
106
+ npm run build # writes the app to src/synapto/web/, which `synapto serve` serves and the wheel ships
107
+ npm run dev # hot-reloading UI on :5173; proxies /api to a running `synapto serve`
108
+ ```
109
+
110
+ [docs/spec.md](docs/spec.md) is the full specification, and [docs/adr/](docs/adr/README.md) holds the architecture decisions.
111
+
112
+ ### Releasing
113
+
114
+ 1. Bump `version` in `pyproject.toml`, `plugin/.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json`.
115
+ 2. Tag and push: `git tag v0.1.0 && git push origin main v0.1.0`.
116
+
117
+ The `release` workflow builds the UI, runs the tests and builds the wheel. It checks that the wheel contains the UI and that the tag matches the version, then publishes `synapto-hub` to PyPI through [trusted publishing](https://docs.pypi.org/trusted-publishers/). Set this up once on PyPI for the repo, the `release.yml` workflow and the `pypi` environment. The plugin goes live when its manifest reaches `main` on GitHub.
118
+
119
+ ### Recording the demo
120
+
121
+ `docs/demo.gif` is about 20–30 s at 800 px wide. It shows: `/debrief` finishing in Claude Code with a `Lesson URL:` → the Library → Explain → running a Notebook cell → a passing Rebuild test run.
122
+
123
+ ## License
124
+
125
+ [MIT](LICENSE)
@@ -0,0 +1,97 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/rick-scheper/synapto/main/docs/design/design-system/assets/logos/synapto-logo-lockup.png" alt="Synapto" width="360">
3
+ </p>
4
+
5
+ **Understand the code your coding agent writes.**
6
+
7
+ After Claude Code has built something, run `/debrief`. The agent writes a lesson about exactly what it just built and publishes it to Synapto, a learning hub that runs on your own machine. In the lesson you can:
8
+
9
+ - **Explain:** read a walkthrough of the build, with diagrams.
10
+ - **Decisions:** review the architectural choices the agent made, and the alternatives it rejected.
11
+ - **Notebook:** run and edit the built code, split into functions. It runs in your project's own venv, on fixtures or on your real data.
12
+ - **Quiz:** check that you understood it.
13
+ - **Rebuild:** rewrite the key functions from a stub until the tests pass.
14
+
15
+ <!-- Demo GIF: record docs/demo.gif (see "Recording the demo" below), then uncomment:
16
+ <p align="center"><img src="https://raw.githubusercontent.com/rick-scheper/synapto/main/docs/demo.gif" alt="Running /debrief and opening the lesson" width="800"></p>
17
+ -->
18
+
19
+ ## Install
20
+
21
+ You need [uv](https://docs.astral.sh/uv/), Python ≥ 3.11 and [Claude Code](https://claude.com/claude-code).
22
+
23
+ ```sh
24
+ uv tool install synapto-hub
25
+ ```
26
+
27
+ This installs the `synapto` command. Next, add the Claude Code plugin, which provides the `/debrief` and `/decide` skills. Run these inside Claude Code:
28
+
29
+ ```
30
+ /plugin marketplace add rick-scheper/synapto
31
+ /plugin install synapto@synapto
32
+ ```
33
+
34
+ Your project's venv needs `ipykernel` and `pytest`. `synapto doctor` checks for them and prints the install command if one is missing. The skill runs this check for you.
35
+
36
+ ## Use it
37
+
38
+ 1. In your project, let the agent build something.
39
+ 2. Type `/debrief`. The agent asks which parts you want: explanation, decisions, notebook, quiz and rebuild exercises. To skip that question, name them: `/debrief only explain,quiz`. To narrow the scope, use `/debrief since a1b2c3d` or `/debrief src/pkg/module.py`.
40
+ 3. The agent writes the lesson, checks it with `synapto validate` and publishes it with `synapto publish`. It then gives you the lesson URL.
41
+ 4. Start the hub with `synapto serve --open`. It serves on http://127.0.0.1:8765.
42
+
43
+ ### Before you build: `/decide`
44
+
45
+ Facing a choice before any code exists, such as which database to use? Type `/decide "which database fits this project?"`, optionally followed by the candidates you have in mind. The agent researches the strongest candidates and publishes a **decision lesson**: an explanation of the problem space, the options compared on criteria that matter for your project, and a quiz.
46
+
47
+ - `/decide guided "…"`: the lesson leads you to the agent's recommendation.
48
+ - `/decide open "…"`: you make the call. Pick an option on the lesson's Options tab and write down why, then run `/decide review <lesson id>`. The agent challenges your reasoning in Claude Code, and the hub then shows its verdict next to the recommendation it wrote down *before* you chose. At the end it offers to record the decision as an ADR in your project.
49
+
50
+ | Command | What it does |
51
+ |---|---|
52
+ | `synapto serve [--open] [--port N] [--reload]` | Start the hub on 127.0.0.1; `--reload` restarts a hub that's already running |
53
+ | `synapto doctor [--python PATH]` | Check that a project interpreter can run lessons |
54
+ | `synapto validate <bundle>` | Check a lesson bundle: it runs the notebook, and checks that the tests pass on the solution and fail on the stub |
55
+ | `synapto publish <bundle> [--force]` | Validate a bundle and add it to your library |
56
+ | `synapto decision show <id>` / `synapto decision verdict <id> <file>` | Read and store the review of an open decision (used by `/decide review`) |
57
+ | `synapto remove <id> [--yes]` | Delete a lesson and your progress in it (or use **Delete lesson** in the hub) |
58
+
59
+ Lessons and your progress live in `~/.synapto` (override with `SYNAPTO_HOME`).
60
+
61
+ ## Scope and safety
62
+
63
+ Synapto v1 supports Python projects with Claude Code, for a single local user. Lesson code runs **with your own permissions** in your project's interpreter, with no sandbox. Lessons are generated from your own code. The server binds only to `127.0.0.1` and rejects cross-site and DNS-rebinding requests. See [spec §11](docs/spec.md#11-security-model).
64
+
65
+ ## Development
66
+
67
+ ```sh
68
+ uv sync # Python deps + dev tools
69
+ uv run pytest
70
+ claude --plugin-dir ./plugin # run from a project to try the skill from this checkout
71
+ uv tool install --editable . # puts this checkout's `synapto` on PATH
72
+ ```
73
+
74
+ The UI is a React + Vite app in `web/` ([ADR-0005](docs/adr/0005-frontend-react-vite.md)). Its design reference is in [docs/design/](docs/design/README.md). You only need Node to work on the UI:
75
+
76
+ ```sh
77
+ cd web && npm install
78
+ npm run build # writes the app to src/synapto/web/, which `synapto serve` serves and the wheel ships
79
+ npm run dev # hot-reloading UI on :5173; proxies /api to a running `synapto serve`
80
+ ```
81
+
82
+ [docs/spec.md](docs/spec.md) is the full specification, and [docs/adr/](docs/adr/README.md) holds the architecture decisions.
83
+
84
+ ### Releasing
85
+
86
+ 1. Bump `version` in `pyproject.toml`, `plugin/.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json`.
87
+ 2. Tag and push: `git tag v0.1.0 && git push origin main v0.1.0`.
88
+
89
+ The `release` workflow builds the UI, runs the tests and builds the wheel. It checks that the wheel contains the UI and that the tag matches the version, then publishes `synapto-hub` to PyPI through [trusted publishing](https://docs.pypi.org/trusted-publishers/). Set this up once on PyPI for the repo, the `release.yml` workflow and the `pypi` environment. The plugin goes live when its manifest reaches `main` on GitHub.
90
+
91
+ ### Recording the demo
92
+
93
+ `docs/demo.gif` is about 20–30 s at 800 px wide. It shows: `/debrief` finishing in Claude Code with a `Lesson URL:` → the Library → Explain → running a Notebook cell → a passing Rebuild test run.
94
+
95
+ ## License
96
+
97
+ [MIT](LICENSE)
@@ -0,0 +1,65 @@
1
+ [project]
2
+ name = "synapto-hub"
3
+ version = "0.1.0"
4
+ description = "Turn what your coding agent just built into an interactive lesson."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = "MIT"
8
+ authors = [{ name = "rick-scheper" }]
9
+ keywords = ["claude-code", "coding-agents", "learning", "jupyter", "lessons"]
10
+ classifiers = [
11
+ "Development Status :: 3 - Alpha",
12
+ "Environment :: Web Environment",
13
+ "Framework :: FastAPI",
14
+ "Framework :: Jupyter",
15
+ "Intended Audience :: Developers",
16
+ "Programming Language :: Python :: 3",
17
+ "Topic :: Education",
18
+ "Topic :: Software Development",
19
+ ]
20
+ dependencies = [
21
+ "fastapi>=0.115",
22
+ "jupyter_client>=8.6",
23
+ "nbformat>=5.10",
24
+ "pydantic>=2.7",
25
+ "typer>=0.12",
26
+ "uvicorn>=0.30",
27
+ # uvicorn needs it to serve the kernel WebSocket (spec §9.1).
28
+ "websockets>=13",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/rick-scheper/synapto"
33
+ Issues = "https://github.com/rick-scheper/synapto/issues"
34
+
35
+ [project.scripts]
36
+ synapto = "synapto.cli:app"
37
+
38
+ [dependency-groups]
39
+ dev = [
40
+ "pytest>=8",
41
+ "pytest-asyncio>=0.24",
42
+ # The test suite uses the dev venv as the "project venv", so it needs
43
+ # what synapto doctor requires of a project interpreter.
44
+ "ipykernel>=6.29",
45
+ "httpx>=0.27",
46
+ ]
47
+
48
+ [build-system]
49
+ requires = ["hatchling"]
50
+ build-backend = "hatchling.build"
51
+
52
+ [tool.hatch.build.targets.sdist]
53
+ include = ["/src/synapto", "/tests", "/README.md", "/LICENSE"]
54
+ artifacts = ["/src/synapto/web/**"]
55
+
56
+ [tool.hatch.build.targets.wheel]
57
+ packages = ["src/synapto"]
58
+ # The built web UI is gitignored but ships in the wheel (ADR-0005).
59
+ artifacts = ["/src/synapto/web/**"]
60
+
61
+ [tool.pytest.ini_options]
62
+ testpaths = ["tests"]
63
+ asyncio_mode = "auto"
64
+ asyncio_default_fixture_loop_scope = "function"
65
+ markers = ["slow: creates a separate venv"]
@@ -0,0 +1 @@
1
+ """Synapto: interactive lessons from what your coding agent just built."""
@@ -0,0 +1 @@
1
+ """The lesson bundle: the contract between the /debrief skill and the hub (spec §5)."""
@@ -0,0 +1,86 @@
1
+ """Parse ``decisions.md`` (spec §5.3) into one record per decision.
2
+
3
+ The template is a ``## <title>`` section per decision with ``**Context:**``,
4
+ ``**Options:**``, ``**Chosen:**``, ``**Why:**``, ``**Trade-offs:**`` and
5
+ ``**ADR:**`` fields. The validator doesn't enforce it, so parsing is lenient:
6
+ anything it can't place stays available as the section's raw Markdown, and a
7
+ file without sections (the "no decisions were made" case) becomes a note.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import re
13
+
14
+ from pydantic import BaseModel
15
+
16
+ _SECTION = re.compile(r"^##[ \t]+(.+?)[ \t]*#*[ \t]*$", re.MULTILINE)
17
+ _FIELD = re.compile(r"^\*\*(Context|Options|Chosen|Why|Trade-offs|ADR):?\*\*:?[ \t]*", re.MULTILINE | re.IGNORECASE)
18
+ # "A — text", "(C — text)"; the dash may be an em dash, en dash or hyphen.
19
+ _OPTION = re.compile(r"^\(?\s*([A-Za-z0-9]{1,3})\s*[—–-]\s*(.+?)\s*\)?\.?$", re.DOTALL)
20
+
21
+
22
+ class DecisionOption(BaseModel):
23
+ id: str
24
+ label: str
25
+
26
+
27
+ class Decision(BaseModel):
28
+ title: str
29
+ context: str | None = None
30
+ options: list[DecisionOption] = []
31
+ chosen: str | None = None
32
+ why: str | None = None
33
+ tradeoffs: str | None = None
34
+ adr: str | None = None
35
+ body: str
36
+ """The section's Markdown without its heading, for when the fields don't parse."""
37
+
38
+
39
+ class Decisions(BaseModel):
40
+ decisions: list[Decision]
41
+ note: str | None = None
42
+ """Text before the first section, e.g. "No architectural decisions were made."."""
43
+
44
+
45
+ def parse_decisions(text: str) -> Decisions:
46
+ sections = list(_SECTION.finditer(text))
47
+ intro = text[: sections[0].start()] if sections else text
48
+ intro = re.sub(r"^#[ \t].*$", "", intro, flags=re.MULTILINE).strip() # drop a "# Decisions" title
49
+ decisions = []
50
+ for i, match in enumerate(sections):
51
+ end = sections[i + 1].start() if i + 1 < len(sections) else len(text)
52
+ decisions.append(_parse_section(match[1], text[match.end():end].strip()))
53
+ return Decisions(decisions=decisions, note=intro or None)
54
+
55
+
56
+ def _parse_section(title: str, body: str) -> Decision:
57
+ fields: dict[str, str] = {}
58
+ marks = list(_FIELD.finditer(body))
59
+ for i, mark in enumerate(marks):
60
+ end = marks[i + 1].start() if i + 1 < len(marks) else len(body)
61
+ fields[mark[1].lower()] = " ".join(body[mark.end():end].split())
62
+
63
+ options = _parse_options(fields.get("options", ""))
64
+ chosen = fields.get("chosen")
65
+ if chosen and options:
66
+ # "B", "B — dict accumulation", "Option B"
67
+ ids = {o.id.lower(): o.id for o in options}
68
+ head = re.match(r"^(?:option\s+)?([A-Za-z0-9]{1,3})\b", chosen, re.IGNORECASE)
69
+ chosen = ids.get(head[1].lower()) if head else None
70
+ elif not options:
71
+ chosen = None
72
+
73
+ return Decision(
74
+ title=title, context=fields.get("context"), options=options, chosen=chosen,
75
+ why=fields.get("why"), tradeoffs=fields.get("trade-offs"), adr=fields.get("adr"), body=body,
76
+ )
77
+
78
+
79
+ def _parse_options(text: str) -> list[DecisionOption]:
80
+ options = []
81
+ for part in (p.strip() for p in text.split(";")):
82
+ match = _OPTION.match(part)
83
+ if match is None:
84
+ return [] # one unrecognised part means the list isn't in the template's shape
85
+ options.append(DecisionOption(id=match[1], label=match[2]))
86
+ return options