rdstudio 0.2.0__py3-none-any.whl

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 (251) hide show
  1. rdstudio/__init__.py +3 -0
  2. rdstudio/_node/rdstudio.mjs +59351 -0
  3. rdstudio/brief.py +46 -0
  4. rdstudio/build.py +218 -0
  5. rdstudio/classify.py +131 -0
  6. rdstudio/cli.py +369 -0
  7. rdstudio/config.py +115 -0
  8. rdstudio/gitlog.py +92 -0
  9. rdstudio/launcher.py +63 -0
  10. rdstudio/learner.py +187 -0
  11. rdstudio/mcp_server.py +313 -0
  12. rdstudio/okf.py +775 -0
  13. rdstudio/procedures.py +233 -0
  14. rdstudio/references.py +228 -0
  15. rdstudio/reports.py +75 -0
  16. rdstudio/scaffold.py +227 -0
  17. rdstudio/scopes.py +162 -0
  18. rdstudio/search.py +149 -0
  19. rdstudio/serve.py +214 -0
  20. rdstudio/store.py +161 -0
  21. rdstudio/templates/agents/critic.md +20 -0
  22. rdstudio/templates/agents/librarian.md +21 -0
  23. rdstudio/templates/agents/searcher.md +21 -0
  24. rdstudio/templates/agents_section.md +31 -0
  25. rdstudio/templates/claude_section.md +3 -0
  26. rdstudio/templates/knowledge/overview.md +20 -0
  27. rdstudio/templates/knowledge/tasks/bootstrap-knowledge-base.md +45 -0
  28. rdstudio/templates/skills/decision/SKILL.md +47 -0
  29. rdstudio/templates/skills/handoff/SKILL.md +18 -0
  30. rdstudio/templates/skills/ingest-ref/SKILL.md +32 -0
  31. rdstudio/templates/skills/lint-okf/SKILL.md +15 -0
  32. rdstudio/templates/skills/promote/SKILL.md +25 -0
  33. rdstudio/templates/skills/question/SKILL.md +33 -0
  34. rdstudio/templates/skills/record-okf/SKILL.md +61 -0
  35. rdstudio/templates/skills/report/SKILL.md +46 -0
  36. rdstudio/templates/skills/report/template.html +74 -0
  37. rdstudio/templates/skills/search-okf/SKILL.md +36 -0
  38. rdstudio/templates/skills/task/SKILL.md +51 -0
  39. rdstudio/web/app.js +140 -0
  40. rdstudio/web/fonts.css +48 -0
  41. rdstudio/web/icon-192.png +0 -0
  42. rdstudio/web/icon-512.png +0 -0
  43. rdstudio/web/index.html +109 -0
  44. rdstudio/web/js/data.js +125 -0
  45. rdstudio/web/js/diagrams.js +116 -0
  46. rdstudio/web/js/graph.js +396 -0
  47. rdstudio/web/js/knowledge.js +212 -0
  48. rdstudio/web/js/layout-worker.js +15 -0
  49. rdstudio/web/js/layout.js +226 -0
  50. rdstudio/web/js/learn.js +83 -0
  51. rdstudio/web/js/map.js +1293 -0
  52. rdstudio/web/js/markdown.js +173 -0
  53. rdstudio/web/js/pages.js +219 -0
  54. rdstudio/web/js/procedures.js +262 -0
  55. rdstudio/web/js/settings.js +95 -0
  56. rdstudio/web/js/util.js +99 -0
  57. rdstudio/web/report.css +35 -0
  58. rdstudio/web/report.js +104 -0
  59. rdstudio/web/style.css +566 -0
  60. rdstudio/web/sw.js +166 -0
  61. rdstudio/web/themes/cyber.css +139 -0
  62. rdstudio/web/themes/map.css +133 -0
  63. rdstudio/web/themes/notebook.css +134 -0
  64. rdstudio/web/themes/space.css +135 -0
  65. rdstudio/web/themes/studio.css +110 -0
  66. rdstudio/web/vendor/d3.min.js +2 -0
  67. rdstudio/web/vendor/fonts/atkinson-hyperlegible-next-latin-ext-wght-italic.woff2 +0 -0
  68. rdstudio/web/vendor/fonts/atkinson-hyperlegible-next-latin-ext-wght-normal.woff2 +0 -0
  69. rdstudio/web/vendor/fonts/atkinson-hyperlegible-next-latin-wght-italic.woff2 +0 -0
  70. rdstudio/web/vendor/fonts/atkinson-hyperlegible-next-latin-wght-normal.woff2 +0 -0
  71. rdstudio/web/vendor/fonts/caveat-latin-wght-normal.woff2 +0 -0
  72. rdstudio/web/vendor/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  73. rdstudio/web/vendor/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  74. rdstudio/web/vendor/fonts/ibm-plex-sans-latin-wght-italic.woff2 +0 -0
  75. rdstudio/web/vendor/fonts/ibm-plex-sans-latin-wght-normal.woff2 +0 -0
  76. rdstudio/web/vendor/fonts/inter-latin-opsz-italic.woff2 +0 -0
  77. rdstudio/web/vendor/fonts/inter-latin-opsz-normal.woff2 +0 -0
  78. rdstudio/web/vendor/fonts/jetbrains-mono-latin-wght-italic.woff2 +0 -0
  79. rdstudio/web/vendor/fonts/jetbrains-mono-latin-wght-normal.woff2 +0 -0
  80. rdstudio/web/vendor/fonts/literata-latin-ext-opsz-italic.woff2 +0 -0
  81. rdstudio/web/vendor/fonts/literata-latin-ext-opsz-normal.woff2 +0 -0
  82. rdstudio/web/vendor/fonts/literata-latin-opsz-italic.woff2 +0 -0
  83. rdstudio/web/vendor/fonts/literata-latin-opsz-normal.woff2 +0 -0
  84. rdstudio/web/vendor/fonts/source-sans-3-latin-wght-italic.woff2 +0 -0
  85. rdstudio/web/vendor/fonts/source-sans-3-latin-wght-normal.woff2 +0 -0
  86. rdstudio/web/vendor/fonts/source-serif-4-latin-opsz-italic.woff2 +0 -0
  87. rdstudio/web/vendor/fonts/source-serif-4-latin-opsz-normal.woff2 +0 -0
  88. rdstudio/web/vendor/hljs/github-dark.min.css +10 -0
  89. rdstudio/web/vendor/hljs/github.min.css +10 -0
  90. rdstudio/web/vendor/hljs/highlight.min.js +1262 -0
  91. rdstudio/web/vendor/hljs/julia.min.js +18 -0
  92. rdstudio/web/vendor/hljs/latex.min.js +33 -0
  93. rdstudio/web/vendor/hljs/matlab.min.js +15 -0
  94. rdstudio/web/vendor/katex/auto-render.min.js +1 -0
  95. rdstudio/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  96. rdstudio/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  97. rdstudio/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  98. rdstudio/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  99. rdstudio/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  100. rdstudio/web/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  101. rdstudio/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  102. rdstudio/web/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  103. rdstudio/web/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  104. rdstudio/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  105. rdstudio/web/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  106. rdstudio/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  107. rdstudio/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  108. rdstudio/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  109. rdstudio/web/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  110. rdstudio/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  111. rdstudio/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  112. rdstudio/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  113. rdstudio/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  114. rdstudio/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  115. rdstudio/web/vendor/katex/katex.min.css +1 -0
  116. rdstudio/web/vendor/katex/katex.min.js +1 -0
  117. rdstudio/web/vendor/licenses/atkinson-hyperlegible-next-OFL.txt +93 -0
  118. rdstudio/web/vendor/licenses/caveat-OFL.txt +93 -0
  119. rdstudio/web/vendor/licenses/d3-7.9.0.txt +13 -0
  120. rdstudio/web/vendor/licenses/highlightjs-cdn-assets-11.12.0.txt +29 -0
  121. rdstudio/web/vendor/licenses/ibm-plex-mono-OFL.txt +93 -0
  122. rdstudio/web/vendor/licenses/ibm-plex-sans-OFL.txt +93 -0
  123. rdstudio/web/vendor/licenses/inter-OFL.txt +93 -0
  124. rdstudio/web/vendor/licenses/jetbrains-mono-OFL.txt +93 -0
  125. rdstudio/web/vendor/licenses/katex-0.18.7.txt +21 -0
  126. rdstudio/web/vendor/licenses/literata-OFL.txt +93 -0
  127. rdstudio/web/vendor/licenses/markdown-it-15.0.2.txt +22 -0
  128. rdstudio/web/vendor/licenses/markdown-it-footnote-4.0.0.txt +22 -0
  129. rdstudio/web/vendor/licenses/markdown-it-texmath-1.0.0.txt +21 -0
  130. rdstudio/web/vendor/licenses/mermaid-12.0.0.txt +21 -0
  131. rdstudio/web/vendor/licenses/source-sans-3-OFL.txt +93 -0
  132. rdstudio/web/vendor/licenses/source-serif-4-OFL.txt +93 -0
  133. rdstudio/web/vendor/licenses/vega-6.4.0.txt +27 -0
  134. rdstudio/web/vendor/licenses/vega-embed-7.2.0.txt +27 -0
  135. rdstudio/web/vendor/licenses/vega-lite-6.4.3.txt +27 -0
  136. rdstudio/web/vendor/markdown-it-footnote.min.js +2 -0
  137. rdstudio/web/vendor/markdown-it.min.js +14 -0
  138. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/abnfDiagram-DGLNOSUI.mjs +1 -0
  139. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/architecture-7GRP2DOG-HF4J73XS.mjs +1 -0
  140. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/architectureDiagram-IU6H5EVY.mjs +36 -0
  141. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/blockDiagram-FSX6DT2V.mjs +140 -0
  142. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/c4Diagram-YNRVYILN.mjs +38 -0
  143. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-22SYA5HA.mjs +1 -0
  144. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-2FTYKNOQ.mjs +1 -0
  145. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-36VM3LGA.mjs +1 -0
  146. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-3SEI7E7V.mjs +1 -0
  147. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-3T2MNPEU.mjs +1 -0
  148. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-44AU3KAP.mjs +174 -0
  149. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-4MZOTL6B.mjs +1 -0
  150. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-52IMQVNL.mjs +1 -0
  151. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-5NYYYZGL.mjs +1 -0
  152. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-BOZ75642.mjs +15 -0
  153. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-CVBX6EWK.mjs +1 -0
  154. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-D36F7FYG.mjs +14 -0
  155. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-DEGLBX6O.mjs +213 -0
  156. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-F6PIGZHG.mjs +1 -0
  157. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-FE53TMRB.mjs +133 -0
  158. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-FNR2RGIF.mjs +1 -0
  159. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-GDOATIN3.mjs +32 -0
  160. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-H7BX327W.mjs +2 -0
  161. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-HI3EPXIC.mjs +2 -0
  162. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-JCBDXJ5W.mjs +1 -0
  163. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-JGXDHUYS.mjs +1 -0
  164. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-L2B47AAZ.mjs +1 -0
  165. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-LIEGGFIX.mjs +1 -0
  166. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-LLWYPJEZ.mjs +1 -0
  167. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-MFYRHTYG.mjs +1 -0
  168. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-MIE3LDYP.mjs +1 -0
  169. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-MLQVGYRW.mjs +1 -0
  170. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-MNT3HHAC.mjs +1 -0
  171. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-NRRTYJAG.mjs +1 -0
  172. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-OCMJQKYE.mjs +10 -0
  173. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-PKJCTQEK.mjs +339 -0
  174. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-QADHU5CF.mjs +2 -0
  175. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-QVIEFLGF.mjs +1 -0
  176. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-QX7LGXZU.mjs +1 -0
  177. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-R2KH53MI.mjs +1 -0
  178. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-RXM7Z7NK.mjs +2 -0
  179. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-S4TE5DX6.mjs +1 -0
  180. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-SDIBIOXT.mjs +1 -0
  181. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-TDFULVG6.mjs +1 -0
  182. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-TECWHJ6D.mjs +62 -0
  183. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-TINYC7RT.mjs +70 -0
  184. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-V3KJR4XU.mjs +1 -0
  185. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-VTMXW4ES.mjs +1 -0
  186. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-Y5NT6LW5.mjs +88 -0
  187. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-Y5O5OPKC.mjs +3 -0
  188. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-YZG4BXY6.mjs +1 -0
  189. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZZ52EIBP.mjs +1 -0
  190. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-v2-VV4INRHB.mjs +217 -0
  191. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/cose-bilkent-MSIMRU2B.mjs +1 -0
  192. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/cynefin-OW5HDTMX-KMPDV6GS.mjs +1 -0
  193. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/cynefinDiagram-KTGLXFXK.mjs +62 -0
  194. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/dagre-QJJCSHY4.mjs +4 -0
  195. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/diagram-2ZIM67RP.mjs +3 -0
  196. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/diagram-FYHH5O6R.mjs +24 -0
  197. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/diagram-LYLDACMS.mjs +200 -0
  198. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/diagram-TYT3HRBC.mjs +24 -0
  199. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/diagram-XVIN6YVD.mjs +30 -0
  200. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/diagram-ZO2NLLYC.mjs +41 -0
  201. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/ebnfDiagram-TU3FGH35.mjs +1 -0
  202. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/elk-GHAXNSLK.mjs +27 -0
  203. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/erDiagram-XID3VV23.mjs +99 -0
  204. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/eventmodeling-NTZA5JFV-P5QJSWSK.mjs +1 -0
  205. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/flowDiagram-WPHQAKTU.mjs +1 -0
  206. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/ganttDiagram-QACGSKG6.mjs +292 -0
  207. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/gitGraph-4MIJSDKK-V5NEQQYP.mjs +1 -0
  208. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/gitGraphDiagram-EB76O4HC.mjs +106 -0
  209. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/info-A6RAGUB7-IGBHH2L7.mjs +1 -0
  210. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/infoDiagram-343ZKIDN.mjs +2 -0
  211. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/ishikawaDiagram-UEEU5D75.mjs +70 -0
  212. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/journeyDiagram-4QTZOVLF.mjs +139 -0
  213. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/kanban-definition-VEJL4RG2.mjs +89 -0
  214. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/katex-QVRI4OIV.mjs +257 -0
  215. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/mindmap-definition-C5K5ZT7T.mjs +96 -0
  216. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/packet-AYTQ26CC-PJTKDECJ.mjs +1 -0
  217. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/pegDiagram-FHIILVJB.mjs +1 -0
  218. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/pie-WAS4IAKB-U3JFQXZN.mjs +1 -0
  219. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/pieDiagram-S76BU6V7.mjs +39 -0
  220. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/quadrantDiagram-HZBLWUZE.mjs +7 -0
  221. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/radar-RG4KPBEZ-7T2UW32D.mjs +1 -0
  222. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/railroad-74A4TZTK-DZY7ARDL.mjs +1 -0
  223. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/railroad-abnf-HS5TGJTU-LWS535ZK.mjs +1 -0
  224. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/railroad-ebnf-LZEXJU2U-EA3S4ZFM.mjs +1 -0
  225. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/railroad-peg-WCYAUIDC-DUKZONPX.mjs +1 -0
  226. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/railroadDiagram-R6RP3MQ2.mjs +1 -0
  227. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/requirementDiagram-SGBABQV6.mjs +84 -0
  228. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/sankeyDiagram-IM6M5BOW.mjs +40 -0
  229. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/sequenceDiagram-FGJOIFJ2.mjs +169 -0
  230. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/sizeCapture-ERV5D3W5.mjs +1 -0
  231. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-v2-6KDGCZ5J.mjs +291 -0
  232. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/swimlanes-OJBUSBEF.mjs +1 -0
  233. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/swimlanesDiagram-LNXYTHDW.mjs +8 -0
  234. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/timeline-definition-7G6YQRTY.mjs +120 -0
  235. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/treeView-Q6P3EWNA-AABPVERA.mjs +1 -0
  236. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/treemap-WGGIJYW6-F2JSY3V3.mjs +1 -0
  237. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/usecaseDiagram-6Y6TMLBO.mjs +383 -0
  238. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/vennDiagram-6RODYPCK.mjs +34 -0
  239. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/wardley-WFR3VGLG-EIT5NWVN.mjs +1 -0
  240. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/wardleyDiagram-RFZKLAPA.mjs +78 -0
  241. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/xychartDiagram-TWWI2QJZ.mjs +7 -0
  242. rdstudio/web/vendor/mermaid/mermaid.esm.min.mjs +14 -0
  243. rdstudio/web/vendor/texmath.js +368 -0
  244. rdstudio/web/vendor/vega/vega-embed.min.js +7 -0
  245. rdstudio/web/vendor/vega/vega-lite.min.js +2 -0
  246. rdstudio/web/vendor/vega/vega.min.js +1 -0
  247. rdstudio-0.2.0.dist-info/METADATA +201 -0
  248. rdstudio-0.2.0.dist-info/RECORD +251 -0
  249. rdstudio-0.2.0.dist-info/WHEEL +4 -0
  250. rdstudio-0.2.0.dist-info/entry_points.txt +3 -0
  251. rdstudio-0.2.0.dist-info/licenses/LICENSE +21 -0
