ronin-skillforce 0.0.0-stage → 0.1.0

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 (429) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +21 -0
  3. package/README.md +225 -2
  4. package/bin/cli.js +811 -0
  5. package/bin/commands/check.js +298 -0
  6. package/bin/commands/create.js +169 -0
  7. package/bin/commands/doctor.js +392 -0
  8. package/bin/commands/import.js +557 -0
  9. package/bin/commands/next.js +125 -0
  10. package/bin/commands/review.js +207 -0
  11. package/bin/commands/status.js +114 -0
  12. package/bin/commands/studio.js +497 -0
  13. package/bin/commands/trail.js +211 -0
  14. package/bin/commands/web.js +350 -0
  15. package/bin/course-reader.js +611 -0
  16. package/bin/node-version.js +60 -0
  17. package/bin/platforms/_shared.js +211 -0
  18. package/bin/platforms/claude.js +104 -0
  19. package/bin/platforms/cursor.js +49 -0
  20. package/bin/platforms/gemini.js +65 -0
  21. package/bin/platforms/opencode.js +95 -0
  22. package/bin/registry-helpers.js +504 -0
  23. package/bin/static-server.js +153 -0
  24. package/bin/studio-store.js +94 -0
  25. package/bin/sync-shared.sh +5 -0
  26. package/package.json +91 -4
  27. package/plugin.json +139 -0
  28. package/shared/SKILL.md +568 -0
  29. package/shared/agents/coach.md +225 -0
  30. package/shared/agents/navigator.md +215 -0
  31. package/shared/agents/professor.md +372 -0
  32. package/shared/agents/researcher.md +181 -0
  33. package/shared/agents/spotter.md +129 -0
  34. package/shared/commands/professor/archive.md +103 -0
  35. package/shared/commands/professor/capstone-review.md +79 -0
  36. package/shared/commands/professor/capstone.md +34 -0
  37. package/shared/commands/professor/discuss.md +18 -0
  38. package/shared/commands/professor/done.md +44 -0
  39. package/shared/commands/professor/export.md +69 -0
  40. package/shared/commands/professor/hint.md +108 -0
  41. package/shared/commands/professor/navigator.md +67 -0
  42. package/shared/commands/professor/new-topic.md +73 -0
  43. package/shared/commands/professor/next.md +174 -0
  44. package/shared/commands/professor/note.md +63 -0
  45. package/shared/commands/professor/onboard.md +95 -0
  46. package/shared/commands/professor/progress.md +46 -0
  47. package/shared/commands/professor/publish.md +93 -0
  48. package/shared/commands/professor/quiz.md +27 -0
  49. package/shared/commands/professor/recall.md +74 -0
  50. package/shared/commands/professor/review.md +43 -0
  51. package/shared/commands/professor/schedule.md +75 -0
  52. package/shared/commands/professor/skill-export.md +87 -0
  53. package/shared/commands/professor/spotter.md +76 -0
  54. package/shared/commands/professor/stuck.md +27 -0
  55. package/shared/commands/professor/switch.md +50 -0
  56. package/shared/commands/professor/syllabus.md +9 -0
  57. package/shared/commands/professor/template-export.md +91 -0
  58. package/shared/commands/professor/template-import.md +111 -0
  59. package/shared/commands/professor/worktrees.md +58 -0
  60. package/shared/hooks/pre-compact.js +232 -0
  61. package/studio/README.md +313 -0
  62. package/studio/dist/assets/BetaReportPanel-8NrDLiPm.js +1 -0
  63. package/studio/dist/assets/Capstone-Cv9kH6LY.js +6 -0
  64. package/studio/dist/assets/CodeWorkspace-BwCEINWo.js +2 -0
  65. package/studio/dist/assets/Course-BJ_hafUP.js +1 -0
  66. package/studio/dist/assets/Editor-CV0mNhlz.js +53 -0
  67. package/studio/dist/assets/Home-C1ry89ak.js +1 -0
  68. package/studio/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  69. package/studio/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  70. package/studio/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  71. package/studio/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  72. package/studio/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  73. package/studio/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  74. package/studio/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  75. package/studio/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  76. package/studio/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  77. package/studio/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  78. package/studio/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  79. package/studio/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  80. package/studio/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  81. package/studio/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  82. package/studio/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  83. package/studio/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  84. package/studio/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  85. package/studio/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  86. package/studio/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  87. package/studio/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  88. package/studio/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  89. package/studio/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  90. package/studio/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  91. package/studio/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  92. package/studio/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  93. package/studio/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  94. package/studio/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  95. package/studio/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  96. package/studio/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  97. package/studio/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  98. package/studio/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  99. package/studio/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  100. package/studio/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  101. package/studio/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  102. package/studio/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  103. package/studio/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  104. package/studio/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  105. package/studio/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  106. package/studio/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  107. package/studio/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  108. package/studio/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  109. package/studio/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  110. package/studio/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  111. package/studio/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  112. package/studio/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  113. package/studio/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  114. package/studio/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  115. package/studio/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  116. package/studio/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  117. package/studio/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  118. package/studio/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  119. package/studio/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  120. package/studio/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  121. package/studio/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  122. package/studio/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  123. package/studio/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  124. package/studio/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  125. package/studio/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  126. package/studio/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  127. package/studio/dist/assets/Lesson-2e9TD9yL.js +2 -0
  128. package/studio/dist/assets/NewCourse-BZeOg2xn.js +952 -0
  129. package/studio/dist/assets/Progress-CRc_xEWC.js +1 -0
  130. package/studio/dist/assets/Review-6QEbHEfk.js +1 -0
  131. package/studio/dist/assets/Settings-NFnqVQui.js +1 -0
  132. package/studio/dist/assets/TutorPanel-DXQ6Z3BY.js +123 -0
  133. package/studio/dist/assets/abnfDiagram-WEYOIA3C-qeSjKlGM.js +1 -0
  134. package/studio/dist/assets/anthropic-D7NiQCUH.js +15 -0
  135. package/studio/dist/assets/arc-Y1ARJVZ4.js +1 -0
  136. package/studio/dist/assets/architecture-WOLXFQ4H-COG-m35l.js +1 -0
  137. package/studio/dist/assets/architectureDiagram-ZHVYKT53-VO7uKoeD.js +36 -0
  138. package/studio/dist/assets/array-BifhSqXX.js +1 -0
  139. package/studio/dist/assets/blockDiagram-RJAE7QA3-BGJF_qiL.js +140 -0
  140. package/studio/dist/assets/bridge-BTh0AeNX.js +1 -0
  141. package/studio/dist/assets/c4Diagram-XDPPPE54-C-S10RgU.js +38 -0
  142. package/studio/dist/assets/capstone-Amn6Hztq.js +135 -0
  143. package/studio/dist/assets/card-store-BrbrtX9w.js +1 -0
  144. package/studio/dist/assets/channel-SJiUQu_N.js +1 -0
  145. package/studio/dist/assets/chunk-2E7WHVMD-fv-iqTyC.js +88 -0
  146. package/studio/dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  147. package/studio/dist/assets/chunk-4EMNKR2T-CY9IZrgv.js +1 -0
  148. package/studio/dist/assets/chunk-55XCU76K-DyTZz7v_.js +1 -0
  149. package/studio/dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  150. package/studio/dist/assets/chunk-7S6XSTFO-BNMzTLRW.js +2 -0
  151. package/studio/dist/assets/chunk-AQDTSKZB-CEYANohH.js +72 -0
  152. package/studio/dist/assets/chunk-DSNLSHJZ-CDk5EXjH.js +129 -0
  153. package/studio/dist/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
  154. package/studio/dist/assets/chunk-HAGFOTED-hNAi1zh_.js +62 -0
  155. package/studio/dist/assets/chunk-HNE4Q36P-COJnSYoZ.js +1 -0
  156. package/studio/dist/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
  157. package/studio/dist/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  158. package/studio/dist/assets/chunk-NGNAAXSQ-C2Wy9Xmz.js +136 -0
  159. package/studio/dist/assets/chunk-NQOKRVIF-BU6KVxRr.js +213 -0
  160. package/studio/dist/assets/chunk-UHW7PFBP-DYvxL9JG.js +1 -0
  161. package/studio/dist/assets/chunk-VQ5W62QI-CuRfk65j.js +1 -0
  162. package/studio/dist/assets/chunk-WQQL4CDK-KaG7x-Mb.js +10 -0
  163. package/studio/dist/assets/chunk-XXDRQBXY-CnbRkih_.js +1 -0
  164. package/studio/dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  165. package/studio/dist/assets/chunk-YZO67HFG-DbuqIZmk.js +1 -0
  166. package/studio/dist/assets/chunk-ZAUX2LTX-bk4ivUmc.js +1 -0
  167. package/studio/dist/assets/chunk-ZCHMQSOR-B_LElfwh.js +2 -0
  168. package/studio/dist/assets/classDiagram-v2-4MYWR3UA-DtCJ6H_X.js +217 -0
  169. package/studio/dist/assets/cose-bilkent-JH36ORCC-DMBAZFnj.js +1 -0
  170. package/studio/dist/assets/course-export-DpaOTT3a.js +1 -0
  171. package/studio/dist/assets/cynefin-EF2NZ3EQ-D9btHJvU.js +1 -0
  172. package/studio/dist/assets/cynefinDiagram-WF24UDRP-CsFJuv27.js +62 -0
  173. package/studio/dist/assets/cytoscape.esm-CHBj4VkZ.js +321 -0
  174. package/studio/dist/assets/dagre-NK4GIZVX-Cg1Duec7.js +4 -0
  175. package/studio/dist/assets/defaultLocale-BFoDCU3G.js +1 -0
  176. package/studio/dist/assets/diagram-CBK3MWTC-_KU2m5gd.js +24 -0
  177. package/studio/dist/assets/diagram-I5XIIV2D-5RaBrwUA.js +24 -0
  178. package/studio/dist/assets/diagram-ODIL2UCW-C0VKxoD3.js +30 -0
  179. package/studio/dist/assets/diagram-PFBEJUXE-BaHcZEP2.js +3 -0
  180. package/studio/dist/assets/diagram-RJA3I4VV-cwSAZ90p.js +41 -0
  181. package/studio/dist/assets/diagram-TYUNFSNR-BJImxCEN.js +200 -0
  182. package/studio/dist/assets/dist-CumnGVmJ.js +1 -0
  183. package/studio/dist/assets/ebnfDiagram-V4P3TTPT-OQQLRXcm.js +1 -0
  184. package/studio/dist/assets/elk-QONTBCTQ-BJGhQHxQ.js +27 -0
  185. package/studio/dist/assets/erDiagram-KYQIGQAA-D91EKSep.js +99 -0
  186. package/studio/dist/assets/error-BTdmRKoB.js +1 -0
  187. package/studio/dist/assets/errors-BZa_CNHL.js +1 -0
  188. package/studio/dist/assets/eventmodeling-K75KTNOO-DgdTRgnw.js +1 -0
  189. package/studio/dist/assets/fetch-local-DG3O3hmQ.js +4 -0
  190. package/studio/dist/assets/flowDiagram-IUGLLWBU-CVnJYMum.js +1 -0
  191. package/studio/dist/assets/ganttDiagram-R7GPNTJY-CSYLPWEI.js +292 -0
  192. package/studio/dist/assets/gitGraph-VSP46ZUC-D6GWaV9y.js +1 -0
  193. package/studio/dist/assets/gitGraphDiagram-PDYL7XHY-CaV31bvT.js +106 -0
  194. package/studio/dist/assets/graphlib-DS17s2tU.js +1 -0
  195. package/studio/dist/assets/index-CVenTTOY.css +2 -0
  196. package/studio/dist/assets/index-DioXTZVc.js +313 -0
  197. package/studio/dist/assets/info-OHQRW6UA-BN-x8GoN.js +1 -0
  198. package/studio/dist/assets/infoDiagram-MVDRKAX5-CIose_cy.js +2 -0
  199. package/studio/dist/assets/init-C-OQMol4.js +1 -0
  200. package/studio/dist/assets/ishikawaDiagram-HPEHV2GE-DCihSNBw.js +70 -0
  201. package/studio/dist/assets/journeyDiagram-BOSHW7O7-Bz3o3RIX.js +139 -0
  202. package/studio/dist/assets/js.worker-AD7DjksO.js +4 -0
  203. package/studio/dist/assets/kanban-definition-FNSA4HC5-BUyqvlFE.js +89 -0
  204. package/studio/dist/assets/katex-B_JcIwqj.js +257 -0
  205. package/studio/dist/assets/katex-BgsA2lO1.css +1 -0
  206. package/studio/dist/assets/line-B08T8KlK.js +1 -0
  207. package/studio/dist/assets/linear-C9iwaMOi.js +1 -0
  208. package/studio/dist/assets/mermaid-parser.core-BYwreSUy.js +7 -0
  209. package/studio/dist/assets/mermaid.core-DTO1oDmq.js +44 -0
  210. package/studio/dist/assets/mindmap-definition-6OY57UG3-BGLwKheY.js +98 -0
  211. package/studio/dist/assets/node.browser-BISniYn8.js +1 -0
  212. package/studio/dist/assets/node.browser-HQ5SKBOf.js +1 -0
  213. package/studio/dist/assets/ollama-De5w84tG.js +1 -0
  214. package/studio/dist/assets/openai-compatible-Joy1oK3L.js +1 -0
  215. package/studio/dist/assets/ordinal-BDEzSJ7C.js +1 -0
  216. package/studio/dist/assets/packet-JDAUHWVQ-CDIMjHS_.js +1 -0
  217. package/studio/dist/assets/path-fybaL0A-.js +1 -0
  218. package/studio/dist/assets/pegDiagram-ECIUJNBX-DWXm4mwS.js +1 -0
  219. package/studio/dist/assets/pie-XZMESJXO-CY3QQiKZ.js +1 -0
  220. package/studio/dist/assets/pieDiagram-GNMPY74T-M1aCnI7W.js +39 -0
  221. package/studio/dist/assets/python.worker-CzTrFdd9.js +24 -0
  222. package/studio/dist/assets/quadrantDiagram-D57K4MC3-BFFtO7U5.js +7 -0
  223. package/studio/dist/assets/radar-ABXABTNO-DT0W4_Xn.js +1 -0
  224. package/studio/dist/assets/railroad-I3PHUGI6-_VCpgZR5.js +1 -0
  225. package/studio/dist/assets/railroad-abnf-LNEFI6M7-DkSpQ5YY.js +1 -0
  226. package/studio/dist/assets/railroad-ebnf-DWYD2UWJ-BqWILdGb.js +1 -0
  227. package/studio/dist/assets/railroad-peg-JR7BVNR7-BB0bQL-Z.js +1 -0
  228. package/studio/dist/assets/railroadDiagram-Y72NRBL7-CjLKjIdJ.js +1 -0
  229. package/studio/dist/assets/react-B8SKf8RM.js +1 -0
  230. package/studio/dist/assets/rehype-katex-C-sKddsH.js +259 -0
  231. package/studio/dist/assets/remark-math-C-ox67hE.js +5 -0
  232. package/studio/dist/assets/requirementDiagram-EQYSW5RA-CnwzZqR8.js +84 -0
  233. package/studio/dist/assets/rolldown-runtime-COiEfhsA.js +1 -0
  234. package/studio/dist/assets/rough.esm-Dy-Kn_BL.js +1 -0
  235. package/studio/dist/assets/sankeyDiagram-SY24JHO5-CPDugTUl.js +40 -0
  236. package/studio/dist/assets/schemas-BOc2YsOP.js +55 -0
  237. package/studio/dist/assets/sequenceDiagram-BLEHNHQU-1dLfeU9Y.js +169 -0
  238. package/studio/dist/assets/settings-DhwuyNPU.js +1 -0
  239. package/studio/dist/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  240. package/studio/dist/assets/spec-nAnsSMkw.js +1 -0
  241. package/studio/dist/assets/sql-wasm-C1D_RJuy.wasm +0 -0
  242. package/studio/dist/assets/sql.worker-D7pBHOrq.js +4 -0
  243. package/studio/dist/assets/src-BWF7jMDv.js +1 -0
  244. package/studio/dist/assets/stateDiagram-v2-A6IESKWZ-DR7CmEjj.js +291 -0
  245. package/studio/dist/assets/swimlanes-B3ENRKFQ-BAWB9omQ.js +1 -0
  246. package/studio/dist/assets/swimlanesDiagram-AYQRV4AK-qeuHru16.js +8 -0
  247. package/studio/dist/assets/timeline-definition-JXYKGLGB-DNkj8z77.js +120 -0
  248. package/studio/dist/assets/treeView-D4JQ5SDB-BA-ouhMb.js +1 -0
  249. package/studio/dist/assets/treemap-SAJKECNS-BrrGxsAb.js +1 -0
  250. package/studio/dist/assets/useStream-rdsR6A7P.js +1 -0
  251. package/studio/dist/assets/usecaseDiagram-WPOLBNNX-BB3D0Dt_.js +328 -0
  252. package/studio/dist/assets/vennDiagram-Q3Y3IDCW-BrtqtKTE.js +34 -0
  253. package/studio/dist/assets/wardley-7MLQ67FV-BKkemDql.js +1 -0
  254. package/studio/dist/assets/wardleyDiagram-IVAIDKWA-DFnI3P5z.js +78 -0
  255. package/studio/dist/assets/xychartDiagram-4URNHDU4-Da0DZG_q.js +7 -0
  256. package/studio/dist/index.html +20 -0
  257. package/studio/dist/preview.html +22 -0
  258. package/studio/migrations/0001_init.sql +188 -0
  259. package/studio/migrations/0002_external_ids.sql +20 -0
  260. package/studio/migrations/0003_course_language.sql +12 -0
  261. package/studio/migrations/0004_capstone.sql +51 -0
  262. package/studio/migrations/0005_beta_loop.sql +73 -0
  263. package/studio/migrations/0006_interactive_exercises.sql +49 -0
  264. package/studio/src/lib/activity.js +92 -0
  265. package/studio/src/lib/ai/anthropic.js +109 -0
  266. package/studio/src/lib/ai/bridge.js +126 -0
  267. package/studio/src/lib/ai/catalog.js +160 -0
  268. package/studio/src/lib/ai/config.js +70 -0
  269. package/studio/src/lib/ai/errors.js +95 -0
  270. package/studio/src/lib/ai/fetch-local.js +104 -0
  271. package/studio/src/lib/ai/index.js +194 -0
  272. package/studio/src/lib/ai/ollama.js +136 -0
  273. package/studio/src/lib/ai/openai-compatible.js +101 -0
  274. package/studio/src/lib/ai/provider-utils.js +107 -0
  275. package/studio/src/lib/ai/sse.js +99 -0
  276. package/studio/src/lib/ai/status.js +92 -0
  277. package/studio/src/lib/ai/useStream.js +109 -0
  278. package/studio/src/lib/beta/metrics.js +147 -0
  279. package/studio/src/lib/beta/report.js +134 -0
  280. package/studio/src/lib/capstone-evidence.js +758 -0
  281. package/studio/src/lib/capstone.js +663 -0
  282. package/studio/src/lib/chat-store.js +128 -0
  283. package/studio/src/lib/course-export.js +38 -0
  284. package/studio/src/lib/course.js +187 -0
  285. package/studio/src/lib/exercises/journal-entry.js +259 -0
  286. package/studio/src/lib/exercises/placement.js +31 -0
  287. package/studio/src/lib/exercises/registry.js +19 -0
  288. package/studio/src/lib/exercises/spec.js +129 -0
  289. package/studio/src/lib/i18n/detect.js +162 -0
  290. package/studio/src/lib/i18n/en/app.json +23 -0
  291. package/studio/src/lib/i18n/en/betaReport.json +55 -0
  292. package/studio/src/lib/i18n/en/capstone.json +57 -0
  293. package/studio/src/lib/i18n/en/common.json +25 -0
  294. package/studio/src/lib/i18n/en/course.json +27 -0
  295. package/studio/src/lib/i18n/en/data.json +35 -0
  296. package/studio/src/lib/i18n/en/durability.json +5 -0
  297. package/studio/src/lib/i18n/en/errors.json +20 -0
  298. package/studio/src/lib/i18n/en/exercises.json +55 -0
  299. package/studio/src/lib/i18n/en/externalEvidence.json +76 -0
  300. package/studio/src/lib/i18n/en/home.json +18 -0
  301. package/studio/src/lib/i18n/en/lesson.json +33 -0
  302. package/studio/src/lib/i18n/en/nav.json +8 -0
  303. package/studio/src/lib/i18n/en/newCourse.json +51 -0
  304. package/studio/src/lib/i18n/en/onboarding.json +35 -0
  305. package/studio/src/lib/i18n/en/playwrightTemplate.json +20 -0
  306. package/studio/src/lib/i18n/en/practice.json +79 -0
  307. package/studio/src/lib/i18n/en/provider.json +27 -0
  308. package/studio/src/lib/i18n/en/providerMeta.json +26 -0
  309. package/studio/src/lib/i18n/en/reflection.json +17 -0
  310. package/studio/src/lib/i18n/en/revision.json +52 -0
  311. package/studio/src/lib/i18n/en/runnerNotice.json +14 -0
  312. package/studio/src/lib/i18n/en/settings.json +12 -0
  313. package/studio/src/lib/i18n/en/storage.json +49 -0
  314. package/studio/src/lib/i18n/en/tutor.json +39 -0
  315. package/studio/src/lib/i18n/en/workspace.json +50 -0
  316. package/studio/src/lib/i18n/index.js +144 -0
  317. package/studio/src/lib/i18n/languages.js +93 -0
  318. package/studio/src/lib/i18n/vi/app.json +23 -0
  319. package/studio/src/lib/i18n/vi/betaReport.json +55 -0
  320. package/studio/src/lib/i18n/vi/capstone.json +57 -0
  321. package/studio/src/lib/i18n/vi/common.json +25 -0
  322. package/studio/src/lib/i18n/vi/course.json +27 -0
  323. package/studio/src/lib/i18n/vi/data.json +35 -0
  324. package/studio/src/lib/i18n/vi/durability.json +5 -0
  325. package/studio/src/lib/i18n/vi/errors.json +20 -0
  326. package/studio/src/lib/i18n/vi/exercises.json +55 -0
  327. package/studio/src/lib/i18n/vi/externalEvidence.json +76 -0
  328. package/studio/src/lib/i18n/vi/home.json +18 -0
  329. package/studio/src/lib/i18n/vi/lesson.json +33 -0
  330. package/studio/src/lib/i18n/vi/nav.json +8 -0
  331. package/studio/src/lib/i18n/vi/newCourse.json +51 -0
  332. package/studio/src/lib/i18n/vi/onboarding.json +35 -0
  333. package/studio/src/lib/i18n/vi/playwrightTemplate.json +20 -0
  334. package/studio/src/lib/i18n/vi/practice.json +79 -0
  335. package/studio/src/lib/i18n/vi/provider.json +27 -0
  336. package/studio/src/lib/i18n/vi/providerMeta.json +26 -0
  337. package/studio/src/lib/i18n/vi/reflection.json +17 -0
  338. package/studio/src/lib/i18n/vi/revision.json +52 -0
  339. package/studio/src/lib/i18n/vi/runnerNotice.json +14 -0
  340. package/studio/src/lib/i18n/vi/settings.json +12 -0
  341. package/studio/src/lib/i18n/vi/storage.json +49 -0
  342. package/studio/src/lib/i18n/vi/tutor.json +39 -0
  343. package/studio/src/lib/i18n/vi/workspace.json +50 -0
  344. package/studio/src/lib/ids.js +10 -0
  345. package/studio/src/lib/json.js +26 -0
  346. package/studio/src/lib/markdown.js +74 -0
  347. package/studio/src/lib/prompts/base.js +139 -0
  348. package/studio/src/lib/prompts/capstone.js +777 -0
  349. package/studio/src/lib/prompts/course.js +150 -0
  350. package/studio/src/lib/prompts/lesson.js +275 -0
  351. package/studio/src/lib/prompts/practice.js +511 -0
  352. package/studio/src/lib/prompts/tutor.js +509 -0
  353. package/studio/src/lib/repo/blocked-adapter.js +69 -0
  354. package/studio/src/lib/repo/events.js +239 -0
  355. package/studio/src/lib/repo/idb-adapter.js +226 -0
  356. package/studio/src/lib/repo/index.js +1315 -0
  357. package/studio/src/lib/repo/memory-adapter.js +115 -0
  358. package/studio/src/lib/repo/remote-adapter.js +158 -0
  359. package/studio/src/lib/repo/schema.js +143 -0
  360. package/studio/src/lib/repo/select-adapter.js +345 -0
  361. package/studio/src/lib/router.js +71 -0
  362. package/studio/src/lib/runner/browser.js +244 -0
  363. package/studio/src/lib/runner/checks.js +248 -0
  364. package/studio/src/lib/runner/current-code.js +98 -0
  365. package/studio/src/lib/runner/esm-cjs.js +109 -0
  366. package/studio/src/lib/runner/hash.js +34 -0
  367. package/studio/src/lib/runner/history.js +48 -0
  368. package/studio/src/lib/runner/index.js +168 -0
  369. package/studio/src/lib/runner/languages.js +258 -0
  370. package/studio/src/lib/runner/local.js +139 -0
  371. package/studio/src/lib/runner/path.js +104 -0
  372. package/studio/src/lib/runner/skeleton.js +315 -0
  373. package/studio/src/lib/runner/strip-types.js +134 -0
  374. package/studio/src/lib/settings.js +399 -0
  375. package/studio/src/lib/srs.js +39 -0
  376. package/templates/agent/settings.json +6 -0
  377. package/templates/claude/plugin.json +139 -0
  378. package/templates/claude/settings.json +15 -0
  379. package/templates/cursor/professor.mdc +4 -0
  380. package/templates/cursor/settings.json +18 -0
  381. package/templates/gemini/GEMINI.md +28 -0
  382. package/templates/gemini/settings.json +5 -0
  383. package/templates/opencode/opencode.json +3 -0
  384. package/templates/researcher/README.md +61 -0
  385. package/templates/researcher/settings.json +18 -0
  386. package/web/client/components.json +15 -0
  387. package/web/client/index.html +12 -0
  388. package/web/client/package-lock.json +8732 -0
  389. package/web/client/package.json +39 -0
  390. package/web/client/postcss.config.js +6 -0
  391. package/web/client/src/App.jsx +140 -0
  392. package/web/client/src/api/client.js +357 -0
  393. package/web/client/src/components/ChatPanel.jsx +417 -0
  394. package/web/client/src/components/CommandPills.jsx +50 -0
  395. package/web/client/src/components/DeprecationBanner.jsx +21 -0
  396. package/web/client/src/components/ExerciseEditor.jsx +164 -0
  397. package/web/client/src/components/Header.jsx +119 -0
  398. package/web/client/src/components/LecturePanel.jsx +342 -0
  399. package/web/client/src/components/MobileTabSwitcher.jsx +51 -0
  400. package/web/client/src/components/SplitPane.jsx +42 -0
  401. package/web/client/src/components/ui/alert.jsx +38 -0
  402. package/web/client/src/components/ui/badge.jsx +24 -0
  403. package/web/client/src/components/ui/button.jsx +23 -0
  404. package/web/client/src/components/ui/select.jsx +74 -0
  405. package/web/client/src/hooks/useSSE.js +152 -0
  406. package/web/client/src/hooks/useWebSocket.js +98 -0
  407. package/web/client/src/index.css +23 -0
  408. package/web/client/src/lib/utils.js +6 -0
  409. package/web/client/src/main.jsx +12 -0
  410. package/web/client/src/utils/markdown.js +50 -0
  411. package/web/client/tailwind.config.js +8 -0
  412. package/web/client/vite.config.js +21 -0
  413. package/web/dist/assets/index-C1W0eXIQ.js +294 -0
  414. package/web/dist/assets/index-C3OUx70T.css +1 -0
  415. package/web/dist/index.html +13 -0
  416. package/web/lecture-archive.js +172 -0
  417. package/web/legacy-server.js +1277 -0
  418. package/web/package-lock.json +855 -0
  419. package/web/package.json +10 -0
  420. package/web/security.js +1016 -0
  421. package/web/server.js +390 -0
  422. package/web/studio-api/agents.js +580 -0
  423. package/web/studio-api/bridge.js +199 -0
  424. package/web/studio-api/db.js +66 -0
  425. package/web/studio-api/guards.js +201 -0
  426. package/web/studio-api/health.js +538 -0
  427. package/web/studio-api.js +214 -0
  428. package/web/studio-db.js +380 -0
  429. package/web/studio-runner.js +568 -0
