codemble 0.5.2__tar.gz → 0.6.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 (239) hide show
  1. {codemble-0.5.2 → codemble-0.6.0}/.gitignore +1 -0
  2. {codemble-0.5.2 → codemble-0.6.0}/CHANGELOG.md +82 -0
  3. {codemble-0.5.2 → codemble-0.6.0}/CLAUDE.md +84 -9
  4. codemble-0.6.0/CONTEXT.md +28 -0
  5. {codemble-0.5.2 → codemble-0.6.0}/PKG-INFO +9 -2
  6. {codemble-0.5.2 → codemble-0.6.0}/README.md +7 -0
  7. {codemble-0.5.2 → codemble-0.6.0}/codemble/__init__.py +1 -1
  8. {codemble-0.5.2 → codemble-0.6.0}/codemble/adapters/base.py +8 -1
  9. {codemble-0.5.2 → codemble-0.6.0}/codemble/graph/layout.py +158 -32
  10. {codemble-0.5.2 → codemble-0.6.0}/codemble/graph/mapview.py +196 -8
  11. {codemble-0.5.2 → codemble-0.6.0}/codemble/server/app.py +69 -241
  12. codemble-0.6.0/codemble/server/project_activation.py +212 -0
  13. codemble-0.6.0/codemble/server/project_selection.py +117 -0
  14. codemble-0.5.2/codemble/web_dist/assets/index-DTjcBtZj.js → codemble-0.6.0/codemble/web_dist/assets/index-BWorxSL3.js +1 -1
  15. codemble-0.6.0/codemble/web_dist/assets/index-wY0ZuS7b.css +1 -0
  16. {codemble-0.5.2 → codemble-0.6.0}/codemble/web_dist/index.html +2 -2
  17. codemble-0.6.0/docs/plans/2026-07-20-oss-inspired-map-and-constellations.md +417 -0
  18. codemble-0.6.0/docs/releases/v0.5.3.md +50 -0
  19. codemble-0.6.0/docs/research/2026-07-20-opensource-graph-architecture-inspiration.md +192 -0
  20. codemble-0.6.0/docs/superpowers/specs/2026-07-21-navigation-design-clarity-design.md +183 -0
  21. {codemble-0.5.2 → codemble-0.6.0}/docs-site/package.json +1 -1
  22. {codemble-0.5.2 → codemble-0.6.0}/pyproject.toml +7 -2
  23. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/check_suites.json +36 -36
  24. codemble-0.6.0/tests/test_dependency_compatibility.py +43 -0
  25. codemble-0.6.0/tests/test_graph_finalization.py +277 -0
  26. {codemble-0.5.2 → codemble-0.6.0}/tests/test_mapview.py +190 -3
  27. codemble-0.6.0/tests/test_project_activation.py +68 -0
  28. codemble-0.6.0/tests/test_project_selection.py +71 -0
  29. {codemble-0.5.2 → codemble-0.6.0}/tests/test_python_ast.py +2 -2
  30. {codemble-0.5.2 → codemble-0.6.0}/tests/test_server.py +5 -1
  31. {codemble-0.5.2 → codemble-0.6.0}/web/package-lock.json +2 -2
  32. {codemble-0.5.2 → codemble-0.6.0}/web/package.json +3 -2
  33. codemble-0.6.0/web/scripts/benchmark_learner_projection.mjs +114 -0
  34. {codemble-0.5.2 → codemble-0.6.0}/web/scripts/check_graph_data.mjs +130 -0
  35. codemble-0.6.0/web/scripts/check_learner_projection.mjs +165 -0
  36. {codemble-0.5.2 → codemble-0.6.0}/web/scripts/check_learner_session.mjs +104 -0
  37. codemble-0.6.0/web/scripts/check_name_atlas.mjs +75 -0
  38. codemble-0.6.0/web/scripts/check_project_mapping.mjs +189 -0
  39. {codemble-0.5.2 → codemble-0.6.0}/web/src/App.jsx +295 -69
  40. {codemble-0.5.2 → codemble-0.6.0}/web/src/GalaxyCanvas.jsx +133 -20
  41. {codemble-0.5.2 → codemble-0.6.0}/web/src/GuidanceLayer.jsx +4 -4
  42. {codemble-0.5.2 → codemble-0.6.0}/web/src/MapView.jsx +201 -12
  43. {codemble-0.5.2 → codemble-0.6.0}/web/src/galaxyMaterials.js +86 -0
  44. {codemble-0.5.2 → codemble-0.6.0}/web/src/graphData.js +189 -23
  45. codemble-0.6.0/web/src/learnerProjection.js +212 -0
  46. {codemble-0.5.2 → codemble-0.6.0}/web/src/learnerSession.js +93 -372
  47. codemble-0.6.0/web/src/nameAtlas.js +177 -0
  48. codemble-0.6.0/web/src/projectMapping.js +344 -0
  49. {codemble-0.5.2 → codemble-0.6.0}/web/src/styles.css +422 -9
  50. {codemble-0.5.2 → codemble-0.6.0}/web/src/tokens.css +13 -0
  51. codemble-0.5.2/codemble/web_dist/assets/index-Dbk76SAB.css +0 -1
  52. codemble-0.5.2/tests/test_graph_finalization.py +0 -121
  53. {codemble-0.5.2 → codemble-0.6.0}/.env.example +0 -0
  54. {codemble-0.5.2 → codemble-0.6.0}/.gitattributes +0 -0
  55. {codemble-0.5.2 → codemble-0.6.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  56. {codemble-0.5.2 → codemble-0.6.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  57. {codemble-0.5.2 → codemble-0.6.0}/.github/ISSUE_TEMPLATE/early_tester.yml +0 -0
  58. {codemble-0.5.2 → codemble-0.6.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  59. {codemble-0.5.2 → codemble-0.6.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  60. {codemble-0.5.2 → codemble-0.6.0}/.github/dependabot.yml +0 -0
  61. {codemble-0.5.2 → codemble-0.6.0}/.github/workflows/ci.yml +0 -0
  62. {codemble-0.5.2 → codemble-0.6.0}/.github/workflows/pages.yml +0 -0
  63. {codemble-0.5.2 → codemble-0.6.0}/.github/workflows/publish-pypi.yml +0 -0
  64. {codemble-0.5.2 → codemble-0.6.0}/AGENTS.md +0 -0
  65. {codemble-0.5.2 → codemble-0.6.0}/CODE_OF_CONDUCT.md +0 -0
  66. {codemble-0.5.2 → codemble-0.6.0}/CONTRIBUTING.md +0 -0
  67. {codemble-0.5.2 → codemble-0.6.0}/LICENSE +0 -0
  68. {codemble-0.5.2 → codemble-0.6.0}/NOTICE +0 -0
  69. {codemble-0.5.2 → codemble-0.6.0}/SECURITY.md +0 -0
  70. {codemble-0.5.2 → codemble-0.6.0}/TESTING.md +0 -0
  71. {codemble-0.5.2 → codemble-0.6.0}/assets/demo.gif +0 -0
  72. {codemble-0.5.2 → codemble-0.6.0}/codemble/adapters/__init__.py +0 -0
  73. {codemble-0.5.2 → codemble-0.6.0}/codemble/adapters/discovery.py +0 -0
  74. {codemble-0.5.2 → codemble-0.6.0}/codemble/adapters/parse_progress.py +0 -0
  75. {codemble-0.5.2 → codemble-0.6.0}/codemble/adapters/project.py +0 -0
  76. {codemble-0.5.2 → codemble-0.6.0}/codemble/adapters/python_ast.py +0 -0
  77. {codemble-0.5.2 → codemble-0.6.0}/codemble/adapters/typescript_tree_sitter.py +0 -0
  78. {codemble-0.5.2 → codemble-0.6.0}/codemble/checks/__init__.py +0 -0
  79. {codemble-0.5.2 → codemble-0.6.0}/codemble/checks/service.py +0 -0
  80. {codemble-0.5.2 → codemble-0.6.0}/codemble/cli.py +0 -0
  81. {codemble-0.5.2 → codemble-0.6.0}/codemble/graph/__init__.py +0 -0
  82. {codemble-0.5.2 → codemble-0.6.0}/codemble/graph/finalize.py +0 -0
  83. {codemble-0.5.2 → codemble-0.6.0}/codemble/lens/__init__.py +0 -0
  84. {codemble-0.5.2 → codemble-0.6.0}/codemble/lens/javascript_typescript.py +0 -0
  85. {codemble-0.5.2 → codemble-0.6.0}/codemble/lens/python.py +0 -0
  86. {codemble-0.5.2 → codemble-0.6.0}/codemble/llm/__init__.py +0 -0
  87. {codemble-0.5.2 → codemble-0.6.0}/codemble/llm/local_status.py +0 -0
  88. {codemble-0.5.2 → codemble-0.6.0}/codemble/llm/providers.py +0 -0
  89. {codemble-0.5.2 → codemble-0.6.0}/codemble/llm/structural.py +0 -0
  90. {codemble-0.5.2 → codemble-0.6.0}/codemble/llm/study.py +0 -0
  91. {codemble-0.5.2 → codemble-0.6.0}/codemble/paths.py +0 -0
  92. {codemble-0.5.2 → codemble-0.6.0}/codemble/progress/__init__.py +0 -0
  93. {codemble-0.5.2 → codemble-0.6.0}/codemble/progress/store.py +0 -0
  94. {codemble-0.5.2 → codemble-0.6.0}/codemble/server/__init__.py +0 -0
  95. {codemble-0.5.2 → codemble-0.6.0}/codemble/server/parse_job.py +0 -0
  96. {codemble-0.5.2 → codemble-0.6.0}/codemble/server/runtime.py +0 -0
  97. {codemble-0.5.2 → codemble-0.6.0}/codemble/web_dist/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  98. {codemble-0.5.2 → codemble-0.6.0}/codemble/web_dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  99. {codemble-0.5.2 → codemble-0.6.0}/codemble/web_dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  100. {codemble-0.5.2 → codemble-0.6.0}/codemble/web_dist/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  101. {codemble-0.5.2 → codemble-0.6.0}/codemble/web_dist/assets/shippori-mincho-latin-500-normal-C-QwvIb3.woff +0 -0
  102. {codemble-0.5.2 → codemble-0.6.0}/codemble/web_dist/assets/shippori-mincho-latin-500-normal-XI1O8euf.woff2 +0 -0
  103. {codemble-0.5.2 → codemble-0.6.0}/codemble/web_dist/assets/shippori-mincho-latin-700-normal-CkoCYOiI.woff +0 -0
  104. {codemble-0.5.2 → codemble-0.6.0}/codemble/web_dist/assets/shippori-mincho-latin-700-normal-DHcmzUO5.woff2 +0 -0
  105. {codemble-0.5.2 → codemble-0.6.0}/codemble/web_dist/assets/zen-kaku-gothic-new-latin-400-normal-BEdayliK.woff2 +0 -0
  106. {codemble-0.5.2 → codemble-0.6.0}/codemble/web_dist/assets/zen-kaku-gothic-new-latin-400-normal-CPSmNJAU.woff +0 -0
  107. {codemble-0.5.2 → codemble-0.6.0}/docs/adr/README.md +0 -0
  108. {codemble-0.5.2 → codemble-0.6.0}/docs/plans/2026-07-19-audience-modes-and-local-narration-design.md +0 -0
  109. {codemble-0.5.2 → codemble-0.6.0}/docs/plans/2026-07-19-audience-modes-backend-plan.md +0 -0
  110. {codemble-0.5.2 → codemble-0.6.0}/docs/plans/2026-07-19-audience-modes-frontend-plan.md +0 -0
  111. {codemble-0.5.2 → codemble-0.6.0}/docs/plans/2026-07-19-install-ux-folder-picker-design.md +0 -0
  112. {codemble-0.5.2 → codemble-0.6.0}/docs/plans/2026-07-19-install-ux-folder-picker-plan.md +0 -0
  113. {codemble-0.5.2 → codemble-0.6.0}/docs/plans/README.md +0 -0
  114. {codemble-0.5.2 → codemble-0.6.0}/docs/plans/phase-1.md +0 -0
  115. {codemble-0.5.2 → codemble-0.6.0}/docs/releases/checklist.md +0 -0
  116. {codemble-0.5.2 → codemble-0.6.0}/docs/releases/v0.1.0.md +0 -0
  117. {codemble-0.5.2 → codemble-0.6.0}/docs/releases/v0.2.0.md +0 -0
  118. {codemble-0.5.2 → codemble-0.6.0}/docs/releases/v0.4.0.md +0 -0
  119. {codemble-0.5.2 → codemble-0.6.0}/docs/releases/v0.5.0.md +0 -0
  120. {codemble-0.5.2 → codemble-0.6.0}/docs/releases/v0.5.1.md +0 -0
  121. {codemble-0.5.2 → codemble-0.6.0}/docs/releases/v0.5.2.md +0 -0
  122. {codemble-0.5.2 → codemble-0.6.0}/docs/research/README.md +0 -0
  123. {codemble-0.5.2 → codemble-0.6.0}/docs/superpowers/plans/2026-07-19-galaxy-ux-phase-a.md +0 -0
  124. {codemble-0.5.2 → codemble-0.6.0}/docs/superpowers/plans/2026-07-19-galaxy-ux-phase-b.md +0 -0
  125. {codemble-0.5.2 → codemble-0.6.0}/docs/superpowers/plans/2026-07-19-galaxy-ux-phase-c.md +0 -0
  126. {codemble-0.5.2 → codemble-0.6.0}/docs/superpowers/plans/2026-07-19-galaxy-ux-shared-contract.md +0 -0
  127. {codemble-0.5.2 → codemble-0.6.0}/docs/superpowers/specs/2026-07-19-galaxy-ux-overhaul-design.md +0 -0
  128. {codemble-0.5.2 → codemble-0.6.0}/docs-site/.gitignore +0 -0
  129. {codemble-0.5.2 → codemble-0.6.0}/docs-site/astro.config.mjs +0 -0
  130. {codemble-0.5.2 → codemble-0.6.0}/docs-site/design.md +0 -0
  131. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/brand/favicon-16.png +0 -0
  132. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/brand/favicon-32.png +0 -0
  133. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/brand/icon-180.png +0 -0
  134. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/brand/icon-192.png +0 -0
  135. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/brand/icon-512.png +0 -0
  136. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/brand/icon.svg +0 -0
  137. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/brand/icons/asterism.svg +0 -0
  138. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/brand/icons/install.svg +0 -0
  139. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/brand/mark-animated.svg +0 -0
  140. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/brand/mark.svg +0 -0
  141. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/brand/plates/hero-chart.svg +0 -0
  142. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/brand/plates/hero-field.svg +0 -0
  143. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/brand/plates/hero-gold.svg +0 -0
  144. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/brand/plates/kasumi-rule.svg +0 -0
  145. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/brand/plates/plate-galaxy.svg +0 -0
  146. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/brand/plates/plate-study.svg +0 -0
  147. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/brand/plates/plate-system.svg +0 -0
  148. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/brand/plates/seal.svg +0 -0
  149. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/favicon.svg +0 -0
  150. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/shots/easy-mode.png +0 -0
  151. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/shots/galaxy-lit.png +0 -0
  152. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/shots/galaxy.png +0 -0
  153. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/shots/loading.png +0 -0
  154. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/shots/map-architecture.png +0 -0
  155. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/shots/map-workflow.png +0 -0
  156. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/shots/study-panel.png +0 -0
  157. {codemble-0.5.2 → codemble-0.6.0}/docs-site/public/shots/system.png +0 -0
  158. {codemble-0.5.2 → codemble-0.6.0}/docs-site/scripts/build-plates.mjs +0 -0
  159. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/assets/codemble-mark-dark.svg +0 -0
  160. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/assets/codemble-mark-light.svg +0 -0
  161. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/components/Search.astro +0 -0
  162. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/components/Tatebanko.astro +0 -0
  163. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/architecture.md +0 -0
  164. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/build-from-source.md +0 -0
  165. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/checks-and-lighting.md +0 -0
  166. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/contributing.md +0 -0
  167. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/correctness.md +0 -0
  168. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/early-testing.md +0 -0
  169. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/installation.md +0 -0
  170. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/introduction.md +0 -0
  171. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/progress/m1-parser.md +0 -0
  172. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/progress/m10-polyglot-release.md +0 -0
  173. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/progress/m12-galaxy-look.md +0 -0
  174. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/progress/m13-scale.md +0 -0
  175. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/progress/m2-galaxy.md +0 -0
  176. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/progress/m3-study.md +0 -0
  177. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/progress/m4-lens.md +0 -0
  178. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/progress/m5-checks.md +0 -0
  179. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/progress/m6-release.md +0 -0
  180. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/progress/m8-typescript.md +0 -0
  181. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/progress/m9-typescript-lens.md +0 -0
  182. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/quickstart.md +0 -0
  183. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/roadmap.md +0 -0
  184. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/star-chart.md +0 -0
  185. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/study-panel.md +0 -0
  186. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content/docs/the-galaxy.md +0 -0
  187. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/content.config.ts +0 -0
  188. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/pages/index.astro +0 -0
  189. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/styles/custom.css +0 -0
  190. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/styles/landing.css +0 -0
  191. {codemble-0.5.2 → codemble-0.6.0}/docs-site/src/styles/tokens.css +0 -0
  192. {codemble-0.5.2 → codemble-0.6.0}/scripts/dev.sh +0 -0
  193. {codemble-0.5.2 → codemble-0.6.0}/scripts/record_demo.sh +0 -0
  194. {codemble-0.5.2 → codemble-0.6.0}/tests/conftest.py +0 -0
  195. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/concepts_sample.py +0 -0
  196. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/impact/alpha.py +0 -0
  197. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/impact/beta.py +0 -0
  198. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/impact/gamma.py +0 -0
  199. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/impact/helpers.py +0 -0
  200. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/impact/noisy.py +0 -0
  201. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/polyglot/python_worker.py +0 -0
  202. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/polyglot/src/broken.ts +0 -0
  203. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/polyglot/src/legacy.js +0 -0
  204. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/polyglot/src/local.js +0 -0
  205. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/polyglot/src/main.ts +0 -0
  206. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/polyglot/src/reexport.ts +0 -0
  207. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/polyglot/src/util.ts +0 -0
  208. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/polyglot/src/widget.tsx +0 -0
  209. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/repeated_calls.py +0 -0
  210. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/sampleproj/.gitignore +0 -0
  211. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/sampleproj/ambiguous.py +0 -0
  212. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/sampleproj/api.py +0 -0
  213. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/sampleproj/app.py +0 -0
  214. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/sampleproj/broken.py +0 -0
  215. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/sampleproj/cli.py +0 -0
  216. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/sampleproj/pkg/__init__.py +0 -0
  217. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/sampleproj/pkg/helpers.py +0 -0
  218. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/sampleproj/pkg/service.py +0 -0
  219. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/sampleproj/pkg/util.py +0 -0
  220. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/sampleproj/runner/__main__.py +0 -0
  221. {codemble-0.5.2 → codemble-0.6.0}/tests/fixtures/sampleproj/shared.py +0 -0
  222. {codemble-0.5.2 → codemble-0.6.0}/tests/test_checks.py +0 -0
  223. {codemble-0.5.2 → codemble-0.6.0}/tests/test_parse_job.py +0 -0
  224. {codemble-0.5.2 → codemble-0.6.0}/tests/test_parse_progress.py +0 -0
  225. {codemble-0.5.2 → codemble-0.6.0}/tests/test_progress.py +0 -0
  226. {codemble-0.5.2 → codemble-0.6.0}/tests/test_project_parser.py +0 -0
  227. {codemble-0.5.2 → codemble-0.6.0}/tests/test_providers.py +0 -0
  228. {codemble-0.5.2 → codemble-0.6.0}/tests/test_smoke.py +0 -0
  229. {codemble-0.5.2 → codemble-0.6.0}/tests/test_structural.py +0 -0
  230. {codemble-0.5.2 → codemble-0.6.0}/tests/test_study.py +0 -0
  231. {codemble-0.5.2 → codemble-0.6.0}/tests/test_typescript_tree_sitter.py +0 -0
  232. {codemble-0.5.2 → codemble-0.6.0}/web/README.md +0 -0
  233. {codemble-0.5.2 → codemble-0.6.0}/web/index.html +0 -0
  234. {codemble-0.5.2 → codemble-0.6.0}/web/scripts/record_demo.mjs +0 -0
  235. {codemble-0.5.2 → codemble-0.6.0}/web/src/ModeControl.jsx +0 -0
  236. {codemble-0.5.2 → codemble-0.6.0}/web/src/StudyPanel.jsx +0 -0
  237. {codemble-0.5.2 → codemble-0.6.0}/web/src/galaxyEffects.js +0 -0
  238. {codemble-0.5.2 → codemble-0.6.0}/web/src/main.jsx +0 -0
  239. {codemble-0.5.2 → codemble-0.6.0}/web/vite.config.js +0 -0
@@ -34,3 +34,4 @@ graphify-out/
34
34
 
35
35
  # Subagent-driven-development scratch coordination (not product truth)
36
36
  .superpowers/
37
+ .remember
@@ -5,6 +5,88 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · Versioning:
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.6.0] - 2026-07-21
9
+
10
+ ### Added
11
+ - **You can move the camera.** Dragging now orbits around whatever you are
12
+ looking at, and the wheel zooms. Panning stays off and both rotation and zoom
13
+ are clamped per level, so the thing you are studying can never leave the
14
+ screen — "you cannot get lost" still holds. Changing level is now click,
15
+ Enter, Escape, or the breadcrumb, because one gesture cannot mean both zoom
16
+ and travel.
17
+ - **Systems have names.** The most important stars carry their filename on the
18
+ canvas — Home first, then modules you have proved, then the ones most of your
19
+ project calls — with more names appearing as you zoom in and none allowed to
20
+ overlap another.
21
+ - **Find any module by name.** `⌘K` / `Ctrl-K` opens a filter-as-you-type jump
22
+ list, and a new Modules rail lists every file grouped by the import
23
+ communities the galaxy already places together. Both reach every module,
24
+ including ones the sky has not charted yet.
25
+
26
+ ### Changed
27
+ - Project selection, project activation, project mapping, the galaxy Name
28
+ Atlas, and the Learner Projection now each sit behind one focused module
29
+ interface. The HTTP API, parser graph, checks, persistence, and learner-visible
30
+ interactions are unchanged; focused contract suites pin each new seam.
31
+ - Learner projections are now dependency-scoped and indexed instead of being
32
+ recomputed on every session commit. A 1,000-node hover benchmark dropped from
33
+ about 0.331 ms to 0.001 ms per commit, and hover-only commits preserve the
34
+ identity of every unaffected graph, map, chart, reveal, hint, and module-index
35
+ projection so the 3D renderer does not rebuild stable scene data.
36
+ - **The galaxy no longer draws your whole project at once.** It opens on Home
37
+ and everything within two import routes of it; proving a module reveals what
38
+ it connects to, so the map is uncovered by understanding. Modules you have not
39
+ reached are still drawn — faint, unnamed, and without their routes — so the
40
+ project's real size is never misreported, and they stay clickable. A
41
+ **Show all** toggle draws everything whenever you want it. On a 90-system
42
+ project this is 22 systems and their routes instead of 90 and theirs.
43
+ - **The canvas got its stage back.** The display-size heading that covered the
44
+ left half of the view is now one line of text (`90 systems · 22 charted`), the
45
+ module path at system level lives in the breadcrumb instead of wrapping across
46
+ three lines, and the twelve-row legend is behind a **Key** button.
47
+ - **Map boxes say which module they are.** A box is a fixed width, and it used
48
+ to truncate the dotted module id — so `codemble.server.app` and
49
+ `codemble.server.runtime` both read `codemble.server…`, two different modules
50
+ showing identical text. Boxes are now named by the tail of their real path
51
+ (`server/app.py`, `server/runtime.py`); the full identifier is still in the
52
+ tooltip and read out to screen readers.
53
+ - **You can zoom and pan the Map.** The architecture diagram is far taller than
54
+ the window — on this project, nine import layers of which about four were
55
+ visible. Zoom in and out, drag empty space to pan, or press **Fit** to see the
56
+ whole shape at once. Every coordinate is still the one the backend computed;
57
+ zoom only changes how large it is drawn.
58
+ - The 2D Architecture map is now a deterministic layered diagram rather than
59
+ a straight-line grid: four barycenter sweeps reduce import-edge crossings,
60
+ backend-assigned ports keep fan-out distinct, weighted arrowed paths expose
61
+ direction, and cycles or long routes travel around the diagram flank. Possible
62
+ relationships remain dashed, and React still consumes graph-owned geometry.
63
+ - Galaxy systems now form deterministic constellations from parser-proven
64
+ import communities instead of scattering solely by identifier hash. The
65
+ additive `community` region field is exposed in graph schema 5 for later
66
+ renderers, while progress, Home, and system-internal orbital layout stay
67
+ unchanged.
68
+
69
+ ## [0.5.3] - 2026-07-21
70
+
71
+ ### Fixed
72
+ - **A fresh install crashed on every JavaScript/TypeScript project.** The
73
+ dependency range allowed `tree-sitter` 0.26.0, which is ABI-incompatible with
74
+ the newest published grammar wheels (`tree-sitter-javascript` 0.25.0,
75
+ `tree-sitter-typescript` 0.23.2 — the only ones that exist). Partway through a
76
+ real project the parser died with a SIGSEGV inside `node_get_named_children`,
77
+ taking the whole process down with it: `codemble <path>` exited with no
78
+ traceback, and the app's local server vanished mid-parse, so the loading
79
+ screen reported "Lost contact with the local server" and stopped there.
80
+ Bisected on one machine, one project, one Python: 0.26.0 crashes every run,
81
+ 0.25.2 is clean. The core is now pinned `<0.26`.
82
+ An install that already had 0.25.x kept working, which is why this stayed
83
+ invisible from a developer checkout — it only reached people installing
84
+ fresh, including through the documented `uvx codemble` path.
85
+ - A dependency guard now asserts the resolved `tree-sitter` version stays below
86
+ the segfaulting release. A behavioural test cannot catch this: a small snippet
87
+ parses and walks fine on 0.26.0, and every fixture in the suite is small,
88
+ which is exactly how it reached a release.
89
+
8
90
  ## [0.5.2] - 2026-07-20
9
91
 
10
92
  ### Fixed
@@ -167,13 +167,22 @@ Polish, then the coordinated launch (Show HN / X; lit-galaxy GIF as hero).
167
167
 
168
168
  ## Current State **[AGENT-MAINTAINED]**
169
169
 
170
- **Current milestone: Phase 1 tester evidence** · Last updated: 2026-07-20 ·
171
- Session note: architecture-deepening maintenance is complete after the verified
172
- v0.2.0 release; all four report recommendations are merged in phases while issue
173
- #13 remains open for human tester evidence. The public site was then redesigned
174
- to the Formal Edo palette and Edo star-atlas genre, with an expanding Pagefind
175
- search shared by the landing and docs; no parser, graph, checks, persistence, or
176
- app behaviour was touched. A tester-run rehearsal of the shipped loop then
170
+ **Current milestone: Phase 1 tester evidence** · Last updated: 2026-07-21 ·
171
+ Session note: the v0.6.0 architecture-depth pass is complete in five
172
+ behavior-preserving waves: project selection owns the home-jailed filesystem
173
+ policy; project activation atomically owns parse-to-live binding and graph/map
174
+ caches; project mapping owns picker attempts, polling, retry, outage, stale
175
+ responses, and release; the Name Atlas owns deterministic plate placement; and
176
+ the indexed Learner Projection reuses every unaffected derived view. The
177
+ 1,000-node hover benchmark moved from ~0.331 ms to ~0.001 ms per commit, while
178
+ the HTTP, graph, check, persistence, and UI contracts stayed fixed. The current
179
+ milestone does not advance: issue #13 still requires human tester evidence.
180
+ Earlier architecture-deepening maintenance completed after the verified v0.2.0
181
+ release; all four report recommendations merged in phases. The public site was
182
+ then redesigned to the Formal Edo palette and Edo star-atlas genre, with an
183
+ expanding Pagefind search shared by the landing and docs; no parser, graph,
184
+ checks, persistence, or app behaviour was touched. A tester-run rehearsal of
185
+ the shipped loop then
177
186
  verified Home calibration, study source, checks, illumination, and restart
178
187
  persistence end to end, and found one real defect: multi-answer checks with four
179
188
  or more answers offered no wrong option, so select-all lit a region without
@@ -230,7 +239,42 @@ rebind a released project. Phase C plus that gap-fix wave shipped as **v0.5.0**;
230
239
  the parse work collided with an independent implementation of the same three
231
240
  foundational commits on `main`, reconciled by taking the branch's verified
232
241
  superset while preserving main's unique `CODEMBLE_DATA_DIR`/config-isolation
233
- fix, which lived in files the branch never touched.
242
+ fix, which lived in files the branch never touched. The Architecture map now
243
+ uses deterministic barycenter ordering and backend-routed, directional,
244
+ weight-scaled SVG paths; cycle and long-span routes use clear flank corridors,
245
+ while possible relationships remain dashed and React remains a pure renderer.
246
+ Galaxy regions now place in deterministic constellations derived only from
247
+ parser-proven import communities, with the community ID exposed in graph schema
248
+ 5 and progress signatures remaining coordinate-independent. A tester then
249
+ reported the 169-system galaxy unnavigable and undifferentiated, which resolved
250
+ into four separate defects: the camera could not move at all, no star carried a
251
+ name and there was no search or index, every region route drew unconditionally
252
+ so the mesh outshone the stars, and a display-size heading plus a twelve-row
253
+ always-on legend covered the stage. All four are fixed — bounded orbit,
254
+ progressive reveal keyed to a new `hops_from_home` graph field (schema 6),
255
+ ranked and decluttered name plates, a command palette plus an index sidebar over
256
+ one shared module index, and chrome demoted to a single line with the legend
257
+ behind a disclosure. On this repository the default galaxy went from 90 systems
258
+ with their whole route mesh to 22 charted with the rest drawn faint, unnamed and
259
+ edgeless; nothing was removed from the graph and no region re-dimmed. Four
260
+ defects were caught by running it rather than by the suite: a sprite map cleared
261
+ by an effect that ran after the one that filled it, an undefined constant that
262
+ threw inside the declutter timer and silently erased every name, plates that
263
+ claimed one screen cell regardless of their real width, and an open sidebar
264
+ occluding the system panel's primary action. A fifth followed: labels offered
265
+ only one position each, directly above their star, so at galaxy zoom nearly
266
+ every plate lost its slot to a neighbour and a 90-system sky carried one name.
267
+ Names now try a short list of slots around the star and collision-test where the
268
+ plate actually draws rather than where its star sits — 1 name became roughly 24
269
+ with everything shown, 9 by default. The same navigation and clarity pass was
270
+ then applied to the Map layer, where two of the three galaxy problems turned out
271
+ to exist in a sharper form: a fixed-width box truncated the dotted region id, so
272
+ `codemble.server.app` and `codemble.server.runtime` both rendered as
273
+ `codemble.server…` — identical text for different modules — and a 960x2640
274
+ diagram sat in a plain scroll box showing four of its nine import layers. Boxes
275
+ are now named by the tail of their real path (map schema 3, zero visible-text
276
+ collisions across all 90 boxes on this repository) and the Map gained zoom, Fit,
277
+ and drag-to-pan. Progressive reveal was deliberately not extended to the Map.
234
278
 
235
279
  ### M0 — Repo, docs & website scaffold ✅ (2026-07-19)
236
280
  - [x] Root: README, LICENSE (Apache-2.0), CoC, SECURITY, CONTRIBUTING,
@@ -396,6 +440,23 @@ interactive galaxy; re-fetching the graph does not re-sort the world; the scale
396
440
  prompt is actionable entirely in-app; generated check suites are byte-identical
397
441
  to before the index change.
398
442
 
443
+ ### M14 — Architecture depth and indexed learner views ✅ (2026-07-21)
444
+ - [x] Put canonical browse-root resolution, folder listing, and recent-project
445
+ filtering behind `ProjectSelector`
446
+ - [x] Make parse-to-live binding, stale-worker refusal, release, and graph/map
447
+ cache lifetime atomic behind `ProjectActivation`
448
+ - [x] Put picker attempts, parse polling/backoff, retry, outage, reset, and
449
+ stale-response guards behind one Project Mapping Run
450
+ - [x] Put name ranking, camera budget, projection, slots, collision cells,
451
+ sprite metadata, and cleanup behind one deterministic Name Atlas
452
+ - [x] Index learner projections by their real dependencies and prove hover-only
453
+ commits reuse stable outputs; benchmark the 1,000-node case
454
+
455
+ **Acceptance:** public HTTP payloads and parser/check/persistence contracts are
456
+ unchanged; focused module suites and the existing end-to-end session/server
457
+ suites pass; the production SPA is rebuilt; the 1,000-node projection benchmark
458
+ shows lower repeated-commit work without changing derived values.
459
+
399
460
  ## Decision Log **[AGENT-MAINTAINED — append only]**
400
461
 
401
462
  | Date | Decision | Why |
@@ -462,10 +523,24 @@ to before the index change.
462
523
  | 2026-07-20 | Progress reporting is a thread-scoped per-file hook (`note_file_parsed`) bound by `ProjectParser`, not a new `LanguageAdapter` parameter | The public adapter seam must stay unchanged for Phase 2 languages; one hook site per adapter also gives cancellation its exact "between files" meaning |
463
524
  | 2026-07-20 | Phase C adds `DELETE /api/progress`, the `CLEAR_PROGRESS` session event, and a `clearProgress` adapter method beyond the shared contract's Phase C rows | The contract's Phase C rows covered parse progress only, while the no-reset-progress-control gap is mapped to Phase C by the spec; recorded here rather than silently widened |
464
525
  | 2026-07-20 | Generated check suites are pinned by a committed golden fixture before any performance work touches `checks/service.py` | The Correctness Contract makes suite drift top-severity, and a refactor that changes an answer is invisible without a byte-level pin |
526
+ | 2026-07-20 | Architecture map edges get backend-computed ports, barycenter ordering, arrowheads, and weight-scaled strokes; `MAP_SCHEMA_VERSION` 2; directory groups stay payload metadata | Within-layer order was arbitrary and direction was invisible in 2D while being parser truth; ordering stays deterministic (fixed sweeps, sorted ties); group containers wait for hierarchical layout |
527
+ | 2026-07-20 | Galaxy regions place by deterministic import-community constellations (pure-Python label propagation in `layout.py`); `community` is an additive Region field; layout bytes change once | Hash-order placement scattered coupled modules; communities are parser-truth-derived and deterministic; progress signatures hash file content so nothing re-dims (M12 precedent) |
528
+ | 2026-07-21 | **Bounded orbit** replaces the fixed camera, amending the free-flight Non-Goal: `controlType('orbit')` with panning disabled, per-level distance clamps, and clamped polar angle. The wheel becomes zoom; level changes move to click/Enter/Escape/breadcrumb | Approved by UD after a tester reported the galaxy unnavigable. Panning is the one degree of freedom that can strand a learner in empty space with nothing to navigate back by, so it stays off — rotation and zoom are clamped instead, which keeps "you cannot get lost" true. One gesture cannot mean both zoom and change-level, so the wheel's old meaning had to move |
529
+ | 2026-07-21 | Galaxy uses **progressive reveal**: floor (within 2 import hops of Home) ∪ neighbours of every lit region ∪ the current selection's neighbours, with a persisted Show-all toggle. An unrevealed region is drawn faint, unnamed, edgeless — never removed | Approved by UD. 169 systems and their whole route mesh was the hairball; dropping the *edges* of what is not yet charted thins the sky without a separate density control. Regions stay drawn and clickable because hiding one would misreport the project's size, which is precisely the kind of wrong a learner cannot detect. Reveal is recomputed from proven progress, never stored, so it cannot drift out of step with it |
530
+ | 2026-07-21 | `Region.hops_from_home` is graph-layer truth (schema 6): undirected BFS from Home over proven import routes, `None` when unreachable; `with_entrypoint` recomputes it | Reveal is game logic and belongs in `LearnerSession`, but the *distance* is a fact about the project and belongs in the graph. The frontend was already re-walking this exact BFS for the Easy-mode hint, so the two could in principle have disagreed about one number; there is now one source. `None` is never softened to a large number, or "unreachable" would read as "very far" |
531
+ | 2026-07-21 | Canvas name plates are ranked (Home → lit → centrality), budgeted by camera distance, and decluttered by claiming the full screen-cell rectangle each plate covers | A name is the cheapest differentiation there is and the sky had none. Claiming one cell per plate let a wide name cover three neighbours, and claiming only a row let two plates straddling a boundary collide — the rectangle is the only version that actually holds. Plate geometry is published on the sprite by the module that sizes it, so the constant is not duplicated across files |
532
+ | 2026-07-21 | Finding a module is a command palette **and** an index sidebar over one shared `moduleIndex`; sidebar rows show each path minus its group's shared prefix | Approved by UD. Progressive reveal makes targeted retrieval mandatory — a thinned sky must never hide a module from someone who knows its name — and both surfaces reach every module whether charted or not. Basenames alone are useless in a Python project where every package carries an `__init__.py`, so rows keep enough real path to be told apart |
533
+ | 2026-07-21 | Progressive reveal stays **galaxy-only**; the Map always draws every module | Approved by UD when the navigation work was extended to the Map. The Map's job is "how it all fits together", and a layered import diagram with holes in it teaches less than a complete one; the galaxy already offers the thinned view for learners who want it |
534
+ | 2026-07-21 | Architecture boxes are named by the tail of their file path (`short_label`, map schema 3); `label` keeps the full identifier for title and aria | A box is a fixed width, so its text always truncates on a real project — and truncating a dotted region id rendered `codemble.server.app` and `codemble.server.runtime` as the same glyphs. Identical text for different modules is worse than no label, and it is exactly the kind of wrong a learner cannot detect. The path tail also survives the `__init__.py` collision a basename alone cannot |
535
+ | 2026-07-21 | The Map gains zoom, Fit, and drag-to-pan; panning rides the container's own scroll and zoom only scales the rendered size | The 2D counterpart of bounded orbit: a 960x2640 diagram in a plain scroll box showed four of nine layers and no way to see the whole shape. Scroll-based panning keeps native scrollbars, keyboard scrolling and screen-reader behaviour intact, and because every coordinate inside the SVG stays backend-computed, React remains a pure renderer of graph-owned geometry. It opens at true size rather than auto-fitting: fitting on mount measured the scroller before layout settled and landed on a scale that was neither fitted nor honest |
536
+ | 2026-07-21 | v0.6.0 deepens five private boundaries without changing the HTTP, graph, check, persistence, or learner-visible contracts: Project Selection, Project Activation, Project Mapping Run, Name Atlas, and Learner Projection | Approved by UD as five behavior-preserving waves in one release PR. The deletion test now holds at each seam, stale activation and mapping responses lose atomically, and dependency-scoped learner projections measured ~0.331 ms → ~0.001 ms per hover commit on a synthetic 1,000-node project while preserving derived outputs |
465
537
 
466
538
  ## Non-Goals — do NOT build (point here when asked)
467
539
 
468
- - ❌ Free-flight 3D navigation — semantic zoom only
540
+ - ❌ ~~Free-flight 3D navigation~~superseded 2026-07-21: **bounded orbit** is
541
+ approved (see Decision Log). The camera may rotate and zoom around the current
542
+ subject; panning, free translation, and any control that can leave the subject
543
+ off screen remain out
469
544
  - ❌ XP, streaks, levels, leaderboards
470
545
  - ❌ ~~A second 2D renderer/toggle in v1~~ — superseded 2026-07-19: the 2D Map layer is approved (see Decision Log); free-form/client-computed 2D layouts remain out
471
546
  - ❌ Accounts, cloud hosting, multi-user; share link waits for Phase 3
@@ -0,0 +1,28 @@
1
+ # Codemble domain model
2
+
3
+ ## Project Selection
4
+
5
+ The learner's choice of one local folder, constrained to Codemble's allowed
6
+ browse root, that may become the current project.
7
+
8
+ ## Project Activation
9
+
10
+ The atomic transition from a successfully parsed project to the live checks,
11
+ study, graph, and map experience. Releasing a project cancels any unfinished
12
+ activation and leaves no project bound.
13
+
14
+ ## Project Mapping Run
15
+
16
+ The learner-visible run that begins when a selected folder is accepted and
17
+ ends when its project activates, parsing fails, or the learner releases it.
18
+
19
+ ## Name Atlas
20
+
21
+ The deterministic set of visible names placed around charted stars for the
22
+ current camera view, prioritizing Home, understood systems, and the learner's
23
+ current pointer subject without allowing name plates to collide.
24
+
25
+ ## Learner Projection
26
+
27
+ The learner-facing view of the current project after language focus, proven
28
+ progress, navigation selection, and view preferences are applied.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codemble
3
- Version: 0.5.2
3
+ Version: 0.6.0
4
4
  Summary: A learning game that turns the code AI wrote for you into a galaxy you light up by understanding it.
5
5
  Project-URL: Homepage, https://udhawan97.github.io/Codemble/
6
6
  Project-URL: Repository, https://github.com/udhawan97/Codemble
@@ -22,7 +22,7 @@ Requires-Python: >=3.11
22
22
  Requires-Dist: fastapi>=0.115
23
23
  Requires-Dist: tree-sitter-javascript<0.26,>=0.25
24
24
  Requires-Dist: tree-sitter-typescript<0.24,>=0.23.2
25
- Requires-Dist: tree-sitter<0.27,>=0.25
25
+ Requires-Dist: tree-sitter<0.26,>=0.25
26
26
  Requires-Dist: uvicorn>=0.30
27
27
  Provides-Extra: dev
28
28
  Requires-Dist: httpx>=0.27; extra == 'dev'
@@ -322,6 +322,13 @@ The [public roadmap](https://udhawan97.github.io/Codemble/roadmap/) separates
322
322
  shipped work from planned work. Milestones move only when their acceptance
323
323
  evidence exists.
324
324
 
325
+ ## Acknowledgements
326
+
327
+ - [dagre](https://github.com/dagrejs/dagre) and [Eclipse ELK](https://github.com/eclipse-elk/elk) for the layered-diagram approach.
328
+ - [tt-a1i/archify](https://github.com/tt-a1i/archify) for 2D architecture-diagram inspiration.
329
+ - [Graphify](https://github.com/Graphify-Labs/graphify) for the community-constellation idea.
330
+ - Codemble's shipped open-source stack: [3d-force-graph](https://github.com/vasturiano/3d-force-graph), [tree-sitter](https://github.com/tree-sitter/tree-sitter), [FastAPI](https://github.com/fastapi/fastapi), [Vite](https://github.com/vitejs/vite), and [React](https://github.com/facebook/react).
331
+
325
332
  ## License
326
333
 
327
334
  Codemble is released under the [Apache License 2.0](https://github.com/udhawan97/Codemble/blob/main/LICENSE).
@@ -290,6 +290,13 @@ The [public roadmap](https://udhawan97.github.io/Codemble/roadmap/) separates
290
290
  shipped work from planned work. Milestones move only when their acceptance
291
291
  evidence exists.
292
292
 
293
+ ## Acknowledgements
294
+
295
+ - [dagre](https://github.com/dagrejs/dagre) and [Eclipse ELK](https://github.com/eclipse-elk/elk) for the layered-diagram approach.
296
+ - [tt-a1i/archify](https://github.com/tt-a1i/archify) for 2D architecture-diagram inspiration.
297
+ - [Graphify](https://github.com/Graphify-Labs/graphify) for the community-constellation idea.
298
+ - Codemble's shipped open-source stack: [3d-force-graph](https://github.com/vasturiano/3d-force-graph), [tree-sitter](https://github.com/tree-sitter/tree-sitter), [FastAPI](https://github.com/fastapi/fastapi), [Vite](https://github.com/vitejs/vite), and [React](https://github.com/facebook/react).
299
+
293
300
  ## License
294
301
 
295
302
  Codemble is released under the [Apache License 2.0](https://github.com/udhawan97/Codemble/blob/main/LICENSE).
@@ -1,4 +1,4 @@
1
1
  """Codemble — a learning game that turns the code AI wrote for you into a galaxy
2
2
  you light up by understanding it. See CLAUDE.md for the agent brief and roadmap."""
3
3
 
4
- __version__ = "0.5.2"
4
+ __version__ = "0.6.0"
@@ -89,6 +89,13 @@ class Region:
89
89
  x: float
90
90
  y: float
91
91
  z: float
92
+ community: int
93
+ # Undirected breadth-first distance from the Home region over proven import
94
+ # routes. ``None`` means no route reaches this region -- or that no Home is
95
+ # selected yet -- and is never softened into a large number, because the
96
+ # renderer reveals the sky by this distance and a guessed one would quietly
97
+ # promise a path the parser never found.
98
+ hops_from_home: int | None = None
92
99
 
93
100
 
94
101
  @dataclass(frozen=True, slots=True)
@@ -115,7 +122,7 @@ class Graph:
115
122
  regions: tuple[Region, ...] = ()
116
123
  region_edges: tuple[RegionEdge, ...] = ()
117
124
  partial_files: tuple[str, ...] = ()
118
- schema_version: int = field(default=4, init=False)
125
+ schema_version: int = field(default=6, init=False)
119
126
 
120
127
  def to_dict(self) -> dict[str, object]:
121
128
  """Return a JSON-ready representation in canonical collection order."""
@@ -24,22 +24,72 @@ def layout_graph(graph: Graph) -> Graph:
24
24
  for node in graph.nodes:
25
25
  grouped[node.region].append(node)
26
26
 
27
+ node_by_id = {node.id: node for node in graph.nodes}
28
+
29
+ routes: dict[tuple[str, str], list[bool]] = defaultdict(list)
30
+ for edge in graph.edges:
31
+ if edge.kind != "import" or edge.external:
32
+ continue
33
+ src_node = node_by_id.get(edge.src)
34
+ dst_node = node_by_id.get(edge.dst)
35
+ if src_node is None or dst_node is None or src_node.region == dst_node.region:
36
+ continue
37
+ routes[(src_node.region, dst_node.region)].append(edge.certain)
38
+
39
+ # ``all``, deliberately, where ``mapview.py``'s ``_workflow`` uses ``any``
40
+ # for a call pair. The two marks claim different things. A workflow row
41
+ # asserts only that a relationship exists, so one proven call site settles
42
+ # it and the ambiguous ones beside it change nothing. A route is a single
43
+ # line standing in for ``weight`` imports, so calling it certain asserts
44
+ # every one of them: one unproven import among them and the whole route
45
+ # drops to possible. Under-claiming is the only direction that is safe
46
+ # when one mark speaks for many edges.
47
+ region_edges = tuple(
48
+ RegionEdge(src=src, dst=dst, weight=len(certainties), certain=all(certainties))
49
+ for (src, dst), certainties in sorted(routes.items())
50
+ )
51
+
27
52
  region_order = sorted(grouped, key=lambda region_id: (_digest(region_id), region_id))
53
+ communities = _communities(tuple(grouped), region_edges)
54
+ community_members: dict[int, list[str]] = defaultdict(list)
55
+ for region_id in region_order:
56
+ community_members[communities[region_id]].append(region_id)
57
+
58
+ region_positions: dict[str, tuple[float, float, float]] = {}
59
+ cumulative_members = 0
60
+ for community in sorted(community_members):
61
+ members = community_members[community]
62
+ community_angle = community * _GOLDEN_ANGLE
63
+ community_radius = 42.0 + 54.0 * math.sqrt(cumulative_members)
64
+ community_x = math.cos(community_angle) * community_radius
65
+ community_z = math.sin(community_angle) * community_radius
66
+ for member_index, region_id in enumerate(members):
67
+ local_angle = (
68
+ member_index * _GOLDEN_ANGLE
69
+ + _fraction(region_id, "phase") * 0.18
70
+ )
71
+ local_radius = 16.0 + 12.0 * math.sqrt(member_index)
72
+ region_positions[region_id] = (
73
+ _rounded(community_x + math.cos(local_angle) * local_radius),
74
+ _rounded(((_fraction(region_id, "height") * 2.0) - 1.0) * 28.0),
75
+ _rounded(community_z + math.sin(local_angle) * local_radius),
76
+ )
77
+ cumulative_members += len(members)
78
+
79
+ home_region = next(
80
+ (node.region for node in graph.nodes if node.id == graph.selected_entrypoint),
81
+ None,
82
+ )
83
+ hops = _hops_from_home(tuple(grouped), region_edges, home_region)
84
+
28
85
  regions: list[Region] = []
29
86
  positioned_nodes: list[Node] = []
30
- node_by_id = {node.id: node for node in graph.nodes}
31
-
32
- for region_index, region_id in enumerate(region_order):
87
+ for region_id in region_order:
33
88
  members = sorted(
34
89
  grouped[region_id],
35
90
  key=lambda node: (node.kind != "module", node.id),
36
91
  )
37
- region_angle = region_index * _GOLDEN_ANGLE + _fraction(region_id, "phase") * 0.18
38
- region_radius = 42.0 + 54.0 * math.sqrt(region_index)
39
- region_x = _rounded(math.cos(region_angle) * region_radius)
40
- region_y = _rounded(((_fraction(region_id, "height") * 2.0) - 1.0) * 28.0)
41
- region_z = _rounded(math.sin(region_angle) * region_radius)
42
-
92
+ region_x, region_y, region_z = region_positions[region_id]
43
93
  module_nodes = [node for node in members if node.kind == "module"]
44
94
  loc = sum(node.loc for node in module_nodes) or sum(node.loc for node in members)
45
95
  regions.append(
@@ -54,6 +104,8 @@ def layout_graph(graph: Graph) -> Graph:
54
104
  x=region_x,
55
105
  y=region_y,
56
106
  z=region_z,
107
+ community=communities[region_id],
108
+ hops_from_home=hops.get(region_id),
57
109
  )
58
110
  )
59
111
 
@@ -85,28 +137,6 @@ def layout_graph(graph: Graph) -> Graph:
85
137
  )
86
138
  )
87
139
 
88
- routes: dict[tuple[str, str], list[bool]] = defaultdict(list)
89
- for edge in graph.edges:
90
- if edge.kind != "import" or edge.external:
91
- continue
92
- src_node = node_by_id.get(edge.src)
93
- dst_node = node_by_id.get(edge.dst)
94
- if src_node is None or dst_node is None or src_node.region == dst_node.region:
95
- continue
96
- routes[(src_node.region, dst_node.region)].append(edge.certain)
97
-
98
- # ``all``, deliberately, where ``mapview.py``'s ``_workflow`` uses ``any``
99
- # for a call pair. The two marks claim different things. A workflow row
100
- # asserts only that a relationship exists, so one proven call site settles
101
- # it and the ambiguous ones beside it change nothing. A route is a single
102
- # line standing in for ``weight`` imports, so calling it certain asserts
103
- # every one of them: one unproven import among them and the whole route
104
- # drops to possible. Under-claiming is the only direction that is safe
105
- # when one mark speaks for many edges.
106
- region_edges = tuple(
107
- RegionEdge(src=src, dst=dst, weight=len(certainties), certain=all(certainties))
108
- for (src, dst), certainties in sorted(routes.items())
109
- )
110
140
  return replace(
111
141
  graph,
112
142
  nodes=tuple(sorted(positioned_nodes, key=lambda node: node.id)),
@@ -115,6 +145,90 @@ def layout_graph(graph: Graph) -> Graph:
115
145
  )
116
146
 
117
147
 
148
+ def _communities(
149
+ region_ids: tuple[str, ...], routes: tuple[RegionEdge, ...]
150
+ ) -> dict[str, int]:
151
+ """Find deterministic import communities with label propagation.
152
+
153
+ Label propagation after Raghavan, Albert & Kumara (2007), "Near linear
154
+ time algorithm to detect community structures in large-scale networks";
155
+ constellation idea inspired by graphify. Implemented independently; no
156
+ code copied.
157
+ """
158
+
159
+ ordered = sorted(region_ids)
160
+ neighbors: dict[str, set[str]] = {region_id: set() for region_id in ordered}
161
+ for route in sorted(routes, key=lambda edge: (edge.src, edge.dst)):
162
+ if route.src not in neighbors or route.dst not in neighbors or route.src == route.dst:
163
+ continue
164
+ neighbors[route.src].add(route.dst)
165
+ neighbors[route.dst].add(route.src)
166
+
167
+ labels = {region_id: index for index, region_id in enumerate(ordered)}
168
+ for _ in range(10):
169
+ next_labels = dict(labels)
170
+ for region_id in ordered:
171
+ if not neighbors[region_id]:
172
+ continue
173
+ frequencies: dict[int, int] = defaultdict(int)
174
+ for neighbor in sorted(neighbors[region_id]):
175
+ frequencies[labels[neighbor]] += 1
176
+ highest = max(frequencies.values())
177
+ next_labels[region_id] = min(
178
+ label for label, count in frequencies.items() if count == highest
179
+ )
180
+ if next_labels == labels:
181
+ break
182
+ labels = next_labels
183
+
184
+ dense_labels: dict[int, int] = {}
185
+ communities: dict[str, int] = {}
186
+ for region_id in ordered:
187
+ label = labels[region_id]
188
+ if label not in dense_labels:
189
+ dense_labels[label] = len(dense_labels)
190
+ communities[region_id] = dense_labels[label]
191
+ return communities
192
+
193
+
194
+ def _hops_from_home(
195
+ region_ids: tuple[str, ...],
196
+ routes: tuple[RegionEdge, ...],
197
+ home: str | None,
198
+ ) -> dict[str, int]:
199
+ """Return each region's import-route distance from Home.
200
+
201
+ Undirected on purpose: an import route is a relationship between two
202
+ modules, and a learner following it from Home arrives just as surely at the
203
+ importer as at the imported. Direction is still parser truth and is still
204
+ drawn as an arrow -- it just is not what "how far from Home" means.
205
+
206
+ Regions absent from the result have no route to Home at all. The caller
207
+ stores that as ``None`` rather than a sentinel distance, so "unreachable"
208
+ can never be mistaken for "very far".
209
+ """
210
+
211
+ if home is None or home not in set(region_ids):
212
+ return {}
213
+
214
+ neighbors: dict[str, set[str]] = {region_id: set() for region_id in region_ids}
215
+ for route in routes:
216
+ if route.src not in neighbors or route.dst not in neighbors:
217
+ continue
218
+ neighbors[route.src].add(route.dst)
219
+ neighbors[route.dst].add(route.src)
220
+
221
+ hops = {home: 0}
222
+ queue: deque[str] = deque([home])
223
+ while queue:
224
+ current = queue.popleft()
225
+ for neighbor in sorted(neighbors[current]):
226
+ if neighbor not in hops:
227
+ hops[neighbor] = hops[current] + 1
228
+ queue.append(neighbor)
229
+ return hops
230
+
231
+
118
232
  def _call_depths(members: list[Node], edges: tuple[Edge, ...]) -> dict[str, int]:
119
233
  """Return each member's orbit ring: call depth from the system's entry node.
120
234
 
@@ -169,8 +283,20 @@ def with_entrypoint(graph: Graph, node_id: str) -> Graph:
169
283
  raise ValueError(f"entrypoint is not a parser-ranked candidate: {node_id}")
170
284
  node_by_id = {node.id: node for node in graph.nodes}
171
285
  selected = node_by_id[node_id]
286
+ # Every distance is measured from Home, so moving Home invalidates all of
287
+ # them. Coordinates and communities are untouched: they depend on the import
288
+ # structure, not on which entrypoint the learner chose, so a Home change
289
+ # must not re-sort the sky or re-dim a region.
290
+ hops = _hops_from_home(
291
+ tuple(region.id for region in graph.regions), graph.region_edges, selected.region
292
+ )
172
293
  regions = tuple(
173
- replace(region, home=region.id == selected.region) for region in graph.regions
294
+ replace(
295
+ region,
296
+ home=region.id == selected.region,
297
+ hops_from_home=hops.get(region.id),
298
+ )
299
+ for region in graph.regions
174
300
  )
175
301
  return replace(graph, selected_entrypoint=node_id, regions=regions)
176
302