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.
- rdstudio/__init__.py +3 -0
- rdstudio/_node/rdstudio.mjs +59351 -0
- rdstudio/brief.py +46 -0
- rdstudio/build.py +218 -0
- rdstudio/classify.py +131 -0
- rdstudio/cli.py +369 -0
- rdstudio/config.py +115 -0
- rdstudio/gitlog.py +92 -0
- rdstudio/launcher.py +63 -0
- rdstudio/learner.py +187 -0
- rdstudio/mcp_server.py +313 -0
- rdstudio/okf.py +775 -0
- rdstudio/procedures.py +233 -0
- rdstudio/references.py +228 -0
- rdstudio/reports.py +75 -0
- rdstudio/scaffold.py +227 -0
- rdstudio/scopes.py +162 -0
- rdstudio/search.py +149 -0
- rdstudio/serve.py +214 -0
- rdstudio/store.py +161 -0
- rdstudio/templates/agents/critic.md +20 -0
- rdstudio/templates/agents/librarian.md +21 -0
- rdstudio/templates/agents/searcher.md +21 -0
- rdstudio/templates/agents_section.md +31 -0
- rdstudio/templates/claude_section.md +3 -0
- rdstudio/templates/knowledge/overview.md +20 -0
- rdstudio/templates/knowledge/tasks/bootstrap-knowledge-base.md +45 -0
- rdstudio/templates/skills/decision/SKILL.md +47 -0
- rdstudio/templates/skills/handoff/SKILL.md +18 -0
- rdstudio/templates/skills/ingest-ref/SKILL.md +32 -0
- rdstudio/templates/skills/lint-okf/SKILL.md +15 -0
- rdstudio/templates/skills/promote/SKILL.md +25 -0
- rdstudio/templates/skills/question/SKILL.md +33 -0
- rdstudio/templates/skills/record-okf/SKILL.md +61 -0
- rdstudio/templates/skills/report/SKILL.md +46 -0
- rdstudio/templates/skills/report/template.html +74 -0
- rdstudio/templates/skills/search-okf/SKILL.md +36 -0
- rdstudio/templates/skills/task/SKILL.md +51 -0
- rdstudio/web/app.js +140 -0
- rdstudio/web/fonts.css +48 -0
- rdstudio/web/icon-192.png +0 -0
- rdstudio/web/icon-512.png +0 -0
- rdstudio/web/index.html +109 -0
- rdstudio/web/js/data.js +125 -0
- rdstudio/web/js/diagrams.js +116 -0
- rdstudio/web/js/graph.js +396 -0
- rdstudio/web/js/knowledge.js +212 -0
- rdstudio/web/js/layout-worker.js +15 -0
- rdstudio/web/js/layout.js +226 -0
- rdstudio/web/js/learn.js +83 -0
- rdstudio/web/js/map.js +1293 -0
- rdstudio/web/js/markdown.js +173 -0
- rdstudio/web/js/pages.js +219 -0
- rdstudio/web/js/procedures.js +262 -0
- rdstudio/web/js/settings.js +95 -0
- rdstudio/web/js/util.js +99 -0
- rdstudio/web/report.css +35 -0
- rdstudio/web/report.js +104 -0
- rdstudio/web/style.css +566 -0
- rdstudio/web/sw.js +166 -0
- rdstudio/web/themes/cyber.css +139 -0
- rdstudio/web/themes/map.css +133 -0
- rdstudio/web/themes/notebook.css +134 -0
- rdstudio/web/themes/space.css +135 -0
- rdstudio/web/themes/studio.css +110 -0
- rdstudio/web/vendor/d3.min.js +2 -0
- rdstudio/web/vendor/fonts/atkinson-hyperlegible-next-latin-ext-wght-italic.woff2 +0 -0
- rdstudio/web/vendor/fonts/atkinson-hyperlegible-next-latin-ext-wght-normal.woff2 +0 -0
- rdstudio/web/vendor/fonts/atkinson-hyperlegible-next-latin-wght-italic.woff2 +0 -0
- rdstudio/web/vendor/fonts/atkinson-hyperlegible-next-latin-wght-normal.woff2 +0 -0
- rdstudio/web/vendor/fonts/caveat-latin-wght-normal.woff2 +0 -0
- rdstudio/web/vendor/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- rdstudio/web/vendor/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
- rdstudio/web/vendor/fonts/ibm-plex-sans-latin-wght-italic.woff2 +0 -0
- rdstudio/web/vendor/fonts/ibm-plex-sans-latin-wght-normal.woff2 +0 -0
- rdstudio/web/vendor/fonts/inter-latin-opsz-italic.woff2 +0 -0
- rdstudio/web/vendor/fonts/inter-latin-opsz-normal.woff2 +0 -0
- rdstudio/web/vendor/fonts/jetbrains-mono-latin-wght-italic.woff2 +0 -0
- rdstudio/web/vendor/fonts/jetbrains-mono-latin-wght-normal.woff2 +0 -0
- rdstudio/web/vendor/fonts/literata-latin-ext-opsz-italic.woff2 +0 -0
- rdstudio/web/vendor/fonts/literata-latin-ext-opsz-normal.woff2 +0 -0
- rdstudio/web/vendor/fonts/literata-latin-opsz-italic.woff2 +0 -0
- rdstudio/web/vendor/fonts/literata-latin-opsz-normal.woff2 +0 -0
- rdstudio/web/vendor/fonts/source-sans-3-latin-wght-italic.woff2 +0 -0
- rdstudio/web/vendor/fonts/source-sans-3-latin-wght-normal.woff2 +0 -0
- rdstudio/web/vendor/fonts/source-serif-4-latin-opsz-italic.woff2 +0 -0
- rdstudio/web/vendor/fonts/source-serif-4-latin-opsz-normal.woff2 +0 -0
- rdstudio/web/vendor/hljs/github-dark.min.css +10 -0
- rdstudio/web/vendor/hljs/github.min.css +10 -0
- rdstudio/web/vendor/hljs/highlight.min.js +1262 -0
- rdstudio/web/vendor/hljs/julia.min.js +18 -0
- rdstudio/web/vendor/hljs/latex.min.js +33 -0
- rdstudio/web/vendor/hljs/matlab.min.js +15 -0
- rdstudio/web/vendor/katex/auto-render.min.js +1 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- rdstudio/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- rdstudio/web/vendor/katex/katex.min.css +1 -0
- rdstudio/web/vendor/katex/katex.min.js +1 -0
- rdstudio/web/vendor/licenses/atkinson-hyperlegible-next-OFL.txt +93 -0
- rdstudio/web/vendor/licenses/caveat-OFL.txt +93 -0
- rdstudio/web/vendor/licenses/d3-7.9.0.txt +13 -0
- rdstudio/web/vendor/licenses/highlightjs-cdn-assets-11.12.0.txt +29 -0
- rdstudio/web/vendor/licenses/ibm-plex-mono-OFL.txt +93 -0
- rdstudio/web/vendor/licenses/ibm-plex-sans-OFL.txt +93 -0
- rdstudio/web/vendor/licenses/inter-OFL.txt +93 -0
- rdstudio/web/vendor/licenses/jetbrains-mono-OFL.txt +93 -0
- rdstudio/web/vendor/licenses/katex-0.18.7.txt +21 -0
- rdstudio/web/vendor/licenses/literata-OFL.txt +93 -0
- rdstudio/web/vendor/licenses/markdown-it-15.0.2.txt +22 -0
- rdstudio/web/vendor/licenses/markdown-it-footnote-4.0.0.txt +22 -0
- rdstudio/web/vendor/licenses/markdown-it-texmath-1.0.0.txt +21 -0
- rdstudio/web/vendor/licenses/mermaid-12.0.0.txt +21 -0
- rdstudio/web/vendor/licenses/source-sans-3-OFL.txt +93 -0
- rdstudio/web/vendor/licenses/source-serif-4-OFL.txt +93 -0
- rdstudio/web/vendor/licenses/vega-6.4.0.txt +27 -0
- rdstudio/web/vendor/licenses/vega-embed-7.2.0.txt +27 -0
- rdstudio/web/vendor/licenses/vega-lite-6.4.3.txt +27 -0
- rdstudio/web/vendor/markdown-it-footnote.min.js +2 -0
- rdstudio/web/vendor/markdown-it.min.js +14 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/abnfDiagram-DGLNOSUI.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/architecture-7GRP2DOG-HF4J73XS.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/architectureDiagram-IU6H5EVY.mjs +36 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/blockDiagram-FSX6DT2V.mjs +140 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/c4Diagram-YNRVYILN.mjs +38 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-22SYA5HA.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-2FTYKNOQ.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-36VM3LGA.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-3SEI7E7V.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-3T2MNPEU.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-44AU3KAP.mjs +174 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-4MZOTL6B.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-52IMQVNL.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-5NYYYZGL.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-BOZ75642.mjs +15 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-CVBX6EWK.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-D36F7FYG.mjs +14 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-DEGLBX6O.mjs +213 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-F6PIGZHG.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-FE53TMRB.mjs +133 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-FNR2RGIF.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-GDOATIN3.mjs +32 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-H7BX327W.mjs +2 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-HI3EPXIC.mjs +2 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-JCBDXJ5W.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-JGXDHUYS.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-L2B47AAZ.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-LIEGGFIX.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-LLWYPJEZ.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-MFYRHTYG.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-MIE3LDYP.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-MLQVGYRW.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-MNT3HHAC.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-NRRTYJAG.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-OCMJQKYE.mjs +10 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-PKJCTQEK.mjs +339 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-QADHU5CF.mjs +2 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-QVIEFLGF.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-QX7LGXZU.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-R2KH53MI.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-RXM7Z7NK.mjs +2 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-S4TE5DX6.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-SDIBIOXT.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-TDFULVG6.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-TECWHJ6D.mjs +62 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-TINYC7RT.mjs +70 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-V3KJR4XU.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-VTMXW4ES.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-Y5NT6LW5.mjs +88 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-Y5O5OPKC.mjs +3 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-YZG4BXY6.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZZ52EIBP.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-v2-VV4INRHB.mjs +217 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/cose-bilkent-MSIMRU2B.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/cynefin-OW5HDTMX-KMPDV6GS.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/cynefinDiagram-KTGLXFXK.mjs +62 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/dagre-QJJCSHY4.mjs +4 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/diagram-2ZIM67RP.mjs +3 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/diagram-FYHH5O6R.mjs +24 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/diagram-LYLDACMS.mjs +200 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/diagram-TYT3HRBC.mjs +24 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/diagram-XVIN6YVD.mjs +30 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/diagram-ZO2NLLYC.mjs +41 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/ebnfDiagram-TU3FGH35.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/elk-GHAXNSLK.mjs +27 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/erDiagram-XID3VV23.mjs +99 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/eventmodeling-NTZA5JFV-P5QJSWSK.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/flowDiagram-WPHQAKTU.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/ganttDiagram-QACGSKG6.mjs +292 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/gitGraph-4MIJSDKK-V5NEQQYP.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/gitGraphDiagram-EB76O4HC.mjs +106 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/info-A6RAGUB7-IGBHH2L7.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/infoDiagram-343ZKIDN.mjs +2 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/ishikawaDiagram-UEEU5D75.mjs +70 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/journeyDiagram-4QTZOVLF.mjs +139 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/kanban-definition-VEJL4RG2.mjs +89 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/katex-QVRI4OIV.mjs +257 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/mindmap-definition-C5K5ZT7T.mjs +96 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/packet-AYTQ26CC-PJTKDECJ.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/pegDiagram-FHIILVJB.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/pie-WAS4IAKB-U3JFQXZN.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/pieDiagram-S76BU6V7.mjs +39 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/quadrantDiagram-HZBLWUZE.mjs +7 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/radar-RG4KPBEZ-7T2UW32D.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/railroad-74A4TZTK-DZY7ARDL.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/railroad-abnf-HS5TGJTU-LWS535ZK.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/railroad-ebnf-LZEXJU2U-EA3S4ZFM.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/railroad-peg-WCYAUIDC-DUKZONPX.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/railroadDiagram-R6RP3MQ2.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/requirementDiagram-SGBABQV6.mjs +84 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/sankeyDiagram-IM6M5BOW.mjs +40 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/sequenceDiagram-FGJOIFJ2.mjs +169 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/sizeCapture-ERV5D3W5.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-v2-6KDGCZ5J.mjs +291 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/swimlanes-OJBUSBEF.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/swimlanesDiagram-LNXYTHDW.mjs +8 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/timeline-definition-7G6YQRTY.mjs +120 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/treeView-Q6P3EWNA-AABPVERA.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/treemap-WGGIJYW6-F2JSY3V3.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/usecaseDiagram-6Y6TMLBO.mjs +383 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/vennDiagram-6RODYPCK.mjs +34 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/wardley-WFR3VGLG-EIT5NWVN.mjs +1 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/wardleyDiagram-RFZKLAPA.mjs +78 -0
- rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/xychartDiagram-TWWI2QJZ.mjs +7 -0
- rdstudio/web/vendor/mermaid/mermaid.esm.min.mjs +14 -0
- rdstudio/web/vendor/texmath.js +368 -0
- rdstudio/web/vendor/vega/vega-embed.min.js +7 -0
- rdstudio/web/vendor/vega/vega-lite.min.js +2 -0
- rdstudio/web/vendor/vega/vega.min.js +1 -0
- rdstudio-0.2.0.dist-info/METADATA +201 -0
- rdstudio-0.2.0.dist-info/RECORD +251 -0
- rdstudio-0.2.0.dist-info/WHEEL +4 -0
- rdstudio-0.2.0.dist-info/entry_points.txt +3 -0
- 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()
|