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/brief.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""A short orientation for the start of an agent session (``rdstudio brief``).
|
|
2
|
+
|
|
3
|
+
Printed by a Claude Code SessionStart hook and returned by the MCP ``brief``
|
|
4
|
+
tool, so it must stay small: it tells the
|
|
5
|
+
agent what exists and where to look, not the content itself.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from . import gitlog
|
|
11
|
+
from .config import Config
|
|
12
|
+
from .okf import Bundle
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def brief(cfg: Config, *, commits: int = 3) -> str:
|
|
16
|
+
b = Bundle.load(cfg.knowledge_dir)
|
|
17
|
+
if not b.concepts:
|
|
18
|
+
return f"rdstudio: no knowledge base found in {cfg.knowledge}/ (run `rdstudio init`)."
|
|
19
|
+
lines = [f"Project knowledge base ({cfg.title}): {len(b.concepts)} concepts in {cfg.knowledge}/, OKF format."]
|
|
20
|
+
root = b.directories[""]
|
|
21
|
+
dirs = []
|
|
22
|
+
for d in sorted(root.children):
|
|
23
|
+
count = sum(1 for cid in b.concepts if cid.startswith(d + "/"))
|
|
24
|
+
dirs.append(f"{d} ({count})")
|
|
25
|
+
if dirs:
|
|
26
|
+
lines.append("Directories: " + ", ".join(dirs))
|
|
27
|
+
active = [c for c in b.concepts.values() if c.type.lower() == "task" and "active" in c.tags]
|
|
28
|
+
if active:
|
|
29
|
+
lines.append("Active tasks: " + "; ".join(f"{c.title} [{c.id}]" for c in active[:5]))
|
|
30
|
+
stale = sum(c.verification_stale for c in b.concepts.values())
|
|
31
|
+
questions = sum(c.type.lower() == "question" and "answered" not in c.tags for c in b.concepts.values())
|
|
32
|
+
unverified = sum(c.trust == "unverified" for c in b.concepts.values())
|
|
33
|
+
lines.append(f"Awaiting the developer: {stale} changed since review, {questions} open questions, {unverified} unverified.")
|
|
34
|
+
history = gitlog.history(cfg.root, cfg.category_globs(), limit=commits, exclude=(cfg.output.strip("/") + "/",))
|
|
35
|
+
recent = [c["subject"] for c in history.get("commits", []) if not c.get("pending")][:commits]
|
|
36
|
+
if recent:
|
|
37
|
+
lines.append("Recent commits: " + " | ".join(recent))
|
|
38
|
+
from .scopes import global_config
|
|
39
|
+
|
|
40
|
+
g = global_config(cfg)
|
|
41
|
+
if g is not None:
|
|
42
|
+
count = len(Bundle.load(g.knowledge_dir).concepts)
|
|
43
|
+
lines.append(f"A global knowledge base (cross-project; {count} concepts) is searchable with scope=\"global\".")
|
|
44
|
+
lines.append("Search it (rdstudio MCP tools, the search-okf skill, or the librarian subagent) before re-deriving; "
|
|
45
|
+
"record decisions, questions and findings as you go.")
|
|
46
|
+
return "\n".join(lines)
|
rdstudio/build.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Build the static dashboard: web assets plus JSON data under ``.rdstudio/site``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import shutil
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from . import gitlog, learner, reports
|
|
12
|
+
from .config import Config
|
|
13
|
+
from .okf import Bundle, FrontmatterError, headings, iso, jsonable, now, split_frontmatter
|
|
14
|
+
|
|
15
|
+
WEB_DIR = Path(__file__).parent / "web"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _sync_tree(src: Path, dst: Path, *, skip: frozenset[str] = frozenset()) -> None:
|
|
19
|
+
"""Copy ``src`` into ``dst``, skipping files whose size and mtime already match
|
|
20
|
+
and the relative paths in ``skip``."""
|
|
21
|
+
for path in src.rglob("*"):
|
|
22
|
+
rel = path.relative_to(src)
|
|
23
|
+
if rel.as_posix() in skip:
|
|
24
|
+
continue
|
|
25
|
+
target = dst / rel
|
|
26
|
+
if path.is_dir():
|
|
27
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
continue
|
|
29
|
+
st = path.stat()
|
|
30
|
+
if target.exists():
|
|
31
|
+
tt = target.stat()
|
|
32
|
+
if tt.st_size == st.st_size and int(tt.st_mtime) == int(st.st_mtime):
|
|
33
|
+
continue
|
|
34
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
shutil.copy2(path, target)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def service_worker() -> str:
|
|
39
|
+
"""The service worker, stamped with a fingerprint of the app's files, so a
|
|
40
|
+
new rdstudio (or an edited app file) replaces what browsers have cached."""
|
|
41
|
+
digest = hashlib.sha256()
|
|
42
|
+
for path in sorted(WEB_DIR.rglob("*")):
|
|
43
|
+
if path.is_file():
|
|
44
|
+
st = path.stat()
|
|
45
|
+
digest.update(f"{path.relative_to(WEB_DIR).as_posix()}:{st.st_size}:{st.st_mtime_ns}\n".encode())
|
|
46
|
+
return (WEB_DIR / "sw.js").read_text(encoding="utf-8").replace("__SHELL__", digest.hexdigest()[:16])
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _write_if_changed(path: Path, content: str) -> None:
|
|
50
|
+
if path.exists() and path.read_text(encoding="utf-8") == content:
|
|
51
|
+
return
|
|
52
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
path.write_text(content, encoding="utf-8")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _dump(value: Any) -> str:
|
|
57
|
+
return json.dumps(value, ensure_ascii=False, separators=(",", ":"), default=str)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def concept_record(bundle: Bundle, cid: str) -> dict[str, Any]:
|
|
61
|
+
c = bundle.concepts[cid]
|
|
62
|
+
rec = c.summary()
|
|
63
|
+
place = bundle.prerequisite_order()[cid]
|
|
64
|
+
rec.update({
|
|
65
|
+
"hash": learner.content_hash(c.body), # the version learner-record events refer to
|
|
66
|
+
"order": place["order"], # reading order from requires links
|
|
67
|
+
"depth": place["depth"], # longest chain of prerequisites below this note
|
|
68
|
+
"requires": bundle.requires_graph()[cid],
|
|
69
|
+
"meta": jsonable(c.meta),
|
|
70
|
+
"directory": c.directory,
|
|
71
|
+
"links": [{"target": l.target, "kind": l.kind, "broken": l.broken, "rel": l.rel} for l in c.links],
|
|
72
|
+
"backlinks": bundle.backlinks(cid),
|
|
73
|
+
"headings": [{"level": h.level, "text": h.text, "slug": h.slug} for h in headings(c.body)],
|
|
74
|
+
"generated_at": iso(c.generated_at) if c.generated_at else None,
|
|
75
|
+
"mtime": int(c.mtime),
|
|
76
|
+
})
|
|
77
|
+
return rec
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def tree_record(bundle: Bundle) -> dict[str, Any]:
|
|
81
|
+
out = {}
|
|
82
|
+
for did, d in bundle.directories.items():
|
|
83
|
+
overview = bundle.overview_for(did)
|
|
84
|
+
out[did] = {
|
|
85
|
+
"id": did,
|
|
86
|
+
"name": d.name,
|
|
87
|
+
"concepts": sorted(d.concepts, key=lambda c: bundle.concepts[c].title.lower()),
|
|
88
|
+
"children": sorted(d.children),
|
|
89
|
+
"overview": overview.id if overview else None,
|
|
90
|
+
"index": bundle.render_index(did),
|
|
91
|
+
}
|
|
92
|
+
return out
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _skill_files(root: Path, *, user: bool = True) -> dict[str, list[dict[str, Any]]]:
|
|
96
|
+
"""Skills (``.claude/skills/<name>/SKILL.md``) and agents (``.claude/agents/*.md``),
|
|
97
|
+
from the project and, unless ``user`` is false, from ``~/.claude``."""
|
|
98
|
+
out: dict[str, list[dict[str, Any]]] = {"skills": [], "agents": []}
|
|
99
|
+
specs = [("skills", "project", root, sorted((root / ".claude" / "skills").glob("*/SKILL.md"))),
|
|
100
|
+
("agents", "project", root, sorted((root / ".claude" / "agents").glob("*.md")))]
|
|
101
|
+
if user:
|
|
102
|
+
home = Path.home()
|
|
103
|
+
specs += [("skills", "user", home, sorted((home / ".claude" / "skills").glob("*/SKILL.md"))),
|
|
104
|
+
("agents", "user", home, sorted((home / ".claude" / "agents").glob("*.md")))]
|
|
105
|
+
for kind, scope, base, paths in specs:
|
|
106
|
+
for path in paths:
|
|
107
|
+
try:
|
|
108
|
+
meta, body = split_frontmatter(path.read_text(encoding="utf-8"))
|
|
109
|
+
except FrontmatterError:
|
|
110
|
+
meta, body = {}, path.read_text(encoding="utf-8")
|
|
111
|
+
meta = meta or {}
|
|
112
|
+
name = meta.get("name") or (path.parent.name if kind == "skills" else path.stem)
|
|
113
|
+
out[kind].append({
|
|
114
|
+
"name": str(name),
|
|
115
|
+
"description": str(meta.get("description") or ""),
|
|
116
|
+
"path": ("~/" if scope == "user" else "") + path.relative_to(base).as_posix(),
|
|
117
|
+
"scope": scope,
|
|
118
|
+
"meta": jsonable(meta),
|
|
119
|
+
"body": body,
|
|
120
|
+
})
|
|
121
|
+
return out
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def build(cfg: Config, *, write_indexes: bool | None = None, export: bool = False,
|
|
125
|
+
site: Path | None = None) -> Path:
|
|
126
|
+
"""Build the dashboard into ``site`` (default ``.rdstudio/site``).
|
|
127
|
+
|
|
128
|
+
``export`` produces a snapshot for static hosting: no user-level skills, no
|
|
129
|
+
uncommitted changes, and the page does not poll for updates."""
|
|
130
|
+
site = site or cfg.site_dir
|
|
131
|
+
data = site / "data"
|
|
132
|
+
site.mkdir(parents=True, exist_ok=True)
|
|
133
|
+
_sync_tree(WEB_DIR, site, skip=frozenset({"sw.js"}))
|
|
134
|
+
_write_if_changed(site / "sw.js", service_worker())
|
|
135
|
+
|
|
136
|
+
auto_index = cfg.raw.get("index", {}).get("auto", True) if write_indexes is None else write_indexes
|
|
137
|
+
bundle = Bundle.load(cfg.knowledge_dir)
|
|
138
|
+
if auto_index and cfg.knowledge_dir.is_dir():
|
|
139
|
+
if bundle.write_indexes():
|
|
140
|
+
bundle = Bundle.load(cfg.knowledge_dir)
|
|
141
|
+
|
|
142
|
+
concepts = [concept_record(bundle, cid) for cid in sorted(bundle.concepts)]
|
|
143
|
+
tree = tree_record(bundle)
|
|
144
|
+
changes = gitlog.history(cfg.root, cfg.category_globs(), exclude=(cfg.output.strip("/") + "/",))
|
|
145
|
+
if export:
|
|
146
|
+
changes["commits"] = [c for c in changes["commits"] if not c.get("pending")]
|
|
147
|
+
report_items = reports.scan(cfg.reports_dir, cfg.knowledge, cfg.reports)
|
|
148
|
+
skills = _skill_files(cfg.root, user=not export)
|
|
149
|
+
issues = [{"path": i.path, "level": i.level, "code": i.code, "message": i.message} for i in bundle.lint()]
|
|
150
|
+
|
|
151
|
+
payload = {
|
|
152
|
+
"concepts.json": _dump(concepts),
|
|
153
|
+
"tree.json": _dump(tree),
|
|
154
|
+
"changes.json": _dump(changes),
|
|
155
|
+
"reports.json": _dump(report_items),
|
|
156
|
+
"skills.json": _dump(skills),
|
|
157
|
+
}
|
|
158
|
+
site_info = {
|
|
159
|
+
"title": cfg.title,
|
|
160
|
+
"knowledge": cfg.knowledge,
|
|
161
|
+
"reports": cfg.reports,
|
|
162
|
+
"human": cfg.human,
|
|
163
|
+
"okf_version": bundle.root_meta.get("okf_version"),
|
|
164
|
+
"static": export,
|
|
165
|
+
"map": cfg.raw.get("map", {}), # project defaults for the Map tab ([map] in rdstudio.toml)
|
|
166
|
+
"issues": issues,
|
|
167
|
+
"counts": {"concepts": len(concepts), "reports": len(report_items),
|
|
168
|
+
"skills": len(skills["skills"]), "agents": len(skills["agents"])},
|
|
169
|
+
}
|
|
170
|
+
payload["site.json"] = _dump(site_info)
|
|
171
|
+
|
|
172
|
+
bodies: dict[str, str] = {f"k/{cid}.md": c.body for cid, c in bundle.concepts.items()}
|
|
173
|
+
digest = hashlib.sha256()
|
|
174
|
+
for name in sorted(payload):
|
|
175
|
+
digest.update(payload[name].encode())
|
|
176
|
+
for name in sorted(bodies):
|
|
177
|
+
digest.update(name.encode() + bodies[name].encode())
|
|
178
|
+
version = digest.hexdigest()[:16]
|
|
179
|
+
|
|
180
|
+
for rel, text in bodies.items():
|
|
181
|
+
_write_if_changed(data / rel, text)
|
|
182
|
+
keep = {data / rel for rel in bodies}
|
|
183
|
+
# Non-markdown bundle files (images, data) are served beside the bodies.
|
|
184
|
+
if cfg.knowledge_dir.is_dir():
|
|
185
|
+
for path in cfg.knowledge_dir.rglob("*"):
|
|
186
|
+
rel = path.relative_to(cfg.knowledge_dir)
|
|
187
|
+
if path.is_file() and path.suffix != ".md" and not any(p.startswith(".") for p in rel.parts):
|
|
188
|
+
target = data / "k" / rel
|
|
189
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
190
|
+
if not target.exists() or target.stat().st_mtime < path.stat().st_mtime:
|
|
191
|
+
shutil.copy2(path, target)
|
|
192
|
+
if (data / "k").is_dir():
|
|
193
|
+
for stale in (data / "k").rglob("*.md"):
|
|
194
|
+
if stale not in keep:
|
|
195
|
+
stale.unlink()
|
|
196
|
+
|
|
197
|
+
# Reports are served beside the app so relative media keeps working.
|
|
198
|
+
if cfg.reports_dir.is_dir():
|
|
199
|
+
_sync_tree(cfg.reports_dir, site / "reports")
|
|
200
|
+
|
|
201
|
+
for name, text in payload.items():
|
|
202
|
+
_write_if_changed(data / name, text)
|
|
203
|
+
# Installable as an app (add to home screen), opening full screen.
|
|
204
|
+
_write_if_changed(site / "manifest.webmanifest", _dump({
|
|
205
|
+
"name": cfg.title, "short_name": cfg.title[:24], "start_url": "./", "scope": "./",
|
|
206
|
+
"display": "fullscreen", "display_override": ["fullscreen", "standalone"],
|
|
207
|
+
"background_color": "#141a20", "theme_color": "#141a20",
|
|
208
|
+
"icons": [{"src": f"icon-{n}.png", "sizes": f"{n}x{n}", "type": "image/png", "purpose": "any maskable"}
|
|
209
|
+
for n in (192, 512)],
|
|
210
|
+
}))
|
|
211
|
+
vfile = data / "version.json"
|
|
212
|
+
try:
|
|
213
|
+
old = json.loads(vfile.read_text(encoding="utf-8")).get("version")
|
|
214
|
+
except (OSError, ValueError):
|
|
215
|
+
old = None
|
|
216
|
+
if old != version:
|
|
217
|
+
vfile.write_text(_dump({"version": version, "built": iso(now())}), encoding="utf-8")
|
|
218
|
+
return site
|
rdstudio/classify.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Small structured decisions behind one interface.
|
|
2
|
+
|
|
3
|
+
Two places in rdstudio need a quick classification rather than generated text:
|
|
4
|
+
|
|
5
|
+
- whether an edit to a concept is significant (it bumps ``generated`` and flags
|
|
6
|
+
human-reviewed concepts for re-review), when the agent leaves it to rdstudio;
|
|
7
|
+
- which step of a procedure an agent is on, when its description does not match
|
|
8
|
+
a step exactly.
|
|
9
|
+
|
|
10
|
+
The default backend is deterministic and offline. A ``command`` backend hands
|
|
11
|
+
the question to any external program, which is how a structured-output model
|
|
12
|
+
(for example TypeSafe's Jev) or a local model can be plugged in without
|
|
13
|
+
rdstudio depending on a vendor SDK::
|
|
14
|
+
|
|
15
|
+
[classifier]
|
|
16
|
+
backend = "command"
|
|
17
|
+
command = ["my-classifier"] # reads JSON on stdin, writes JSON on stdout
|
|
18
|
+
min_confidence = 0.7 # below this, fall back to the rules
|
|
19
|
+
|
|
20
|
+
The program receives ``{"question", "options", "state"}`` and must answer
|
|
21
|
+
``{"choice": <one of options>, "confidence": <0..1>}``.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import difflib
|
|
27
|
+
import json
|
|
28
|
+
import re
|
|
29
|
+
import subprocess
|
|
30
|
+
from dataclasses import dataclass
|
|
31
|
+
from typing import Any, Protocol
|
|
32
|
+
|
|
33
|
+
from .search import tokenize
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class Decision:
|
|
38
|
+
choice: str | None
|
|
39
|
+
confidence: float
|
|
40
|
+
backend: str
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Classifier(Protocol):
|
|
44
|
+
def choose(self, question: str, options: list[str], state: dict[str, Any]) -> Decision: ...
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# --------------------------------------------------------------------------- #
|
|
48
|
+
# Rules
|
|
49
|
+
# --------------------------------------------------------------------------- #
|
|
50
|
+
|
|
51
|
+
_WS = re.compile(r"\s+")
|
|
52
|
+
_PUNCT = re.compile(r"[^\w\s]")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _norm(text: str) -> str:
|
|
56
|
+
return _WS.sub(" ", _PUNCT.sub("", text.lower())).strip()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def edit_significance(before: str, after: str) -> Decision:
|
|
60
|
+
"""``minor`` for formatting, punctuation, case or very small wording changes;
|
|
61
|
+
``significant`` otherwise."""
|
|
62
|
+
if _norm(before) == _norm(after):
|
|
63
|
+
return Decision("minor", 0.95, "rules")
|
|
64
|
+
a, b = tokenize(before), tokenize(after)
|
|
65
|
+
matcher = difflib.SequenceMatcher(a=a, b=b, autojunk=False)
|
|
66
|
+
changed = sum(max(i2 - i1, j2 - j1) for tag, i1, i2, j1, j2 in matcher.get_opcodes() if tag != "equal")
|
|
67
|
+
if changed <= 3 and changed / max(len(a), 1) < 0.05:
|
|
68
|
+
return Decision("minor", 0.7, "rules")
|
|
69
|
+
return Decision("significant", 0.8 if changed > 10 else 0.6, "rules")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def match_step(description: str, steps: dict[str, str]) -> Decision:
|
|
73
|
+
"""The step whose label shares the most words with ``description``."""
|
|
74
|
+
def same(a: str, b: str) -> bool: # tolerate inflection: "renamed" ~ "rename"
|
|
75
|
+
return a == b or (min(len(a), len(b)) >= 4 and (a.startswith(b) or b.startswith(a)))
|
|
76
|
+
|
|
77
|
+
words = set(tokenize(description))
|
|
78
|
+
best, best_score = None, 0.0
|
|
79
|
+
for sid, label in steps.items():
|
|
80
|
+
label_words = set(tokenize(label)) | set(tokenize(sid.replace("-", " ").replace("_", " ")))
|
|
81
|
+
if not label_words or not words:
|
|
82
|
+
continue
|
|
83
|
+
shared = sum(1 for w in words if any(same(w, l) for l in label_words))
|
|
84
|
+
score = shared / (len(words) + len(label_words) - shared)
|
|
85
|
+
if score > best_score:
|
|
86
|
+
best, best_score = sid, score
|
|
87
|
+
return Decision(best if best_score >= 0.34 else None, round(best_score, 2), "rules")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class RulesClassifier:
|
|
91
|
+
def choose(self, question: str, options: list[str], state: dict[str, Any]) -> Decision:
|
|
92
|
+
if question == "edit_significance":
|
|
93
|
+
return edit_significance(state.get("before", ""), state.get("after", ""))
|
|
94
|
+
if question == "procedure_step":
|
|
95
|
+
return match_step(state.get("description", ""), state.get("steps", {}))
|
|
96
|
+
return Decision(None, 0.0, "rules")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# --------------------------------------------------------------------------- #
|
|
100
|
+
# External command
|
|
101
|
+
# --------------------------------------------------------------------------- #
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class CommandClassifier:
|
|
105
|
+
def __init__(self, command: list[str], min_confidence: float = 0.7, timeout: float = 10.0):
|
|
106
|
+
self.command = command
|
|
107
|
+
self.min_confidence = min_confidence
|
|
108
|
+
self.timeout = timeout
|
|
109
|
+
self.fallback = RulesClassifier()
|
|
110
|
+
|
|
111
|
+
def choose(self, question: str, options: list[str], state: dict[str, Any]) -> Decision:
|
|
112
|
+
payload = json.dumps({"question": question, "options": options, "state": state})
|
|
113
|
+
try:
|
|
114
|
+
proc = subprocess.run(self.command, input=payload, capture_output=True, text=True,
|
|
115
|
+
timeout=self.timeout, check=True)
|
|
116
|
+
answer = json.loads(proc.stdout)
|
|
117
|
+
choice, confidence = answer.get("choice"), float(answer.get("confidence", 0))
|
|
118
|
+
except (OSError, subprocess.SubprocessError, ValueError, AttributeError):
|
|
119
|
+
return self.fallback.choose(question, options, state)
|
|
120
|
+
if choice not in options or confidence < self.min_confidence:
|
|
121
|
+
return self.fallback.choose(question, options, state)
|
|
122
|
+
return Decision(choice, confidence, "command")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def from_config(raw: dict[str, Any]) -> Classifier:
|
|
126
|
+
conf = raw.get("classifier", {})
|
|
127
|
+
if conf.get("backend") == "command" and conf.get("command"):
|
|
128
|
+
command = conf["command"]
|
|
129
|
+
return CommandClassifier(command if isinstance(command, list) else str(command).split(),
|
|
130
|
+
float(conf.get("min_confidence", 0.7)))
|
|
131
|
+
return RulesClassifier()
|