learnx-cli 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. learnx_cli-0.3.0/.claude/agents/README.md +32 -0
  2. learnx_cli-0.3.0/.claude/agents/implementation.md +17 -0
  3. learnx_cli-0.3.0/.claude/agents/product_check.md +111 -0
  4. learnx_cli-0.3.0/.claude/agents/quality.md +15 -0
  5. learnx_cli-0.3.0/.claude/agents/regression_check.md +22 -0
  6. learnx_cli-0.3.0/.claude/agents/simplification.md +15 -0
  7. learnx_cli-0.3.0/.claude/agents/testing.md +15 -0
  8. learnx_cli-0.3.0/.claude/agents/verify_fixes.md +20 -0
  9. learnx_cli-0.3.0/.claude/settings.assisted.json +27 -0
  10. learnx_cli-0.3.0/.claude/settings.json +26 -0
  11. learnx_cli-0.3.0/.dockerignore +19 -0
  12. learnx_cli-0.3.0/.github/workflows/ci.yml +80 -0
  13. learnx_cli-0.3.0/.gitignore +71 -0
  14. learnx_cli-0.3.0/.pre-commit-config.yaml +7 -0
  15. learnx_cli-0.3.0/CLAUDE.md +144 -0
  16. learnx_cli-0.3.0/DEVLOOP.md +214 -0
  17. learnx_cli-0.3.0/Dockerfile +45 -0
  18. learnx_cli-0.3.0/PKG-INFO +240 -0
  19. learnx_cli-0.3.0/README.md +215 -0
  20. learnx_cli-0.3.0/README_v1.md +438 -0
  21. learnx_cli-0.3.0/agile.png +0 -0
  22. learnx_cli-0.3.0/dev_setup/README.md +48 -0
  23. learnx_cli-0.3.0/dev_setup/autonomy_plan.md +285 -0
  24. learnx_cli-0.3.0/dev_setup/container_plan.md +105 -0
  25. learnx_cli-0.3.0/dev_setup/context_hygiene_plan.md +266 -0
  26. learnx_cli-0.3.0/dev_setup/handoff_template.md +297 -0
  27. learnx_cli-0.3.0/dev_setup/run_example.md +272 -0
  28. learnx_cli-0.3.0/dev_setup/sandbox_plan.md +311 -0
  29. learnx_cli-0.3.0/dev_setup/spec-driven_plan.md +277 -0
  30. learnx_cli-0.3.0/devloop.toml +47 -0
  31. learnx_cli-0.3.0/fixes/fix001.md +41 -0
  32. learnx_cli-0.3.0/fixes/fix002.md +52 -0
  33. learnx_cli-0.3.0/fixes/fix003.md +42 -0
  34. learnx_cli-0.3.0/fixes/fix004.md +31 -0
  35. learnx_cli-0.3.0/fixes/fix005.md +35 -0
  36. learnx_cli-0.3.0/fixes/fix006.md +36 -0
  37. learnx_cli-0.3.0/fixes/fix007.md +44 -0
  38. learnx_cli-0.3.0/fixes/fix008.md +35 -0
  39. learnx_cli-0.3.0/fixes/fix009.md +45 -0
  40. learnx_cli-0.3.0/fixes/fix010.md +36 -0
  41. learnx_cli-0.3.0/fixes/fix011.md +37 -0
  42. learnx_cli-0.3.0/fixes/fix012.md +40 -0
  43. learnx_cli-0.3.0/fixes/fix013.md +82 -0
  44. learnx_cli-0.3.0/fixes/fix014.md +48 -0
  45. learnx_cli-0.3.0/fixes/fix015.md +36 -0
  46. learnx_cli-0.3.0/fixes/fix016.md +73 -0
  47. learnx_cli-0.3.0/fixes/fix017.md +77 -0
  48. learnx_cli-0.3.0/fixes/fix018.md +90 -0
  49. learnx_cli-0.3.0/fixes/fix019.md +110 -0
  50. learnx_cli-0.3.0/fixes/fix020.md +73 -0
  51. learnx_cli-0.3.0/fixes/fix021.md +69 -0
  52. learnx_cli-0.3.0/fixes/fix022.md +56 -0
  53. learnx_cli-0.3.0/fixes/fix023.md +68 -0
  54. learnx_cli-0.3.0/fixes/fix024.md +61 -0
  55. learnx_cli-0.3.0/fixes/fix025.md +58 -0
  56. learnx_cli-0.3.0/fixes/fix026.md +47 -0
  57. learnx_cli-0.3.0/fixes/fix027.md +40 -0
  58. learnx_cli-0.3.0/fixes/fix028.md +51 -0
  59. learnx_cli-0.3.0/fixes/fix029.md +58 -0
  60. learnx_cli-0.3.0/fixes/fix030.md +53 -0
  61. learnx_cli-0.3.0/fixes/fix031.md +58 -0
  62. learnx_cli-0.3.0/fixes/fix032.md +52 -0
  63. learnx_cli-0.3.0/fixes/fix033.md +46 -0
  64. learnx_cli-0.3.0/fixes/fix034.md +59 -0
  65. learnx_cli-0.3.0/fixes/fix035.md +72 -0
  66. learnx_cli-0.3.0/fixes/fix036.md +87 -0
  67. learnx_cli-0.3.0/fixes/fix037.md +58 -0
  68. learnx_cli-0.3.0/fixes/fix038.md +88 -0
  69. learnx_cli-0.3.0/fixes/fix039.md +71 -0
  70. learnx_cli-0.3.0/fixes/fix040.md +67 -0
  71. learnx_cli-0.3.0/fixes/fix041.md +74 -0
  72. learnx_cli-0.3.0/learnX.png +0 -0
  73. learnx_cli-0.3.0/learning/javaConcept.md +117 -0
  74. learnx_cli-0.3.0/plan/devloop_cli_plan.md +284 -0
  75. learnx_cli-0.3.0/plan/devloop_packaging_plan.md +209 -0
  76. learnx_cli-0.3.0/plan/v0_plan.md +179 -0
  77. learnx_cli-0.3.0/plan/v10_plan.md +112 -0
  78. learnx_cli-0.3.0/plan/v11_plan.md +118 -0
  79. learnx_cli-0.3.0/plan/v12_plan.md +134 -0
  80. learnx_cli-0.3.0/plan/v1_plan.md +1961 -0
  81. learnx_cli-0.3.0/plan/v2_plan.md +454 -0
  82. learnx_cli-0.3.0/plan/v3_plan.md +1132 -0
  83. learnx_cli-0.3.0/plan/v4_architecture.md +252 -0
  84. learnx_cli-0.3.0/plan/v4_plan.md +137 -0
  85. learnx_cli-0.3.0/plan/v4_update_plan.md +187 -0
  86. learnx_cli-0.3.0/plan/v5_plan.md +125 -0
  87. learnx_cli-0.3.0/plan/v6_plan.md +120 -0
  88. learnx_cli-0.3.0/plan/v7_plan.md +151 -0
  89. learnx_cli-0.3.0/plan/v8_plan.md +115 -0
  90. learnx_cli-0.3.0/plan/v9_plan.md +115 -0
  91. learnx_cli-0.3.0/pyproject.toml +67 -0
  92. learnx_cli-0.3.0/scripts/__init__.py +0 -0
  93. learnx_cli-0.3.0/scripts/devloop.py +206 -0
  94. learnx_cli-0.3.0/scripts/dk/__init__.py +0 -0
  95. learnx_cli-0.3.0/scripts/dk/config.py +69 -0
  96. learnx_cli-0.3.0/scripts/dk/dashboard.py +190 -0
  97. learnx_cli-0.3.0/scripts/dk/docker.py +96 -0
  98. learnx_cli-0.3.0/scripts/dk/notifier.py +127 -0
  99. learnx_cli-0.3.0/scripts/dk/process.py +95 -0
  100. learnx_cli-0.3.0/scripts/dk/runners.py +315 -0
  101. learnx_cli-0.3.0/scripts/run_review.py +315 -0
  102. learnx_cli-0.3.0/scripts/test_openrouter_models.py +221 -0
  103. learnx_cli-0.3.0/scripts/tests/__init__.py +0 -0
  104. learnx_cli-0.3.0/scripts/tests/conftest.py +10 -0
  105. learnx_cli-0.3.0/scripts/tests/test_cli.py +145 -0
  106. learnx_cli-0.3.0/scripts/tests/test_devloop.py +215 -0
  107. learnx_cli-0.3.0/scripts/tests/test_docker_commands.py +173 -0
  108. learnx_cli-0.3.0/scripts/tests/test_notifier.py +271 -0
  109. learnx_cli-0.3.0/scripts/tests/test_review_agents.py +263 -0
  110. learnx_cli-0.3.0/scripts/tests/test_version_runner.py +176 -0
  111. learnx_cli-0.3.0/specs/v0/day0.md +207 -0
  112. learnx_cli-0.3.0/specs/v0/day1.md +123 -0
  113. learnx_cli-0.3.0/specs/v0/day2.md +176 -0
  114. learnx_cli-0.3.0/specs/v0/day3.md +266 -0
  115. learnx_cli-0.3.0/specs/v1/day1.md +588 -0
  116. learnx_cli-0.3.0/specs/v1/day2.md +339 -0
  117. learnx_cli-0.3.0/specs/v1/day3.md +488 -0
  118. learnx_cli-0.3.0/specs/v1/day4.md +490 -0
  119. learnx_cli-0.3.0/specs/v1/day5.md +463 -0
  120. learnx_cli-0.3.0/specs/v1/day6.md +426 -0
  121. learnx_cli-0.3.0/specs/v1/day7.md +822 -0
  122. learnx_cli-0.3.0/specs/v1/explain-mode.md +225 -0
  123. learnx_cli-0.3.0/specs/v10/day28.md +363 -0
  124. learnx_cli-0.3.0/specs/v10/day29.md +236 -0
  125. learnx_cli-0.3.0/specs/v11/day30.md +411 -0
  126. learnx_cli-0.3.0/specs/v11/day31.md +311 -0
  127. learnx_cli-0.3.0/specs/v12/day1.md +578 -0
  128. learnx_cli-0.3.0/specs/v12/day2.md +486 -0
  129. learnx_cli-0.3.0/specs/v12/day3.md +350 -0
  130. learnx_cli-0.3.0/specs/v12/day4.md +355 -0
  131. learnx_cli-0.3.0/specs/v2/day10.md +380 -0
  132. learnx_cli-0.3.0/specs/v2/day11.md +334 -0
  133. learnx_cli-0.3.0/specs/v2/day12.md +370 -0
  134. learnx_cli-0.3.0/specs/v2/day8.md +253 -0
  135. learnx_cli-0.3.0/specs/v2/day9.md +222 -0
  136. learnx_cli-0.3.0/specs/v3/day13.md +242 -0
  137. learnx_cli-0.3.0/specs/v3/day14.md +364 -0
  138. learnx_cli-0.3.0/specs/v3/day15.md +396 -0
  139. learnx_cli-0.3.0/specs/v3/day16.md +363 -0
  140. learnx_cli-0.3.0/specs/v3/day16_extra.md +355 -0
  141. learnx_cli-0.3.0/specs/v4/day17.md +175 -0
  142. learnx_cli-0.3.0/specs/v4-workflow/day0.md +138 -0
  143. learnx_cli-0.3.0/specs/v4-workflow/day1.md +157 -0
  144. learnx_cli-0.3.0/specs/v4-workflow/day1_preparation.md +83 -0
  145. learnx_cli-0.3.0/specs/v4-workflow/day2.md +207 -0
  146. learnx_cli-0.3.0/specs/v4-workflow/day2b.md +428 -0
  147. learnx_cli-0.3.0/specs/v4-workflow/day3.md +488 -0
  148. learnx_cli-0.3.0/specs/v4-workflow/day4.md +228 -0
  149. learnx_cli-0.3.0/specs/v4-workflow/day5.md +248 -0
  150. learnx_cli-0.3.0/specs/v4-workflow/day6.md +182 -0
  151. learnx_cli-0.3.0/specs/v4-workflow/day7.md +231 -0
  152. learnx_cli-0.3.0/specs/v5/day18.md +224 -0
  153. learnx_cli-0.3.0/specs/v5/day19.md +248 -0
  154. learnx_cli-0.3.0/specs/v6/day20.md +424 -0
  155. learnx_cli-0.3.0/specs/v6/day21.md +163 -0
  156. learnx_cli-0.3.0/specs/v7/day22.md +303 -0
  157. learnx_cli-0.3.0/specs/v7/day23.md +320 -0
  158. learnx_cli-0.3.0/specs/v8/day24.md +443 -0
  159. learnx_cli-0.3.0/specs/v8/day25.md +235 -0
  160. learnx_cli-0.3.0/specs/v9/day26.md +345 -0
  161. learnx_cli-0.3.0/specs/v9/day27.md +230 -0
  162. learnx_cli-0.3.0/tutor/.env copy.example +4 -0
  163. learnx_cli-0.3.0/tutor/__init__.py +0 -0
  164. learnx_cli-0.3.0/tutor/__main__.py +4 -0
  165. learnx_cli-0.3.0/tutor/assets/__init__.py +5 -0
  166. learnx_cli-0.3.0/tutor/assets/html/fonts/Inter-Bold.woff2 +0 -0
  167. learnx_cli-0.3.0/tutor/assets/html/fonts/Inter-Regular.woff2 +0 -0
  168. learnx_cli-0.3.0/tutor/assets/html/fonts/Inter-SemiBold.woff2 +0 -0
  169. learnx_cli-0.3.0/tutor/assets/html/fonts/JetBrainsMono-Regular.woff2 +0 -0
  170. learnx_cli-0.3.0/tutor/assets/html/highlight-java.min.js +2 -0
  171. learnx_cli-0.3.0/tutor/assets/html/highlight-javascript.min.js +2 -0
  172. learnx_cli-0.3.0/tutor/assets/html/highlight-python.min.js +2 -0
  173. learnx_cli-0.3.0/tutor/assets/html/highlight.min.js +17 -0
  174. learnx_cli-0.3.0/tutor/assets/html/mermaid.min.js +31 -0
  175. learnx_cli-0.3.0/tutor/assets/html/slide_base.css +464 -0
  176. learnx_cli-0.3.0/tutor/assets/html/theme-learnx-dark.css +12 -0
  177. learnx_cli-0.3.0/tutor/audio/__init__.py +0 -0
  178. learnx_cli-0.3.0/tutor/audio/audio_builder.py +143 -0
  179. learnx_cli-0.3.0/tutor/audio/sanitizer.py +9 -0
  180. learnx_cli-0.3.0/tutor/audio/tts_renderer.py +54 -0
  181. learnx_cli-0.3.0/tutor/cli/__init__.py +0 -0
  182. learnx_cli-0.3.0/tutor/cli/commands.py +391 -0
  183. learnx_cli-0.3.0/tutor/cli/logo.py +21 -0
  184. learnx_cli-0.3.0/tutor/cli/playback_commands.py +239 -0
  185. learnx_cli-0.3.0/tutor/cli/shell.py +91 -0
  186. learnx_cli-0.3.0/tutor/cli/shell_context.py +18 -0
  187. learnx_cli-0.3.0/tutor/cli/theme.py +39 -0
  188. learnx_cli-0.3.0/tutor/cli/video_commands.py +123 -0
  189. learnx_cli-0.3.0/tutor/config.py +122 -0
  190. learnx_cli-0.3.0/tutor/conftest.py +5 -0
  191. learnx_cli-0.3.0/tutor/constants.py +82 -0
  192. learnx_cli-0.3.0/tutor/exceptions.py +26 -0
  193. learnx_cli-0.3.0/tutor/generation/__init__.py +0 -0
  194. learnx_cli-0.3.0/tutor/generation/assembler.py +81 -0
  195. learnx_cli-0.3.0/tutor/generation/curriculum.py +97 -0
  196. learnx_cli-0.3.0/tutor/generation/dialogue.py +172 -0
  197. learnx_cli-0.3.0/tutor/generation/narrator.py +122 -0
  198. learnx_cli-0.3.0/tutor/generation/segment_parser.py +223 -0
  199. learnx_cli-0.3.0/tutor/generation/segment_planner.py +200 -0
  200. learnx_cli-0.3.0/tutor/generation/visual_planner.py +205 -0
  201. learnx_cli-0.3.0/tutor/infra/__init__.py +0 -0
  202. learnx_cli-0.3.0/tutor/infra/llm.py +152 -0
  203. learnx_cli-0.3.0/tutor/ingestion/__init__.py +0 -0
  204. learnx_cli-0.3.0/tutor/ingestion/chunker.py +171 -0
  205. learnx_cli-0.3.0/tutor/ingestion/doc_analyzer.py +41 -0
  206. learnx_cli-0.3.0/tutor/ingestion/parse_content.py +19 -0
  207. learnx_cli-0.3.0/tutor/ingestion/summarizer.py +51 -0
  208. learnx_cli-0.3.0/tutor/inspector.py +117 -0
  209. learnx_cli-0.3.0/tutor/llm_config.toml +58 -0
  210. learnx_cli-0.3.0/tutor/models.py +147 -0
  211. learnx_cli-0.3.0/tutor/player/__init__.py +0 -0
  212. learnx_cli-0.3.0/tutor/player/input_handler.py +45 -0
  213. learnx_cli-0.3.0/tutor/player/player.py +308 -0
  214. learnx_cli-0.3.0/tutor/player/player_display.py +117 -0
  215. learnx_cli-0.3.0/tutor/prompts/curriculum.txt +67 -0
  216. learnx_cli-0.3.0/tutor/prompts/dialogue.txt +62 -0
  217. learnx_cli-0.3.0/tutor/prompts/narrate.txt +34 -0
  218. learnx_cli-0.3.0/tutor/prompts/qa.txt +17 -0
  219. learnx_cli-0.3.0/tutor/prompts/summarize.txt +9 -0
  220. learnx_cli-0.3.0/tutor/prompts/visual.txt +60 -0
  221. learnx_cli-0.3.0/tutor/prompts/visual_v3.txt +91 -0
  222. learnx_cli-0.3.0/tutor/qa/__init__.py +0 -0
  223. learnx_cli-0.3.0/tutor/qa/qa.py +105 -0
  224. learnx_cli-0.3.0/tutor/requirements-dev.txt +2 -0
  225. learnx_cli-0.3.0/tutor/requirements.txt +12 -0
  226. learnx_cli-0.3.0/tutor/sample_docs/headingless_large.md +1 -0
  227. learnx_cli-0.3.0/tutor/sample_docs/headingless_test.md +1 -0
  228. learnx_cli-0.3.0/tutor/sample_docs/java-basics.md +78 -0
  229. learnx_cli-0.3.0/tutor/tests/__init__.py +0 -0
  230. learnx_cli-0.3.0/tutor/tests/audio/__init__.py +0 -0
  231. learnx_cli-0.3.0/tutor/tests/audio/test_audio_builder.py +106 -0
  232. learnx_cli-0.3.0/tutor/tests/audio/test_sanitizer.py +41 -0
  233. learnx_cli-0.3.0/tutor/tests/cli/__init__.py +0 -0
  234. learnx_cli-0.3.0/tutor/tests/cli/test_commands.py +67 -0
  235. learnx_cli-0.3.0/tutor/tests/cli/test_video_commands.py +190 -0
  236. learnx_cli-0.3.0/tutor/tests/e2e/README.md +61 -0
  237. learnx_cli-0.3.0/tutor/tests/e2e/__init__.py +0 -0
  238. learnx_cli-0.3.0/tutor/tests/e2e/conftest.py +117 -0
  239. learnx_cli-0.3.0/tutor/tests/e2e/fixtures/README.md +17 -0
  240. learnx_cli-0.3.0/tutor/tests/e2e/fixtures/sample.md +13 -0
  241. learnx_cli-0.3.0/tutor/tests/e2e/test_audio_quality.py +40 -0
  242. learnx_cli-0.3.0/tutor/tests/e2e/test_av_sync.py +56 -0
  243. learnx_cli-0.3.0/tutor/tests/e2e/test_pipeline_smoke.py +37 -0
  244. learnx_cli-0.3.0/tutor/tests/e2e/test_slide_render.py +72 -0
  245. learnx_cli-0.3.0/tutor/tests/e2e/test_video_streams.py +104 -0
  246. learnx_cli-0.3.0/tutor/tests/generation/__init__.py +0 -0
  247. learnx_cli-0.3.0/tutor/tests/generation/conftest.py +134 -0
  248. learnx_cli-0.3.0/tutor/tests/generation/test_assembler.py +64 -0
  249. learnx_cli-0.3.0/tutor/tests/generation/test_curriculum.py +107 -0
  250. learnx_cli-0.3.0/tutor/tests/generation/test_narrator.py +165 -0
  251. learnx_cli-0.3.0/tutor/tests/generation/test_segment_edge_cases.py +280 -0
  252. learnx_cli-0.3.0/tutor/tests/generation/test_segment_planner.py +324 -0
  253. learnx_cli-0.3.0/tutor/tests/generation/test_visual_planner.py +319 -0
  254. learnx_cli-0.3.0/tutor/tests/ingestion/__init__.py +0 -0
  255. learnx_cli-0.3.0/tutor/tests/ingestion/test_chunker.py +94 -0
  256. learnx_cli-0.3.0/tutor/tests/ingestion/test_doc_analyzer.py +51 -0
  257. learnx_cli-0.3.0/tutor/tests/player/__init__.py +0 -0
  258. learnx_cli-0.3.0/tutor/tests/player/test_player_states.py +88 -0
  259. learnx_cli-0.3.0/tutor/tests/test_assets.py +39 -0
  260. learnx_cli-0.3.0/tutor/tests/test_models_visual.py +180 -0
  261. learnx_cli-0.3.0/tutor/tests/visual/__init__.py +0 -0
  262. learnx_cli-0.3.0/tutor/tests/visual/test_beat_timer.py +321 -0
  263. learnx_cli-0.3.0/tutor/tests/visual/test_pipeline_integration.py +178 -0
  264. learnx_cli-0.3.0/tutor/tests/visual/test_slide_renderer.py +298 -0
  265. learnx_cli-0.3.0/tutor/tests/visual/test_subtitle_writer.py +165 -0
  266. learnx_cli-0.3.0/tutor/tests/visual/test_video_assembler.py +108 -0
  267. learnx_cli-0.3.0/tutor/tests/visual/test_visual_pipeline.py +270 -0
  268. learnx_cli-0.3.0/tutor/tutor.py +365 -0
  269. learnx_cli-0.3.0/tutor/visual/__init__.py +213 -0
  270. learnx_cli-0.3.0/tutor/visual/beat_timer.py +222 -0
  271. learnx_cli-0.3.0/tutor/visual/slide_renderer.py +236 -0
  272. learnx_cli-0.3.0/tutor/visual/subtitle_writer.py +187 -0
  273. learnx_cli-0.3.0/tutor/visual/templates/_base.html.j2 +40 -0
  274. learnx_cli-0.3.0/tutor/visual/templates/analogy.html.j2 +21 -0
  275. learnx_cli-0.3.0/tutor/visual/templates/callout.html.j2 +10 -0
  276. learnx_cli-0.3.0/tutor/visual/templates/code_example.html.j2 +12 -0
  277. learnx_cli-0.3.0/tutor/visual/templates/comparison.html.j2 +28 -0
  278. learnx_cli-0.3.0/tutor/visual/templates/decision_guide.html.j2 +37 -0
  279. learnx_cli-0.3.0/tutor/visual/templates/definition.html.j2 +13 -0
  280. learnx_cli-0.3.0/tutor/visual/templates/diagram.html.j2 +11 -0
  281. learnx_cli-0.3.0/tutor/visual/templates/hook_question.html.j2 +17 -0
  282. learnx_cli-0.3.0/tutor/visual/templates/key_insight.html.j2 +9 -0
  283. learnx_cli-0.3.0/tutor/visual/templates/memory_hook.html.j2 +7 -0
  284. learnx_cli-0.3.0/tutor/visual/templates/outro.html.j2 +16 -0
  285. learnx_cli-0.3.0/tutor/visual/templates/question_prompt.html.j2 +13 -0
  286. learnx_cli-0.3.0/tutor/visual/templates/step_sequence.html.j2 +14 -0
  287. learnx_cli-0.3.0/tutor/visual/templates/title_card.html.j2 +12 -0
  288. learnx_cli-0.3.0/tutor/visual/video_assembler.py +299 -0