rdstudio/cli.py ADDED
@@ -0,0 +1,369 @@
1
+ """Command-line interface: ``rdstudio <command>``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from . import __version__
11
+ from . import config as config_mod
12
+ from .okf import Bundle
13
+
14
+
15
+ def _bundle(cfg: config_mod.Config) -> Bundle:
16
+ return Bundle.load(cfg.knowledge_dir)
17
+
18
+
19
+ def cmd_check(args: argparse.Namespace, cfg: config_mod.Config) -> int:
20
+ bundle = _bundle(cfg)
21
+ issues = bundle.lint()
22
+ errors = [i for i in issues if i.level == "error"]
23
+ for issue in issues:
24
+ if issue.level == "error" or args.warnings:
25
+ print(f"{issue.level:7} {issue.path}: {issue.message}")
26
+ warnings = len(issues) - len(errors)
27
+ print(f"{len(bundle.concepts)} concepts, {len(errors)} errors, {warnings} warnings")
28
+ return 1 if errors else 0
29
+
30
+
31
+ def cmd_index(args: argparse.Namespace, cfg: config_mod.Config) -> int:
32
+ bundle = _bundle(cfg)
33
+ changed = bundle.write_indexes()
34
+ for path in changed:
35
+ print(f"wrote {path}")
36
+ if not changed:
37
+ print("indexes up to date")
38
+ return 0
39
+
40
+
41
+ def cmd_search(args: argparse.Namespace, cfg: config_mod.Config) -> int:
42
+ from .search import Index
43
+
44
+ hits = Index(_bundle(cfg)).search(
45
+ " ".join(args.query), limit=args.limit, type=args.type, tags=args.tag, under=args.under
46
+ )
47
+ if args.json:
48
+ print(json.dumps([h.as_dict() for h in hits], indent=2))
49
+ return 0
50
+ for h in hits:
51
+ print(f"{h.score:6.2f} {h.concept.id} [{h.concept.type}] {h.concept.title}")
52
+ if h.snippet:
53
+ print(f" {h.snippet}")
54
+ return 0
55
+
56
+
57
+ def cmd_verify(args: argparse.Namespace, cfg: config_mod.Config) -> int:
58
+ from .store import verify
59
+
60
+ actor = args.by or cfg.human
61
+ if not actor.startswith("human:"):
62
+ print("set [actors] human = \"human:<id>\" in rdstudio.toml or ~/.config/rdstudio/config.toml, "
63
+ "or pass --by human:<id>", file=sys.stderr)
64
+ return 2
65
+ for ref in args.concepts:
66
+ result = verify(cfg.knowledge_dir, ref, actor=actor)
67
+ print(f"verified {result.path} by {actor}")
68
+ return 0
69
+
70
+
71
+ def cmd_build(args: argparse.Namespace, cfg: config_mod.Config) -> int:
72
+ from .build import build
73
+
74
+ out = build(cfg)
75
+ print(f"built {out}")
76
+ return 0
77
+
78
+
79
+ def cmd_export(args: argparse.Namespace, cfg: config_mod.Config) -> int:
80
+ import shutil
81
+ import tempfile
82
+
83
+ from .build import build
84
+
85
+ target = Path(args.target).resolve()
86
+ if target.exists() and any(target.iterdir()) and not args.force:
87
+ print(f"{target} is not empty; pass --force to replace it", file=sys.stderr)
88
+ return 1
89
+ with tempfile.TemporaryDirectory() as tmp:
90
+ site = build(cfg, write_indexes=False, export=True, site=Path(tmp) / "site")
91
+ (site / ".nojekyll").write_text("")
92
+ if target.exists():
93
+ shutil.rmtree(target)
94
+ shutil.copytree(site, target)
95
+ print(f"exported a static snapshot to {target} (project knowledge only)")
96
+ return 0
97
+
98
+
99
+ def cmd_serve(args: argparse.Namespace, cfg: config_mod.Config) -> int:
100
+ from .serve import serve
101
+
102
+ serve(cfg, host=args.host, port=args.port, watch=not args.no_watch, allow_hosts=tuple(args.allow_host))
103
+ return 0
104
+
105
+
106
+ def cmd_init(args: argparse.Namespace, cfg: config_mod.Config) -> int:
107
+ from .scaffold import init
108
+
109
+ target = Path(args.path).resolve()
110
+ for line in init(target, title=args.title, human=args.human, force=args.force):
111
+ print(line)
112
+ return 0
113
+
114
+
115
+ def cmd_procedure(args: argparse.Namespace, cfg: config_mod.Config) -> int:
116
+ from . import procedures
117
+
118
+ bundle = _bundle(cfg)
119
+ if args.action == "list":
120
+ for c in bundle.concepts.values():
121
+ if procedures.is_procedure(c):
122
+ pending = sum(p.get("state") == "pending" for p in c.meta.get("proposals") or [])
123
+ g = procedures.graph_of(c)
124
+ print(f"{c.id} {len(g.nodes)} steps, {len(g.edges)} transitions"
125
+ + (f", {pending} pending proposal(s)" if pending else ""))
126
+ return 0
127
+ cid = bundle.resolve_id(args.procedure or "") or bundle.resolve_id("procedures/" + (args.procedure or ""))
128
+ if cid is None:
129
+ print(f"no procedure {args.procedure!r}", file=sys.stderr)
130
+ return 2
131
+ if args.action == "show":
132
+ c = bundle.concepts[cid]
133
+ for p in c.meta.get("proposals") or []:
134
+ print(f"#{p.get('id')} [{p.get('state')}] by {p.get('by')}: {p.get('rationale')}")
135
+ for e in p.get("edits", []):
136
+ print(" " + ", ".join(f"{k}={v}" for k, v in e.items()))
137
+ return 0
138
+ actor = cfg.human or "human:unknown"
139
+ try:
140
+ result = procedures.resolve(cfg.knowledge_dir, cid, args.proposal, accept=args.action == "apply", actor=actor)
141
+ except procedures.ProcedureError as exc:
142
+ print(exc, file=sys.stderr)
143
+ return 1
144
+ print(f"proposal {result['proposal']} on {cid}: {result['state']}")
145
+ return 0
146
+
147
+
148
+ def cmd_refs(args: argparse.Namespace, cfg: config_mod.Config) -> int:
149
+ from . import references
150
+
151
+ if not references.enabled(cfg):
152
+ print('references are off: add [references] backend = "papis" to rdstudio.toml', file=sys.stderr)
153
+ return 2
154
+ try:
155
+ if args.action == "sync":
156
+ created = references.sync_stubs(cfg)
157
+ for cid in created:
158
+ print(f"created {cid}")
159
+ print(f"{len(created)} new reference stubs")
160
+ elif args.action == "search":
161
+ for hit in references.search(cfg, " ".join(args.terms)):
162
+ print(f"{hit['ref']:32} {hit['year']:5} {hit['title']}")
163
+ elif args.action == "text":
164
+ print(references.text(cfg, args.terms[0], pages=args.pages))
165
+ except references.ReferenceError as exc:
166
+ print(exc, file=sys.stderr)
167
+ return 1
168
+ return 0
169
+
170
+
171
+ def cmd_global(args: argparse.Namespace, cfg: config_mod.Config) -> int:
172
+ from . import scopes
173
+
174
+ if args.action == "init":
175
+ for line in scopes.init_global(Path(args.path), human=args.human):
176
+ print(line)
177
+ return 0
178
+ g = scopes.global_config(cfg)
179
+ if g is None:
180
+ print("no global knowledge base; run `rdstudio global init [~/knowledge]`")
181
+ return 1
182
+ print(f"global knowledge base: {g.root} ({len(_bundle(g).concepts)} concepts)")
183
+ return 0
184
+
185
+
186
+ def cmd_promote(args: argparse.Namespace, cfg: config_mod.Config) -> int:
187
+ from . import scopes
188
+
189
+ try:
190
+ result = scopes.promote(cfg, args.concept, as_id=args.as_id, keep=args.keep, force=args.force)
191
+ except scopes.ScopeError as exc:
192
+ print(exc, file=sys.stderr)
193
+ return 1
194
+ verb = "moved" if result["moved"] else "copied"
195
+ print(f"{verb} {result['from']} to the global knowledge base as {result['to']}")
196
+ if result["broken_links_in_global"]:
197
+ print("links that do not resolve in the global base: " + ", ".join(result["broken_links_in_global"]))
198
+ return 0
199
+
200
+
201
+ def cmd_skills(args: argparse.Namespace, cfg: config_mod.Config) -> int:
202
+ from . import scopes
203
+
204
+ if args.action == "list":
205
+ for scope, skills in scopes.skill_dirs(cfg).items():
206
+ print(f"{scope}: {', '.join(skills) or '(none)'}")
207
+ return 0
208
+ try:
209
+ print(scopes.move_skill(cfg, args.name, args.action.split("-")[-1]))
210
+ except scopes.ScopeError as exc:
211
+ print(exc, file=sys.stderr)
212
+ return 1
213
+ return 0
214
+
215
+
216
+ def cmd_brief(args: argparse.Namespace, cfg: config_mod.Config) -> int:
217
+ from .brief import brief
218
+
219
+ print(brief(cfg))
220
+ return 0
221
+
222
+
223
+ def cmd_learner(args: argparse.Namespace, cfg: config_mod.Config) -> int:
224
+ from . import learner
225
+
226
+ if args.action == "where":
227
+ state = "on" if learner.enabled(cfg) else "off (set [learner] enabled = true in " + str(config_mod.user_config_path()) + ")"
228
+ print(f"learner record: {state}")
229
+ print(f"project id: {learner.project_id(cfg.root)}")
230
+ print(f"folder: {learner.record_dir(cfg)}")
231
+ return 0
232
+ for e in learner.events(cfg)[-args.limit:]:
233
+ rest = {k: v for k, v in e.items() if k not in ("at", "event", "concept")}
234
+ print(f"{e.get('at', '')} {e.get('event', ''):10} {e.get('concept', '') or ''} {json.dumps(rest) if rest else ''}")
235
+ return 0
236
+
237
+
238
+ def cmd_path(args: argparse.Namespace, cfg: config_mod.Config) -> int:
239
+ bundle = _bundle(cfg)
240
+ if args.concept:
241
+ cid = bundle.resolve_id(args.concept)
242
+ if not cid:
243
+ print(f"no such concept: {args.concept}", file=sys.stderr)
244
+ return 1
245
+ ids = bundle.prerequisites(cid) + [cid]
246
+ else:
247
+ ids = sorted(bundle.concepts, key=lambda c: bundle.prerequisite_order()[c]["order"])
248
+ order = bundle.prerequisite_order()
249
+ for i, cid in enumerate(ids, 1):
250
+ print(f"{i:3}. [{order[cid]['depth']}] {cid} {bundle.concepts[cid].title}")
251
+ return 0
252
+
253
+
254
+ def cmd_mcp(args: argparse.Namespace, cfg: config_mod.Config) -> int:
255
+ from .mcp_server import run
256
+
257
+ if args.agent:
258
+ cfg.agent = args.agent
259
+ run(cfg)
260
+ return 0
261
+
262
+
263
+ def build_parser() -> argparse.ArgumentParser:
264
+ p = argparse.ArgumentParser(prog="rdstudio", description=__doc__)
265
+ p.add_argument("--version", action="version", version=f"rdstudio {__version__}")
266
+ p.add_argument("-C", "--directory", help="run as if started in this directory")
267
+ sub = p.add_subparsers(dest="command", required=True)
268
+
269
+ s = sub.add_parser("init", help="scaffold rdstudio into a repository")
270
+ s.add_argument("path", nargs="?", default=".")
271
+ s.add_argument("--title", help="project title")
272
+ s.add_argument("--human", help="your actor id, e.g. human:lachlan")
273
+ s.add_argument("--force", action="store_true", help="overwrite rdstudio-managed files")
274
+ s.set_defaults(func=cmd_init)
275
+
276
+ s = sub.add_parser("check", help="check OKF conformance of the knowledge bundle")
277
+ s.add_argument("-w", "--warnings", action="store_true", help="also print warnings")
278
+ s.set_defaults(func=cmd_check)
279
+
280
+ s = sub.add_parser("index", help="regenerate index.md files")
281
+ s.set_defaults(func=cmd_index)
282
+
283
+ s = sub.add_parser("search", help="search the knowledge bundle")
284
+ s.add_argument("query", nargs="+")
285
+ s.add_argument("-n", "--limit", type=int, default=8)
286
+ s.add_argument("--type")
287
+ s.add_argument("--tag", action="append")
288
+ s.add_argument("--under", help="restrict to a directory")
289
+ s.add_argument("--json", action="store_true")
290
+ s.set_defaults(func=cmd_search)
291
+
292
+ s = sub.add_parser("verify", help="record a human verification of concepts")
293
+ s.add_argument("concepts", nargs="+", help="concept ids or paths")
294
+ s.add_argument("--by", help="actor, default from config")
295
+ s.set_defaults(func=cmd_verify)
296
+
297
+ s = sub.add_parser("build", help="build the dashboard site")
298
+ s.set_defaults(func=cmd_build)
299
+
300
+ s = sub.add_parser("export", help="write a static snapshot of the dashboard (e.g. for GitHub Pages)")
301
+ s.add_argument("target")
302
+ s.add_argument("--force", action="store_true", help="replace a non-empty target directory")
303
+ s.set_defaults(func=cmd_export)
304
+
305
+ s = sub.add_parser("serve", help="serve the dashboard, rebuilding on change")
306
+ s.add_argument("--host", default="127.0.0.1")
307
+ s.add_argument("--port", type=int, default=8000)
308
+ s.add_argument("--no-watch", action="store_true")
309
+ s.add_argument("--allow-host", action="append", default=[], metavar="NAME",
310
+ help="a host name a reverse proxy passes through (Tailscale's *.ts.net names are allowed already)")
311
+ s.set_defaults(func=cmd_serve)
312
+
313
+ s = sub.add_parser("procedure", help="list procedures; show, apply or reject proposed edits")
314
+ s.add_argument("action", choices=["list", "show", "apply", "reject"])
315
+ s.add_argument("procedure", nargs="?")
316
+ s.add_argument("proposal", nargs="?", type=int)
317
+ s.set_defaults(func=cmd_procedure)
318
+
319
+ s = sub.add_parser("refs", help="papis references: sync stubs, search, read PDF text")
320
+ s.add_argument("action", choices=["sync", "search", "text"])
321
+ s.add_argument("terms", nargs="*")
322
+ s.add_argument("--pages")
323
+ s.set_defaults(func=cmd_refs)
324
+
325
+ s = sub.add_parser("global", help="set up or show the global knowledge base")
326
+ s.add_argument("action", choices=["init", "status"])
327
+ s.add_argument("path", nargs="?", default="~/knowledge")
328
+ s.add_argument("--human", help="your actor id, e.g. human:lachlan")
329
+ s.set_defaults(func=cmd_global)
330
+
331
+ s = sub.add_parser("promote", help="move a project concept into the global knowledge base")
332
+ s.add_argument("concept")
333
+ s.add_argument("--as", dest="as_id", help="id in the global base (default: same path)")
334
+ s.add_argument("--keep", action="store_true", help="copy instead of move")
335
+ s.add_argument("--force", action="store_true", help="move even if project concepts link to it")
336
+ s.set_defaults(func=cmd_promote)
337
+
338
+ s = sub.add_parser("skills", help="list skills by scope, or move one between project and user scope")
339
+ s.add_argument("action", choices=["list", "to-user", "to-project"])
340
+ s.add_argument("name", nargs="?")
341
+ s.set_defaults(func=cmd_skills)
342
+
343
+ s = sub.add_parser("brief", help="print a short orientation for an agent session")
344
+ s.set_defaults(func=cmd_brief)
345
+
346
+ s = sub.add_parser("learner", help="where your private learner record is, or its recent events")
347
+ s.add_argument("action", choices=["where", "log"], nargs="?", default="where")
348
+ s.add_argument("-n", "--limit", type=int, default=30)
349
+ s.set_defaults(func=cmd_learner)
350
+
351
+ s = sub.add_parser("path", help="a concept's prerequisites in reading order, or the whole bundle's reading order")
352
+ s.add_argument("concept", nargs="?", help="concept id; omit for the whole bundle")
353
+ s.set_defaults(func=cmd_path)
354
+
355
+ s = sub.add_parser("mcp", help="run the MCP server on stdio")
356
+ s.add_argument("--agent", help="actor id stamped on the agent's writes (default: [actors] agent)")
357
+ s.set_defaults(func=cmd_mcp)
358
+ return p
359
+
360
+
361
+ def main(argv: list[str] | None = None) -> int:
362
+ args = build_parser().parse_args(argv)
363
+ start = Path(args.directory).resolve() if args.directory else None
364
+ cfg = config_mod.load(start)
365
+ return args.func(args, cfg) or 0
366
+
367
+
368
+ if __name__ == "__main__":
369
+ sys.exit(main())
rdstudio/config.py ADDED
@@ -0,0 +1,115 @@
1
+ """Project (``rdstudio.toml``) and user (``~/.config/rdstudio/config.toml``) configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import tomllib
7
+ from dataclasses import dataclass, field
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ PROJECT_FILE = "rdstudio.toml"
12
+
13
+ DEFAULT_CATEGORIES: dict[str, list[str]] = {
14
+ "knowledge": ["{knowledge}/**"],
15
+ "reports": ["{reports}/**"],
16
+ "agent": [".claude/**", ".mcp.json", "CLAUDE.md", "AGENTS.md", ".opencode/**", "opencode.json",
17
+ "opencode.jsonc", "rdstudio.toml"],
18
+ "code": [
19
+ "**/*.py", "**/*.pyi", "**/*.ts", "**/*.js", "**/*.svelte", "**/*.rs", "**/*.c",
20
+ "**/*.cpp", "**/*.h", "**/*.jl", "**/*.go", "**/*.java", "**/*.sh", "**/*.css",
21
+ "**/*.html", "**/*.toml", "**/*.lock", "**/pyproject.toml", "**/package.json",
22
+ ],
23
+ }
24
+
25
+
26
+ def user_config_path() -> Path:
27
+ base = os.environ.get("XDG_CONFIG_HOME") or str(Path.home() / ".config")
28
+ return Path(base) / "rdstudio" / "config.toml"
29
+
30
+
31
+ def _read_toml(path: Path) -> dict[str, Any]:
32
+ if not path.is_file():
33
+ return {}
34
+ with path.open("rb") as fh:
35
+ return tomllib.load(fh)
36
+
37
+
38
+ def find_project_root(start: Path | None = None) -> Path | None:
39
+ """Walk up from ``start`` to the nearest directory holding ``rdstudio.toml``."""
40
+ here = (start or Path.cwd()).resolve()
41
+ for candidate in (here, *here.parents):
42
+ if (candidate / PROJECT_FILE).is_file():
43
+ return candidate
44
+ return None
45
+
46
+
47
+ @dataclass
48
+ class Config:
49
+ root: Path
50
+ title: str = "rdstudio"
51
+ knowledge: str = "knowledge"
52
+ reports: str = "reports"
53
+ output: str = ".rdstudio"
54
+ human: str = "" # e.g. "human:lachlan"
55
+ agent: str = "claude-code/unknown"
56
+ categories: dict[str, list[str]] = field(default_factory=dict)
57
+ global_bundle: Path | None = None
58
+ use_global: bool = True
59
+ is_project: bool = True
60
+ raw: dict[str, Any] = field(default_factory=dict)
61
+
62
+ @property
63
+ def knowledge_dir(self) -> Path:
64
+ return self.root / self.knowledge
65
+
66
+ @property
67
+ def reports_dir(self) -> Path:
68
+ return self.root / self.reports
69
+
70
+ @property
71
+ def output_dir(self) -> Path:
72
+ return self.root / self.output
73
+
74
+ @property
75
+ def site_dir(self) -> Path:
76
+ return self.output_dir / "site"
77
+
78
+ def category_globs(self) -> dict[str, list[str]]:
79
+ cats = self.categories or DEFAULT_CATEGORIES
80
+ fmt = {"knowledge": self.knowledge, "reports": self.reports}
81
+ return {name: [g.format(**fmt) for g in globs] for name, globs in cats.items()}
82
+
83
+
84
+ def load(start: Path | None = None) -> Config:
85
+ """Load configuration for the project containing ``start`` (default: cwd).
86
+
87
+ Outside a project, the root is ``start`` itself and defaults apply.
88
+ """
89
+ user = _read_toml(user_config_path())
90
+ root = find_project_root(start)
91
+ is_project = root is not None
92
+ root = root or (start or Path.cwd()).resolve()
93
+ project = _read_toml(root / PROJECT_FILE)
94
+
95
+ proj = project.get("project", {})
96
+ paths = project.get("paths", {})
97
+ actors = {**user.get("actors", {}), **project.get("actors", {})}
98
+ glob = user.get("global", {})
99
+
100
+ global_path = glob.get("path")
101
+ cfg = Config(
102
+ root=root,
103
+ title=proj.get("title") or root.name,
104
+ knowledge=paths.get("knowledge", "knowledge"),
105
+ reports=paths.get("reports", "reports"),
106
+ output=paths.get("output", ".rdstudio"),
107
+ human=actors.get("human", ""),
108
+ agent=actors.get("agent", "claude-code/unknown"),
109
+ categories=project.get("changes", {}).get("categories", {}),
110
+ global_bundle=Path(global_path).expanduser() if global_path else None,
111
+ use_global=project.get("global", {}).get("enabled", True),
112
+ is_project=is_project,
113
+ raw=project,
114
+ )
115
+ return cfg
rdstudio/gitlog.py ADDED
@@ -0,0 +1,92 @@
1
+ """Commit-by-commit change lists, with each file sorted into a category."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ import subprocess
7
+ from functools import lru_cache
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ _STATUS = {"A": "added", "M": "modified", "D": "deleted", "R": "renamed", "C": "copied", "T": "modified"}
12
+
13
+
14
+ @lru_cache(maxsize=256)
15
+ def _glob_regex(pattern: str) -> re.Pattern[str]:
16
+ out, i = "", 0
17
+ while i < len(pattern):
18
+ if pattern.startswith("**/", i):
19
+ out, i = out + "(?:.*/)?", i + 3
20
+ elif pattern.startswith("**", i):
21
+ out, i = out + ".*", i + 2
22
+ elif pattern[i] == "*":
23
+ out, i = out + "[^/]*", i + 1
24
+ elif pattern[i] == "?":
25
+ out, i = out + "[^/]", i + 1
26
+ else:
27
+ out, i = out + re.escape(pattern[i]), i + 1
28
+ return re.compile(out + r"\Z")
29
+
30
+
31
+ def categorise(path: str, categories: dict[str, list[str]]) -> str:
32
+ for name, globs in categories.items():
33
+ if any(_glob_regex(g).match(path) for g in globs):
34
+ return name
35
+ return "other"
36
+
37
+
38
+ def _git(root: Path, *args: str) -> str | None:
39
+ try:
40
+ proc = subprocess.run(
41
+ ["git", "-C", str(root), *args], capture_output=True, text=True, timeout=20, check=False
42
+ )
43
+ except (OSError, subprocess.TimeoutExpired):
44
+ return None
45
+ return proc.stdout if proc.returncode == 0 else None
46
+
47
+
48
+ def history(root: Path, categories: dict[str, list[str]], limit: int = 200,
49
+ exclude: tuple[str, ...] = (".rdstudio/",)) -> dict[str, Any]:
50
+ """Recent commits (newest first), plus uncommitted changes as a pseudo-commit.
51
+
52
+ Paths under ``exclude`` prefixes (build output) are left out."""
53
+ if _git(root, "rev-parse", "--is-inside-work-tree") is None:
54
+ return {"available": False, "commits": []}
55
+ commits: list[dict[str, Any]] = []
56
+
57
+ status = _git(root, "status", "--porcelain", "-uall") or ""
58
+ pending = []
59
+ for line in status.splitlines():
60
+ if len(line) < 4:
61
+ continue
62
+ code, path = line[:2], line[3:]
63
+ if " -> " in path:
64
+ path = path.split(" -> ", 1)[1]
65
+ if path.strip('"').startswith(exclude):
66
+ continue
67
+ kind = "added" if "?" in code or "A" in code else "deleted" if "D" in code else "modified"
68
+ pending.append({"path": path.strip('"'), "status": kind, "category": categorise(path, categories)})
69
+ if pending:
70
+ commits.append({"hash": "", "short": "", "author": "", "date": "", "subject": "Uncommitted changes",
71
+ "files": pending, "pending": True})
72
+
73
+ out = _git(root, "log", f"-n{limit}", "--name-status", "--no-renames", "--format=%x1e%H%x1f%h%x1f%an%x1f%aI%x1f%s%x1f%P")
74
+ for block in (out or "").split("\x1e"):
75
+ if not block.strip():
76
+ continue
77
+ header, _, rest = block.partition("\n")
78
+ full, short, author, date, subject, parents = (header.split("\x1f") + [""] * 6)[:6]
79
+ files = []
80
+ for line in rest.splitlines():
81
+ parts = line.split("\t")
82
+ if len(parts) < 2:
83
+ continue
84
+ path = parts[-1]
85
+ if path.startswith(exclude):
86
+ continue
87
+ files.append({"path": path, "status": _STATUS.get(parts[0][:1], "modified"),
88
+ "category": categorise(path, categories)})
89
+ commits.append({"hash": full, "short": short, "author": author, "date": date, "subject": subject,
90
+ "files": files, "merge": len(parents.split()) > 1})
91
+ branch = (_git(root, "rev-parse", "--abbrev-ref", "HEAD") or "").strip()
92
+ return {"available": True, "branch": branch, "commits": commits}
rdstudio/launcher.py ADDED
@@ -0,0 +1,63 @@
1
+ """The ``rdstudio`` command: runs the Node command line (packages/cli in the
2
+ repository), bundled into this package at release, with Node from the
3
+ ``nodejs-wheel-binaries`` dependency, so ``uv tool install rdstudio`` needs
4
+ nothing else.
5
+
6
+ - In a checkout (an editable install), the TypeScript sources run directly.
7
+ - ``RDSTUDIO_PYTHON=1`` (or the ``rdstudio-py`` command) runs the Python
8
+ command line instead, while it lasts; so does a missing Node program.
9
+ - ``RDSTUDIO_NODE`` names another node executable (24 or later).
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ import shutil
16
+ import subprocess
17
+ import sys
18
+ from pathlib import Path
19
+
20
+ HERE = Path(__file__).resolve().parent
21
+ BUNDLE = HERE / "_node" / "rdstudio.mjs" # built by `npm run bundle --workspace rdstudio`
22
+ SOURCE = HERE.parent.parent / "packages" / "cli" / "src" / "main.ts" # present in a checkout
23
+
24
+
25
+ def node_executable() -> str | None:
26
+ if os.environ.get("RDSTUDIO_NODE"):
27
+ return os.environ["RDSTUDIO_NODE"]
28
+ try:
29
+ from nodejs_wheel import executable
30
+ except ImportError:
31
+ return shutil.which("node")
32
+ return os.path.join(executable.ROOT_DIR, "node.exe" if os.name == "nt" else os.path.join("bin", "node"))
33
+
34
+
35
+ def program() -> Path | None:
36
+ """The Node program to run: the sources in a checkout, else the bundle."""
37
+ if SOURCE.is_file() and (SOURCE.parents[3] / "node_modules").is_dir(): # the repository root
38
+ return SOURCE
39
+ return BUNDLE if BUNDLE.is_file() else None
40
+
41
+
42
+ def python_cli() -> None:
43
+ from .cli import main as python_main
44
+
45
+ sys.exit(python_main())
46
+
47
+
48
+ def main() -> None:
49
+ if os.environ.get("RDSTUDIO_PYTHON"):
50
+ python_cli()
51
+ script, node = program(), node_executable()
52
+ if script is None or node is None:
53
+ print("rdstudio: the Node command line is not available here; using the Python one.", file=sys.stderr)
54
+ python_cli()
55
+ env = {**os.environ, "RDSTUDIO_WEB_DIR": str(HERE / "web"), "RDSTUDIO_TEMPLATES_DIR": str(HERE / "templates")}
56
+ args = [node, str(script), *sys.argv[1:]]
57
+ if os.name == "posix":
58
+ os.execve(node, args, env) # hand over the process: stdio for MCP, signals for serve
59
+ sys.exit(subprocess.run(args, env=env).returncode)
60
+
61
+
62
+ if __name__ == "__main__":
63
+ main()