@@ -0,0 +1,115 @@
1
+ /**
2
+ * In-memory StorageAdapter. Used directly in tests and as the fallback when IndexedDB is
3
+ * unavailable (private mode, some embedded webviews, vitest without fake-indexeddb).
4
+ *
5
+ * Contract (see remote-adapter.js): dumb table-level storage. No stamping, no business logic.
6
+ */
7
+ import { TABLE_NAMES, SCHEMA_VERSION, tableSpec, assertIndexedField } from './schema.js';
8
+
9
+ export const APP_ID = 'professor-studio';
10
+
11
+ function applyList(rows, { where, includeDeleted = false, orderBy, limit } = {}) {
12
+ let out = rows;
13
+ if (where) {
14
+ for (const key of Object.keys(where)) {
15
+ out = out.filter((r) => r[key] === where[key]);
16
+ }
17
+ }
18
+ if (!includeDeleted) out = out.filter((r) => !r.deleted_at);
19
+ if (orderBy) {
20
+ const field = typeof orderBy === 'string' ? orderBy : orderBy.field;
21
+ const dir = typeof orderBy === 'string' ? 1 : orderBy.dir === 'desc' ? -1 : 1;
22
+ out = [...out].sort((a, b) => {
23
+ const av = a[field];
24
+ const bv = b[field];
25
+ if (av === bv) return 0;
26
+ return (av > bv ? 1 : -1) * dir;
27
+ });
28
+ }
29
+ if (typeof limit === 'number') out = out.slice(0, limit);
30
+ return out.map((r) => ({ ...r }));
31
+ }
32
+
33
+ /**
34
+ * @returns {import('./remote-adapter.js').StorageAdapter & { label: string }}
35
+ */
36
+ export function createMemoryAdapter() {
37
+ /** @type {Record<string, Map<string, object>>} */
38
+ const stores = Object.fromEntries(TABLE_NAMES.map((t) => [t, new Map()]));
39
+
40
+ function store(table) {
41
+ const s = stores[table];
42
+ if (!s) throw new Error(`Unknown table: ${table}`);
43
+ return s;
44
+ }
45
+
46
+ return {
47
+ label: 'memory',
48
+ async get(table, id) {
49
+ const row = store(table).get(id);
50
+ return row ? { ...row } : null;
51
+ },
52
+ async put(table, row) {
53
+ const spec = tableSpec(table);
54
+ const id = row[spec.id];
55
+ if (id === undefined || id === null) throw new Error(`Row for "${table}" is missing "${spec.id}"`);
56
+ const next = { ...row, [spec.id]: id };
57
+ store(table).set(id, next);
58
+ return { ...next };
59
+ },
60
+ async putMany(table, rows) {
61
+ const out = [];
62
+ for (const row of rows || []) out.push(await this.put(table, row));
63
+ return out;
64
+ },
65
+ async list(table, opts = {}) {
66
+ if (opts.where) {
67
+ for (const field of Object.keys(opts.where)) assertIndexedField(table, field);
68
+ }
69
+ return applyList([...store(table).values()], opts);
70
+ },
71
+ async softDelete(table, id) {
72
+ const s = store(table);
73
+ const row = s.get(id);
74
+ if (!row) return null;
75
+ const next = { ...row, deleted_at: new Date().toISOString() };
76
+ s.set(id, next);
77
+ return { ...next };
78
+ },
79
+ async exportAll() {
80
+ const tables = {};
81
+ for (const t of TABLE_NAMES) tables[t] = [...stores[t].values()].map((r) => ({ ...r }));
82
+ return {
83
+ app: APP_ID,
84
+ version: 1,
85
+ schemaVersion: SCHEMA_VERSION,
86
+ exportedAt: new Date().toISOString(),
87
+ tables,
88
+ };
89
+ },
90
+ async importAll(dump) {
91
+ validateDump(dump);
92
+ for (const t of TABLE_NAMES) {
93
+ const s = stores[t];
94
+ for (const row of dump.tables[t] || []) {
95
+ const spec = tableSpec(t);
96
+ s.set(row[spec.id], { ...row });
97
+ }
98
+ }
99
+ return { imported: TABLE_NAMES.reduce((n, t) => n + (dump.tables[t]?.length || 0), 0) };
100
+ },
101
+ async info() {
102
+ return { kind: 'memory', persistent: false };
103
+ },
104
+ async clearAll() {
105
+ for (const t of TABLE_NAMES) stores[t].clear();
106
+ return { cleared: true };
107
+ },
108
+ };
109
+ }
110
+
111
+ /** @param {any} dump */
112
+ export function validateDump(dump) {
113
+ if (!dump || dump.app !== APP_ID) throw new Error('Not a Professor Studio backup file');
114
+ if (!dump.tables || typeof dump.tables !== 'object') throw new Error('Backup file has no tables');
115
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * RemoteAdapter — talks to the local Studio server (`web/studio-api.js`) over HTTP, and to the
3
+ * future Cloudflare Worker + D1 backend unchanged (same contract, only base URL + token differ).
4
+ *
5
+ * The adapter is a dumb, table-level store: no business logic, no stamping. `repo/index.js` owns
6
+ * ids, owner_id, timestamps and tombstones. See the contract documented below.
7
+ *
8
+ * --- StorageAdapter contract (same as idb- and memory-adapter) ------------------------------
9
+ * get(table, id) -> row | null
10
+ * put(table, row) -> row (upsert by row.id; adapter stamps nothing)
11
+ * putMany(table, rows) -> rows
12
+ * list(table, { where?, includeDeleted?, orderBy?, limit? }) -> rows
13
+ * where: equality on indexed fields only (schema.js)
14
+ * orderBy: field name | { field, dir: 'asc'|'desc' }
15
+ * softDelete(table, id) -> row | null (sets deleted_at = now, ISO-8601)
16
+ * exportAll() -> { app, version, schemaVersion, exportedAt, tables }
17
+ * importAll(dump) -> { imported }
18
+ * clearAll() -> { cleared: true } (removes every row — STORE-03)
19
+ * info() -> { kind: 'indexeddb'|'memory'|'remote', persistent, … }
20
+ * -------------------------------------------------------------------------------------------
21
+ *
22
+ * --- HTTP contract (implemented by web/studio-api.js; the D1 Worker reuses it) --------------
23
+ * GET {base}/api/studio/db/health header X-Studio-Token
24
+ * -> { ok: true, kind: 'sqlite', path, schemaVersion }
25
+ * POST {base}/api/studio/db header X-Studio-Token
26
+ * body { op, table?, args: [ ...positional args ] }
27
+ * -> { ok: true, result }
28
+ * | { ok: false, error: '<message>', code: '<code>' }
29
+ * -------------------------------------------------------------------------------------------
30
+ */
31
+ import { tableSpec, assertIndexedField } from './schema.js';
32
+ import { isLoopbackBridgeUrl } from '../settings.js';
33
+ import { providerError } from '../ai/errors.js';
34
+
35
+ /** Raised when the Studio server rejects a request (used so callers can distinguish from network). */
36
+ export class RemoteAdapterError extends Error {
37
+ /** @param {string} message @param {{ code?: string, status?: number }} [info] */
38
+ constructor(message, { code = 'remote_error', status = 0 } = {}) {
39
+ super(message);
40
+ this.name = 'RemoteAdapterError';
41
+ this.code = code;
42
+ this.status = status;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * @param {{ baseUrl: string, token?: string, fetchImpl?: typeof fetch }} opts
48
+ * @returns {import('./remote-adapter.js').StorageAdapter & { label: string, health: () => Promise<any> }}
49
+ */
50
+ export function createRemoteAdapter({ baseUrl, token, fetchImpl = globalThis.fetch } = {}) {
51
+ const root = String(baseUrl || '').replace(/\/$/, '');
52
+ const headers = {
53
+ 'Content-Type': 'application/json',
54
+ ...(token ? { 'X-Studio-Token': token } : {}),
55
+ };
56
+ /** @type {{ path?: string, schemaVersion?: number }|null} */
57
+ let cachedHealth = null;
58
+
59
+ function assertLocalEndpoint() {
60
+ if (!isLoopbackBridgeUrl(root)) throw providerError('loopback_required');
61
+ }
62
+
63
+ async function post(op, table, args = []) {
64
+ assertLocalEndpoint();
65
+ let res;
66
+ try {
67
+ res = await fetchImpl(`${root}/api/studio/db`, {
68
+ method: 'POST',
69
+ headers,
70
+ body: JSON.stringify(table ? { op, table, args } : { op, args }),
71
+ });
72
+ } catch (err) {
73
+ throw new RemoteAdapterError(`Studio server not reachable at ${root}: ${err.message}`, { code: 'network' });
74
+ }
75
+ if (res.status === 401 || res.status === 403) {
76
+ throw new RemoteAdapterError(`Studio server rejected the pairing token (${res.status})`, {
77
+ code: 'auth',
78
+ status: res.status,
79
+ });
80
+ }
81
+ if (!res.ok) {
82
+ let payload = null;
83
+ try {
84
+ payload = await res.json();
85
+ } catch {
86
+ /* not JSON */
87
+ }
88
+ throw new RemoteAdapterError(payload?.error || `Studio server error ${res.status}`, {
89
+ code: payload?.code || 'server',
90
+ status: res.status,
91
+ });
92
+ }
93
+ const json = await res.json();
94
+ if (!json.ok) throw new RemoteAdapterError(json.error || 'Studio server error', { code: json.code || 'db_error' });
95
+ return json.result;
96
+ }
97
+
98
+ async function health() {
99
+ assertLocalEndpoint();
100
+ let res;
101
+ try {
102
+ res = await fetchImpl(`${root}/api/studio/db/health`, { headers });
103
+ } catch (err) {
104
+ throw new RemoteAdapterError(`Studio server not reachable at ${root}: ${err.message}`, { code: 'network' });
105
+ }
106
+ if (res.status === 401 || res.status === 403) {
107
+ throw new RemoteAdapterError(`Studio server rejected the pairing token (${res.status})`, { code: 'auth', status: res.status });
108
+ }
109
+ if (!res.ok) throw new RemoteAdapterError(`Studio server error ${res.status}`, { code: 'server', status: res.status });
110
+ const json = await res.json();
111
+ if (!json.ok) throw new RemoteAdapterError(json.error || 'Studio server error', { code: json.code || 'db_error' });
112
+ cachedHealth = { path: json.path, schemaVersion: json.schemaVersion };
113
+ return json;
114
+ }
115
+
116
+ return {
117
+ label: 'remote',
118
+ async get(table, id) {
119
+ tableSpec(table);
120
+ return post('get', table, [id]);
121
+ },
122
+ async put(table, row) {
123
+ tableSpec(table);
124
+ return post('put', table, [row]);
125
+ },
126
+ async putMany(table, rows) {
127
+ tableSpec(table);
128
+ return post('putMany', table, [rows]);
129
+ },
130
+ async list(table, opts = {}) {
131
+ tableSpec(table);
132
+ if (opts.where) for (const field of Object.keys(opts.where)) assertIndexedField(table, field);
133
+ return post('list', table, [opts]);
134
+ },
135
+ async softDelete(table, id) {
136
+ tableSpec(table);
137
+ return post('softDelete', table, [id]);
138
+ },
139
+ async exportAll() {
140
+ return post('exportAll');
141
+ },
142
+ async importAll(dump) {
143
+ return post('importAll', null, [dump]);
144
+ },
145
+ async clearAll() {
146
+ return post('clearAll');
147
+ },
148
+ async info() {
149
+ try {
150
+ const h = cachedHealth || (await health());
151
+ return { kind: 'remote', persistent: true, path: h.path, schemaVersion: h.schemaVersion };
152
+ } catch {
153
+ return { kind: 'remote', persistent: true };
154
+ }
155
+ },
156
+ health,
157
+ };
158
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * JS metadata mirroring migrations/0001_init.sql.
3
+ *
4
+ * The IndexedDB adapter creates one object store per table and one index per entry in
5
+ * `indexes`. Adapters only guarantee equality filtering on these fields (see StorageAdapter
6
+ * contract in remote-adapter.js) — richer queries live in repo/index.js so the SQLite/D1
7
+ * adapters stay generic.
8
+ *
9
+ * `fields` lists every column (documentation / the SQLite side); `indexes` are the only fields
10
+ * an adapter may use in `list({ where })`. `quiz_seed` is a demo-only convenience column
11
+ * (pre-written quiz so the bundled demo course is walkable with no provider).
12
+ */
13
+
14
+ /** @typedef {{ id: string, fields: string[], indexes: string[] }} TableSpec */
15
+
16
+ /** @type {Record<string, TableSpec>} */
17
+ export const TABLES = {
18
+ courses: {
19
+ id: 'id',
20
+ fields: ['id', 'owner_id', 'title', 'description', 'level', 'kind', 'goal', 'topics_json', 'source', 'status', 'language', 'external_id', 'created_at', 'updated_at', 'deleted_at'],
21
+ indexes: ['external_id'],
22
+ },
23
+ lessons: {
24
+ id: 'id',
25
+ fields: ['id', 'owner_id', 'course_id', 'position', 'title', 'summary', 'status', 'completed_at', 'current_version_id', 'quiz_seed', 'external_id', 'created_at', 'updated_at', 'deleted_at'],
26
+ indexes: ['course_id', 'status', 'external_id'],
27
+ },
28
+ lesson_versions: {
29
+ id: 'id',
30
+ fields: ['id', 'owner_id', 'lesson_id', 'version', 'content_md', 'model', 'provider', 'external_id', 'created_at', 'updated_at', 'deleted_at'],
31
+ indexes: ['lesson_id', 'external_id'],
32
+ },
33
+ quiz_attempts: {
34
+ id: 'id',
35
+ fields: ['id', 'owner_id', 'lesson_id', 'questions_json', 'answers_json', 'score', 'total', 'created_at', 'updated_at', 'deleted_at'],
36
+ indexes: ['lesson_id'],
37
+ },
38
+ cards: {
39
+ id: 'id',
40
+ fields: ['id', 'owner_id', 'course_id', 'lesson_id', 'front', 'back', 'ease', 'interval_days', 'reps', 'lapses', 'due_at', 'external_id', 'created_at', 'updated_at', 'deleted_at'],
41
+ indexes: ['course_id', 'lesson_id', 'due_at', 'external_id'],
42
+ },
43
+ reviews: {
44
+ id: 'id',
45
+ fields: ['id', 'owner_id', 'card_id', 'grade', 'reviewed_at', 'created_at', 'updated_at', 'deleted_at'],
46
+ indexes: ['card_id'],
47
+ },
48
+ chat_messages: {
49
+ id: 'id',
50
+ fields: ['id', 'owner_id', 'lesson_id', 'role', 'content', 'mode', 'created_at', 'updated_at', 'deleted_at'],
51
+ indexes: ['lesson_id'],
52
+ },
53
+ reflections: {
54
+ id: 'id',
55
+ fields: ['id', 'owner_id', 'lesson_id', 'hardest', 'confidence', 'note', 'created_at', 'updated_at', 'deleted_at'],
56
+ indexes: ['lesson_id'],
57
+ },
58
+ notes: {
59
+ id: 'id',
60
+ fields: ['id', 'owner_id', 'course_id', 'lesson_id', 'content_md', 'created_at', 'updated_at', 'deleted_at'],
61
+ indexes: ['course_id', 'lesson_id'],
62
+ },
63
+ activity: {
64
+ id: 'id',
65
+ fields: ['id', 'owner_id', 'day', 'kind', 'ref_id', 'created_at', 'updated_at', 'deleted_at'],
66
+ indexes: ['day', 'kind'],
67
+ },
68
+ workspace_files: {
69
+ id: 'id',
70
+ fields: ['id', 'owner_id', 'course_id', 'lesson_id', 'path', 'language', 'content', 'created_at', 'updated_at', 'deleted_at'],
71
+ indexes: ['lesson_id'],
72
+ },
73
+ run_results: {
74
+ id: 'id',
75
+ fields: ['id', 'owner_id', 'lesson_id', 'file_id', 'runner', 'language', 'stdout', 'stderr', 'exit_code', 'checks_json', 'duration_ms', 'created_at', 'updated_at', 'deleted_at'],
76
+ indexes: ['lesson_id'],
77
+ },
78
+ exercises: {
79
+ id: 'id',
80
+ fields: ['id', 'owner_id', 'course_id', 'lesson_id', 'position', 'type', 'spec_json', 'external_id', 'created_at', 'updated_at', 'deleted_at'],
81
+ indexes: ['course_id', 'lesson_id', 'external_id'],
82
+ },
83
+ exercise_attempts: {
84
+ id: 'id',
85
+ fields: ['id', 'owner_id', 'course_id', 'lesson_id', 'exercise_id', 'rows_json', 'result_json', 'correct', 'created_at', 'updated_at', 'deleted_at'],
86
+ indexes: ['exercise_id', 'lesson_id', 'course_id'],
87
+ },
88
+ capstones: {
89
+ id: 'id',
90
+ fields: ['id', 'owner_id', 'course_id', 'kind', 'brief_md', 'rubric_json', 'tests_json', 'hidden_answers_json', 'artifact_json', 'external_id', 'created_at', 'updated_at', 'deleted_at'],
91
+ indexes: ['course_id', 'external_id'],
92
+ },
93
+ capstone_submissions: {
94
+ id: 'id',
95
+ fields: ['id', 'owner_id', 'capstone_id', 'attempt', 'content_md', 'repo_url', 'files_json', 'test_results_json', 'review_json', 'verdict', 'external_id', 'created_at', 'updated_at', 'deleted_at'],
96
+ indexes: ['capstone_id', 'external_id'],
97
+ },
98
+ learning_events: {
99
+ id: 'id',
100
+ fields: ['id', 'owner_id', 'day', 'kind', 'surface', 'course_id', 'lesson_id', 'ref_id', 'meta_json', 'created_at', 'updated_at', 'deleted_at'],
101
+ indexes: ['day', 'kind', 'surface', 'course_id', 'lesson_id'],
102
+ },
103
+ revision_links: {
104
+ id: 'id',
105
+ fields: ['id', 'owner_id', 'surface', 'course_id', 'lesson_id', 'attempt_table', 'attempt_id', 'feedback_table', 'feedback_id', 'revised_table', 'revised_id', 'link_kind', 'created_at', 'updated_at', 'deleted_at'],
106
+ indexes: ['surface', 'course_id', 'lesson_id', 'attempt_id', 'revised_id', 'link_kind'],
107
+ },
108
+ beta_profile: {
109
+ id: 'id',
110
+ fields: ['id', 'owner_id', 'participant_id', 'activated_at', 'activated_day', 'created_at', 'updated_at', 'deleted_at'],
111
+ indexes: [],
112
+ },
113
+ };
114
+
115
+ export const TABLE_NAMES = Object.keys(TABLES);
116
+
117
+ /** Highest migration file shipped in `studio/migrations/` (D-12 single constant source). */
118
+ export const LATEST_MIGRATION_VERSION = 6;
119
+
120
+ export const SCHEMA_VERSION = LATEST_MIGRATION_VERSION;
121
+
122
+ /**
123
+ * @param {string} table
124
+ * @returns {TableSpec}
125
+ */
126
+ export function tableSpec(table) {
127
+ const spec = TABLES[table];
128
+ if (!spec) throw new Error(`Unknown table: ${table}`);
129
+ return spec;
130
+ }
131
+
132
+ /**
133
+ * Throw unless `field` is an equality-filterable (indexed) field of `table`.
134
+ * @param {string} table
135
+ * @param {string} field
136
+ */
137
+ export function assertIndexedField(table, field) {
138
+ const spec = tableSpec(table);
139
+ if (field === spec.id) return;
140
+ if (!spec.indexes.includes(field)) {
141
+ throw new Error(`Field "${field}" is not indexed on table "${table}"`);
142
+ }
143
+ }