@@ -0,0 +1,32 @@
1
+ # Review Agents
2
+
3
+ Each `.md` file in this directory is a Claude sub-agent invoked during pre-merge review.
4
+
5
+ ## Agents
6
+
7
+ | File | Role |
8
+ |------|------|
9
+ | quality.md | Bugs, security, logic errors |
10
+ | implementation.md | Does the code match the spec's acceptance criteria? |
11
+ | testing.md | Test coverage and quality |
12
+ | simplification.md | Over-engineering and dead code |
13
+ | product_check.md | Runs the real pipeline, checks output quality with ffprobe/pydub/Playwright |
14
+
15
+ ## How to add an agent
16
+
17
+ 1. Create `.claude/agents/<name>.md`
18
+ 2. Add YAML frontmatter with `name` (must match filename) and `description`
19
+ 3. Write the review instructions in the body
20
+ 4. Add the agent name to the parallel launch list in `REVIEW_PROMPT_TEMPLATE` in `scripts/run_review.py`
21
+
22
+ ## The fixes/ convention
23
+
24
+ Agents do NOT write to `fixes/`. The `fixes/` directory is human-curated institutional
25
+ memory for surprises that are not derivable from reading the code.
26
+
27
+ Each agent's output includes a **Suggested Fix Notes** section. If an agent encountered
28
+ a novel env quirk, API edge case, or tool gotcha during review, it flags it there.
29
+ The human reads those flags and decides whether to write a permanent `fixes/fixNNN.md`.
30
+
31
+ Rule: if removing the fix note would confuse a future agent reading the codebase cold,
32
+ it belongs in `fixes/`. If the code or a test already explains it, it does not.
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: implementation
3
+ description: Verifies that the implementation matches the spec's acceptance criteria
4
+ ---
5
+
6
+ You will be given a git diff and the acceptance criteria from the spec that drove
7
+ these changes. Check each criterion against the diff.
8
+
9
+ For each criterion:
10
+ - PASS: the diff clearly satisfies it
11
+ - FAIL: the diff does not satisfy it — explain what is missing
12
+ - PARTIAL: the diff partially satisfies it — explain what remains
13
+
14
+ List spec criteria in order. End with a one-line summary: "N/M criteria satisfied."
15
+
16
+ If no spec file is provided, verify that the implementation is internally consistent
17
+ and that all stated goals in commit messages or comments are actually achieved.
@@ -0,0 +1,111 @@
1
+ ---
2
+ name: product_check
3
+ description: Runs the LearnX pipeline on the test fixture and verifies audio, video, and slide output quality
4
+ ---
5
+
6
+ You are checking whether the product works, not whether the code looks right.
7
+ Run the following steps in order and report the result of each.
8
+
9
+ ## Step 1 — Run the pipeline on the test fixture
10
+
11
+ ```bash
12
+ python -m tutor generate tutor/tests/e2e/fixtures/sample.md --output /tmp/learnx_check
13
+ ```
14
+
15
+ Expected: exits 0, output directory contains tutorial.mp3, tutorial.mp4 (or slides).
16
+ If it crashes: paste the traceback. STOP — do not proceed to further steps.
17
+
18
+ ## Step 2 — Verify audio stream in video
19
+
20
+ ```bash
21
+ ffprobe -v error \
22
+ -select_streams a:0 \
23
+ -show_entries stream=codec_type,duration,bit_rate \
24
+ -of json /tmp/learnx_check/tutorial.mp4
25
+ ```
26
+
27
+ Expected: `codec_type` is `audio`, `duration` > 0.
28
+ FAIL if: no audio stream returned, or duration is 0 or null.
29
+
30
+ ## Step 3 — Check audio is not silent
31
+
32
+ ```python
33
+ from pydub import AudioSegment
34
+ audio = AudioSegment.from_mp3("/tmp/learnx_check/tutorial.mp3")
35
+ db_level = audio.dBFS
36
+ print(f"Audio level: {db_level:.1f} dBFS")
37
+ # Anything above -60 dBFS is audible
38
+ assert db_level > -60, f"Audio appears silent: {db_level:.1f} dBFS"
39
+ ```
40
+
41
+ Expected: dBFS above -60.
42
+ FAIL if: dBFS is -inf or below -60 (silent or near-silent track).
43
+
44
+ ## Step 4 — Screenshot HTML slides (if slides were generated)
45
+
46
+ ```python
47
+ from playwright.sync_api import sync_playwright
48
+ import pathlib
49
+
50
+ slide_dir = pathlib.Path("/tmp/learnx_check/slides")
51
+ if slide_dir.exists():
52
+ html_files = list(slide_dir.glob("*.html"))
53
+ with sync_playwright() as p:
54
+ browser = p.chromium.launch()
55
+ page = browser.new_page(viewport={"width": 1280, "height": 720})
56
+ for html in html_files[:3]: # first 3 slides max
57
+ page.goto(f"file://{html}")
58
+ page.wait_for_load_state("networkidle")
59
+ screenshot = f"/tmp/learnx_check/screenshot_{html.stem}.png"
60
+ page.screenshot(path=screenshot)
61
+ print(f"Screenshot saved: {screenshot}")
62
+ browser.close()
63
+ ```
64
+
65
+ After running: describe what the screenshots look like. Are slides blank? Is text visible?
66
+ Are there any error messages or broken layout?
67
+
68
+ ## Step 5 — Check A/V sync (timing.json vs audio duration)
69
+
70
+ ```python
71
+ import json
72
+ from pydub import AudioSegment
73
+ import pathlib
74
+
75
+ timing = json.loads(
76
+ pathlib.Path("/tmp/learnx_check/tutorial.timing.json").read_text()
77
+ )
78
+ audio = AudioSegment.from_mp3("/tmp/learnx_check/tutorial.mp3")
79
+
80
+ last_unit = max(int(k) for k in timing["units"])
81
+ last_entry = timing["units"][str(last_unit)][-1]
82
+ timing_end_ms = last_entry["end_ms"]
83
+ audio_duration_ms = len(audio)
84
+ drift_ms = abs(audio_duration_ms - timing_end_ms)
85
+
86
+ print(f"Audio duration: {audio_duration_ms}ms")
87
+ print(f"Timing end: {timing_end_ms}ms")
88
+ print(f"Drift: {drift_ms}ms")
89
+ assert drift_ms < 500, f"A/V drift too large: {drift_ms}ms"
90
+ ```
91
+
92
+ Expected: drift < 500ms.
93
+ FAIL if: drift >= 500ms (slides and audio will be noticeably out of sync).
94
+
95
+ ## Report format
96
+
97
+ ```
98
+ PIPELINE RUN: PASS / FAIL
99
+ AUDIO STREAM: PRESENT (Xs) / MISSING
100
+ SILENCE CHECK: {dBFS value} — PASS / FAIL
101
+ SLIDE SCREENSHOTS: [describe what you see for each slide]
102
+ A/V SYNC: {drift}ms — PASS / FAIL
103
+
104
+ OVERALL: PRODUCT WORKING / PRODUCT BROKEN
105
+ Blocking issues: [list or "none"]
106
+
107
+ Suggested fix notes:
108
+ [List any novel pipeline surprises — env issues, tool edge cases, encoding quirks —
109
+ that are NOT obvious from reading the code. Write "none" if nothing surprising happened.
110
+ Do NOT write to fixes/ — this is for the human to decide.]
111
+ ```
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: quality
3
+ description: Reviews code for bugs, security issues, and logic errors
4
+ ---
5
+
6
+ Review the git diff for the following. Report each finding as:
7
+ FILE:LINE — severity (critical/major/minor) — description
8
+
9
+ 1. Logic errors: off-by-one, wrong condition, silent failure paths
10
+ 2. Security: hardcoded secrets, path traversal, injection vulnerabilities
11
+ 3. Resource leaks: unclosed files, unreleased locks, unbounded loops
12
+ 4. Error handling: exceptions swallowed silently, wrong exception types caught
13
+
14
+ Output "NO ISSUES FOUND" if the diff is clean. Do not suggest style improvements
15
+ — that is the simplification agent's job.
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: regression_check
3
+ description: Check that phase 1 fix commits did not introduce new problems
4
+ ---
5
+
6
+ You are running Phase 2 of a two-phase review. Examine only the changes introduced
7
+ by the phase 1 fix commits — not the original implementation diff.
8
+
9
+ Run `git log --oneline main..HEAD` to list all commits. Identify which commits are
10
+ fix commits (typically the most recent ones, added after the original spec work).
11
+ Run `git show <hash>` on each fix commit.
12
+
13
+ Look for:
14
+ 1. New logic errors or off-by-ones introduced by the fix
15
+ 2. New security issues or resource leaks
16
+ 3. Broken or removed tests
17
+ 4. Unrelated changes bundled into the fix commit
18
+
19
+ End with a single verdict line:
20
+ CLEAN — no regressions detected
21
+ or
22
+ REGRESSION FOUND — <one-sentence description>
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: simplification
3
+ description: Detects over-engineering, unnecessary abstraction, and dead code
4
+ ---
5
+
6
+ Review the diff for complexity that is not justified by the spec.
7
+
8
+ 1. Abstractions: classes or functions created for a single use case
9
+ 2. Premature generalisation: parameters, config, or interfaces for hypothetical callers
10
+ 3. Dead code: code that is added but never called
11
+ 4. Over-indirection: wrapper functions that just call one other function
12
+ 5. Unnecessary comments: comments that restate what the code already says
13
+
14
+ Rate overall complexity: LOW / MEDIUM / HIGH.
15
+ List specific findings only. Do not suggest refactors not related to over-engineering.
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: testing
3
+ description: Reviews test coverage and test quality for the changed code
4
+ ---
5
+
6
+ Review the test changes in the diff.
7
+
8
+ 1. Coverage: are all new code paths exercised by at least one test?
9
+ 2. Assertions: do tests assert specific values, or do they just assert no exception?
10
+ 3. Isolation: do tests depend on external state (filesystem, network, other tests)?
11
+ 4. Names: do test names describe what they verify (not just "test_function_x")?
12
+ 5. Missing tests: list any new public functions or edge cases with no test at all.
13
+
14
+ Format: FILE — finding description
15
+ Output "TESTS LOOK GOOD" if no issues found.
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: verify_fixes
3
+ description: Verify that each phase 1 review finding was fully resolved
4
+ ---
5
+
6
+ You are running Phase 2 of a two-phase review. You will be given the phase 1
7
+ findings report and must determine whether each finding was addressed.
8
+
9
+ Run `git log --oneline main..HEAD` to list all commits, including any fix commits
10
+ added after phase 1. Run `git show <hash>` on any fix commit to see what changed.
11
+
12
+ For each finding from the phase 1 report, produce one line:
13
+ RESOLVED — the fix is present and correct
14
+ PARTIAL — something changed but the finding is not fully addressed
15
+ UNRESOLVED — no change addresses this finding
16
+
17
+ End with a summary line:
18
+ VERIFIED (N/N findings resolved)
19
+ or
20
+ STILL FAILING (N/M unresolved: <short list>)
@@ -0,0 +1,27 @@
1
+ {
2
+ "_comment": [
3
+ "Assisted mode reference. NOT loaded directly by Claude Code.",
4
+ "The wrapper writes this content to .claude/settings.local.json at session start",
5
+ "and deletes it on exit. settings.local.json is gitignored.",
6
+ "Deny rules removed — local git ops happen without prompts.",
7
+ "git push and git merge to main still require approval (no allow rule for them)."
8
+ ],
9
+ "permissions": {
10
+ "allow": [
11
+ "Bash(py -m pytest*)",
12
+ "Bash(py -m ruff*)",
13
+ "Bash(python*)",
14
+ "Bash(git status*)",
15
+ "Bash(git diff*)",
16
+ "Bash(git log*)",
17
+ "Bash(git add*)",
18
+ "Bash(git commit*)",
19
+ "Bash(git checkout*)",
20
+ "Bash(git branch*)",
21
+ "Bash(git stash*)",
22
+ "Read(*)",
23
+ "Edit(*)",
24
+ "Write(*)"
25
+ ]
26
+ }
27
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "_comment": [
3
+ "Supervised mode — the committed host default.",
4
+ "Use python scripts/learnx_dk.py --mode to select a different mode.",
5
+ "Container modes (container, yolo) ignore this file entirely."
6
+ ],
7
+ "permissions": {
8
+ "allow": [
9
+ "Bash(py -m pytest*)",
10
+ "Bash(py -m ruff check*)",
11
+ "Bash(py -m ruff format*)",
12
+ "Bash(git status*)",
13
+ "Bash(git diff*)",
14
+ "Bash(git log*)",
15
+ "Bash(git checkout main)",
16
+ "Bash(git checkout -b sandbox/*)",
17
+ "Bash(git branch*)"
18
+ ],
19
+ "deny": [
20
+ "Bash(git push*)",
21
+ "Bash(git merge*)",
22
+ "Bash(git reset*)",
23
+ "Bash(git branch -D*)"
24
+ ]
25
+ }
26
+ }
@@ -0,0 +1,19 @@
1
+ # Large generated directories — not needed in build context
2
+ audio/
3
+ __pycache__/
4
+ *.pyc
5
+ *.pyo
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+
9
+ # Version control
10
+ .git/
11
+ .gitignore
12
+
13
+ # Dev environment
14
+ .venv/
15
+ *.egg-info/
16
+
17
+ # OS noise
18
+ .DS_Store
19
+ Thumbs.db
@@ -0,0 +1,80 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["**"]
6
+ pull_request:
7
+ branches: ["main", "v*-*"]
8
+
9
+ jobs:
10
+ lint:
11
+ name: Lint
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.12"
19
+
20
+ - name: Install ruff
21
+ run: pip install "ruff>=0.4"
22
+
23
+ - name: Check style
24
+ run: ruff check tutor/
25
+
26
+ - name: Check formatting
27
+ run: ruff format --check tutor/
28
+
29
+ unit-tests:
30
+ name: Unit Tests
31
+ runs-on: ubuntu-latest
32
+ needs: lint
33
+ if: false # change to false to skip unit tests
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+
37
+ - uses: actions/setup-python@v5
38
+ with:
39
+ python-version: "3.12"
40
+
41
+ - name: Install dependencies
42
+ run: |
43
+ grep -v 'audioop-lts' tutor/requirements.txt > /tmp/requirements-filtered.txt
44
+ pip install -r /tmp/requirements-filtered.txt
45
+ pip install -r tutor/requirements-dev.txt
46
+
47
+ - name: Run unit tests
48
+ run: python -m pytest tutor/tests/ --ignore=tutor/tests/e2e/ -m "not slow" -v
49
+
50
+ e2e-tests:
51
+ name: E2E Smoke Tests
52
+ runs-on: ubuntu-latest
53
+ needs: unit-tests
54
+ if: false # change to false to skip e2e tests
55
+ steps:
56
+ - uses: actions/checkout@v4
57
+
58
+ - uses: actions/setup-python@v5
59
+ with:
60
+ python-version: "3.12"
61
+
62
+ - name: Install system dependencies
63
+ run: |
64
+ sudo apt-get update
65
+ sudo apt-get install -y ffmpeg
66
+
67
+ - name: Install Python dependencies
68
+ run: |
69
+ grep -v 'audioop-lts' tutor/requirements.txt > /tmp/requirements-filtered.txt
70
+ pip install -r /tmp/requirements-filtered.txt
71
+ pip install -r tutor/requirements-dev.txt
72
+
73
+ - name: Install Playwright browsers
74
+ run: python -m playwright install chromium --with-deps
75
+
76
+ - name: Run E2E smoke tests
77
+ run: python -m pytest tutor/tests/e2e/ -v
78
+ env:
79
+ # LLM calls are mocked in conftest.py — no real key needed
80
+ ANTHROPIC_API_KEY: "test-key-not-used"
@@ -0,0 +1,71 @@
1
+ # Folders excluded from this repo
2
+ week1/
3
+ week2/
4
+ systemDesign/
5
+
6
+ # Generated output directories (root-level only — not tutor/audio/ source)
7
+ /audio/
8
+ /video/
9
+
10
+ # Generated audio output
11
+ *.mp3
12
+ *.script.txt
13
+ *.units.json
14
+ *.session.json
15
+ *.chunks.json
16
+ *.meta.json
17
+ *.visuals.json
18
+ tutorial_units/
19
+
20
+ # TTS temp files
21
+ .tts_tmp/
22
+
23
+ # LLM cache (regenerated on demand)
24
+ .tutor_cache/
25
+
26
+ # Secrets — never commit
27
+ tutor/.env
28
+
29
+ # Python
30
+ __pycache__/
31
+ *.py[cod]
32
+ *.pyo
33
+ .mypy_cache/
34
+ .ruff_cache/
35
+
36
+ # Virtual environments
37
+ .venv/
38
+ venv/
39
+ env/
40
+
41
+ # Logs
42
+ tutor.log
43
+ *.log
44
+
45
+ # Editor / OS
46
+ .vscode/
47
+ .idea/
48
+ *.swp
49
+ .DS_Store
50
+ Thumbs.db
51
+
52
+ # Reference repos — cloned locally for reading, not part of this project
53
+ ralphex/
54
+
55
+ # Docker build artifacts
56
+ .docker/
57
+ *.dockerignore.bak
58
+
59
+ # Dev workflow scripts output
60
+ scripts/__pycache__/
61
+
62
+ # Assisted mode runtime settings override — written and deleted by learnx_dk.py
63
+ .claude/settings.local.json
64
+
65
+ # sandbox/ prototype scripts — tracked selectively; outputs are not
66
+ sandbox/*.log
67
+ sandbox/*.json
68
+ sandbox/*.mp3
69
+
70
+ problems/
71
+ week3/
@@ -0,0 +1,7 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.4.10
4
+ hooks:
5
+ - id: ruff
6
+ args: ["--fix"]
7
+ - id: ruff-format
@@ -0,0 +1,144 @@
1
+ # LearnX CLI — Product Context
2
+
3
+ > For DevLoop workflow instructions (spec days, implementation loop, commands) see **DEVLOOP.md**.
4
+
5
+ ---
6
+
7
+ ## What This Project Is
8
+
9
+ LearnX is a Python CLI that converts a Markdown document into an audio-first tutorial
10
+ with optional video. The pipeline is:
11
+
12
+ ```
13
+ Document → chunk → LLM summarise → LLM curriculum → LLM dialogue
14
+ → TTS render → assemble audio (+ timing.json)
15
+ → (optional) LLM visual plan → render slides → subtitle → mp4
16
+ ```
17
+
18
+ Entry point: `tutor/__main__.py`. CLI commands live in `tutor/cli/`.
19
+ Primary platform: Windows / PowerShell. Python 3.12+.
20
+
21
+ ---
22
+
23
+ ## Two Layers in This Repo
24
+
25
+ ```
26
+ tutor/ LearnX CLI — the product. Audio tutorial generator.
27
+ scripts/ DevLoop — the build system. Runs Claude in Docker, manages review pipeline.
28
+ ```
29
+
30
+ These are independent concerns that share one repository. `scripts/` is never imported
31
+ by `tutor/`. A user of LearnX CLI never touches `scripts/`.
32
+
33
+ ---
34
+
35
+ ## Product Layout
36
+
37
+ ```
38
+ tutor/ the Python package
39
+ __main__.py CLI entry point
40
+ models.py all dataclasses — start here to understand data shapes
41
+ constants.py silence gap constants, limits
42
+ config.py LLM provider / model config
43
+ audio/ audio pipeline (audio_builder.py, tts_renderer.py)
44
+ generation/ LLM pipeline (curriculum, dialogue, narrator, visual_planner)
45
+ ingestion/ document processing (chunker, doc_analyzer)
46
+ visual/ video pipeline (beat_timer, slide_compositor, subtitle_writer…)
47
+ player/ interactive playback
48
+ cli/ CLI commands and shell
49
+ tests/ pytest unit suite — mirrors tutor/ structure
50
+ tests/e2e/ E2E smoke tests — run real pipeline, check output quality
51
+ infra/ LLM client wrapper
52
+
53
+ specs/ versioned spec files — source of truth for all tutor/ code
54
+ v0/ … v3/ completed versions (regression reference)
55
+ v4-workflow/ DevLoop upgrade specs (archived)
56
+ v5/ … v11/ active versions
57
+
58
+ plan/ version-level design and architecture docs
59
+ v0_plan.md … v11_plan.md
60
+ v4_update_plan.md DevLoop v4 upgrade rationale
61
+ v4_architecture.md DevLoop component map
62
+
63
+ fixes/ post-mortem notes (fix001.md … )
64
+ read these before starting — they contain env/API gotchas
65
+
66
+ dev_setup/ developer process documentation
67
+ spec-driven_plan.md what SDD means and how to write specs
68
+ context_hygiene_plan.md how to manage session context
69
+ sandbox_plan.md git branch + test isolation strategy
70
+ autonomy_plan.md how to run the implement→test→fix loop (Level 1–4)
71
+ handoff_template.md copy-paste prompt for starting each spec day
72
+ container_plan.md how to run Claude inside Docker (Level 4 workflow)
73
+
74
+ scripts/ DevLoop tooling (NOT imported by tutor/)
75
+ devloop.py launcher — runs Claude inside Docker
76
+ run_review.py triggers 5-agent code + product review
77
+ dk/ devloop submodules (config, docker, dashboard, runners…)
78
+ tests/ pytest tests for scripts/
79
+
80
+ sandbox/ throwaway prototype scripts — NOT imported by tutor/
81
+
82
+ .claude/
83
+ settings.json allow list only (deny rules removed — Docker is the sandbox)
84
+ agents/ review + product check agent definitions
85
+ ```
86
+
87
+ ---
88
+
89
+ ## Code Quality Rules
90
+
91
+ These rules apply to every file touched during a spec day or refactoring session.
92
+
93
+ ```
94
+ MAX 400 LOC PER FILE — if a file exceeds 400 lines, split it before committing.
95
+ Extract by cohesion: group things that change together and depend on each other.
96
+ Keep the public interface in the original file; move implementation to submodules.
97
+
98
+ CLEAN CODE
99
+ Functions do one thing. Name them for what they do, not how.
100
+ No functions longer than 40 lines. No nesting deeper than 3 levels.
101
+ No magic numbers — use named constants.
102
+ No dead code, no commented-out blocks, no TODO left in committed code.
103
+ Never hardcode a parameter value inside a function body when the caller
104
+ passed that value as an argument — always forward the parameter.
105
+
106
+ MAINTAINABILITY
107
+ Every module has a single clear responsibility (SRP).
108
+ Avoid deep coupling: pass what a function needs, don't let it reach into globals.
109
+ Prefer explicit imports over star imports.
110
+ Update imports everywhere when you move code.
111
+
112
+ SCALABILITY
113
+ New behaviour should be addable without modifying existing logic (OCP).
114
+ Favour composition over inheritance for extending behaviour.
115
+ Config goes in devloop.toml / llm_config.toml, not hardcoded in source.
116
+
117
+ REUSABILITY
118
+ Pure functions (no side effects) are easier to test and reuse — prefer them.
119
+ Separate I/O (file reads, subprocess calls, network) from logic.
120
+ If two files share a helper, extract it to a shared module; never duplicate.
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Where to Find Things
126
+
127
+ | Question | Where to look |
128
+ |---|---|
129
+ | What does this feature do? | `specs/vN/dayN.md` |
130
+ | Why was this approach chosen? | `plan/vN_plan.md` |
131
+ | Why does this edge case exist? | `fixes/fix0NN.md` |
132
+ | What dataclasses exist? | `tutor/models.py` |
133
+ | What silence constants are used? | `tutor/constants.py` |
134
+ | How is the LLM called? | `tutor/infra/llm.py` |
135
+ | How to start a new spec day? | `dev_setup/handoff_template.md` |
136
+
137
+ ---
138
+
139
+ ## Read Before Starting Any v3 Day
140
+
141
+ 1. `fixes/fix001.md` — ffmpeg is not always on PATH on Windows; pydub needs the binary patched in at startup.
142
+ 2. `fixes/fix013.md` — timing inflation root cause: `compute_slide_timings()` was using estimated beat offsets instead of actual MP3 durations.
143
+ 3. `fixes/fix009.md` — per-unit loudnorm breaks audio duration with image-based concat video; volume boost is applied at encode time instead.
144
+ 4. `plan/v3_plan.md` — the architectural rationale for the whole v3 approach.