codemble 0.4.0__tar.gz → 0.5.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.
- {codemble-0.4.0 → codemble-0.5.0}/CHANGELOG.md +76 -0
- {codemble-0.4.0 → codemble-0.5.0}/CLAUDE.md +60 -4
- {codemble-0.4.0 → codemble-0.5.0}/PKG-INFO +15 -4
- {codemble-0.4.0 → codemble-0.5.0}/README.md +14 -3
- {codemble-0.4.0 → codemble-0.5.0}/TESTING.md +2 -2
- {codemble-0.4.0 → codemble-0.5.0}/codemble/__init__.py +1 -1
- codemble-0.5.0/codemble/adapters/parse_progress.py +99 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/adapters/project.py +50 -16
- {codemble-0.4.0 → codemble-0.5.0}/codemble/adapters/python_ast.py +23 -5
- {codemble-0.4.0 → codemble-0.5.0}/codemble/adapters/typescript_tree_sitter.py +4 -1
- {codemble-0.4.0 → codemble-0.5.0}/codemble/checks/service.py +130 -106
- {codemble-0.4.0 → codemble-0.5.0}/codemble/llm/study.py +3 -2
- codemble-0.5.0/codemble/paths.py +27 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/progress/store.py +16 -10
- {codemble-0.4.0 → codemble-0.5.0}/codemble/server/app.py +171 -23
- codemble-0.5.0/codemble/server/parse_job.py +153 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/server/runtime.py +66 -1
- codemble-0.4.0/codemble/web_dist/assets/index-DKTfLRbf.js → codemble-0.5.0/codemble/web_dist/assets/index-BlkEA_-c.js +1 -1
- codemble-0.4.0/codemble/web_dist/assets/index-BHcmQpjd.css → codemble-0.5.0/codemble/web_dist/assets/index-xOQxhaMB.css +1 -1
- {codemble-0.4.0 → codemble-0.5.0}/codemble/web_dist/index.html +2 -2
- codemble-0.5.0/docs/releases/v0.5.0.md +94 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/astro.config.mjs +1 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/package.json +1 -1
- codemble-0.5.0/docs-site/public/shots/loading.png +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/checks-and-lighting.md +7 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/installation.md +4 -3
- codemble-0.5.0/docs-site/src/content/docs/progress/m13-scale.md +53 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/quickstart.md +3 -3
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/the-galaxy.md +12 -0
- {codemble-0.4.0 → codemble-0.5.0}/pyproject.toml +1 -1
- codemble-0.5.0/tests/conftest.py +57 -0
- codemble-0.5.0/tests/fixtures/check_suites.json +1240 -0
- codemble-0.5.0/tests/fixtures/impact/alpha.py +6 -0
- codemble-0.5.0/tests/fixtures/impact/beta.py +5 -0
- codemble-0.5.0/tests/fixtures/impact/gamma.py +5 -0
- codemble-0.5.0/tests/fixtures/impact/helpers.py +6 -0
- codemble-0.5.0/tests/fixtures/impact/noisy.py +9 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/test_checks.py +97 -20
- codemble-0.5.0/tests/test_parse_job.py +167 -0
- codemble-0.5.0/tests/test_parse_progress.py +134 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/test_progress.py +25 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/test_project_parser.py +29 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/test_python_ast.py +45 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/test_server.py +563 -17
- codemble-0.5.0/tests/test_smoke.py +214 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/test_study.py +52 -0
- {codemble-0.4.0 → codemble-0.5.0}/web/package-lock.json +2 -2
- {codemble-0.4.0 → codemble-0.5.0}/web/package.json +1 -1
- {codemble-0.4.0 → codemble-0.5.0}/web/scripts/check_graph_data.mjs +85 -0
- {codemble-0.4.0 → codemble-0.5.0}/web/scripts/check_learner_session.mjs +292 -8
- {codemble-0.4.0 → codemble-0.5.0}/web/src/App.jsx +368 -19
- {codemble-0.4.0 → codemble-0.5.0}/web/src/GalaxyCanvas.jsx +7 -0
- {codemble-0.4.0 → codemble-0.5.0}/web/src/GuidanceLayer.jsx +27 -2
- {codemble-0.4.0 → codemble-0.5.0}/web/src/MapView.jsx +32 -6
- {codemble-0.4.0 → codemble-0.5.0}/web/src/graphData.js +42 -0
- {codemble-0.4.0 → codemble-0.5.0}/web/src/learnerSession.js +202 -2
- {codemble-0.4.0 → codemble-0.5.0}/web/src/styles.css +282 -0
- codemble-0.4.0/tests/conftest.py +0 -30
- codemble-0.4.0/tests/test_smoke.py +0 -75
- {codemble-0.4.0 → codemble-0.5.0}/.env.example +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/.gitattributes +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/.github/ISSUE_TEMPLATE/early_tester.yml +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/.github/dependabot.yml +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/.github/workflows/ci.yml +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/.github/workflows/pages.yml +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/.github/workflows/publish-pypi.yml +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/.gitignore +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/AGENTS.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/CODE_OF_CONDUCT.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/CONTRIBUTING.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/LICENSE +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/NOTICE +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/SECURITY.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/assets/demo.gif +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/adapters/__init__.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/adapters/base.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/adapters/discovery.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/checks/__init__.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/cli.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/graph/__init__.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/graph/finalize.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/graph/layout.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/graph/mapview.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/lens/__init__.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/lens/javascript_typescript.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/lens/python.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/llm/__init__.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/llm/local_status.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/llm/providers.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/llm/structural.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/progress/__init__.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/server/__init__.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/web_dist/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/web_dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/web_dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/web_dist/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/web_dist/assets/shippori-mincho-latin-500-normal-C-QwvIb3.woff +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/web_dist/assets/shippori-mincho-latin-500-normal-XI1O8euf.woff2 +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/web_dist/assets/shippori-mincho-latin-700-normal-CkoCYOiI.woff +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/web_dist/assets/shippori-mincho-latin-700-normal-DHcmzUO5.woff2 +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/web_dist/assets/zen-kaku-gothic-new-latin-400-normal-BEdayliK.woff2 +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/codemble/web_dist/assets/zen-kaku-gothic-new-latin-400-normal-CPSmNJAU.woff +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs/adr/README.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs/plans/2026-07-19-audience-modes-and-local-narration-design.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs/plans/2026-07-19-audience-modes-backend-plan.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs/plans/2026-07-19-audience-modes-frontend-plan.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs/plans/2026-07-19-install-ux-folder-picker-design.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs/plans/2026-07-19-install-ux-folder-picker-plan.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs/plans/README.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs/plans/phase-1.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs/releases/checklist.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs/releases/v0.1.0.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs/releases/v0.2.0.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs/releases/v0.4.0.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs/research/README.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs/superpowers/plans/2026-07-19-galaxy-ux-phase-a.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs/superpowers/plans/2026-07-19-galaxy-ux-phase-b.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs/superpowers/plans/2026-07-19-galaxy-ux-phase-c.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs/superpowers/plans/2026-07-19-galaxy-ux-shared-contract.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs/superpowers/specs/2026-07-19-galaxy-ux-overhaul-design.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/.gitignore +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/design.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/brand/favicon-16.png +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/brand/favicon-32.png +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/brand/icon-180.png +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/brand/icon-192.png +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/brand/icon-512.png +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/brand/icon.svg +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/brand/icons/asterism.svg +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/brand/icons/install.svg +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/brand/mark-animated.svg +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/brand/mark.svg +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/brand/plates/hero-chart.svg +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/brand/plates/hero-field.svg +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/brand/plates/hero-gold.svg +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/brand/plates/kasumi-rule.svg +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/brand/plates/plate-galaxy.svg +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/brand/plates/plate-study.svg +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/brand/plates/plate-system.svg +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/brand/plates/seal.svg +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/favicon.svg +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/shots/easy-mode.png +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/shots/galaxy-lit.png +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/shots/galaxy.png +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/shots/map-architecture.png +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/shots/map-workflow.png +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/shots/study-panel.png +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/public/shots/system.png +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/scripts/build-plates.mjs +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/assets/codemble-mark-dark.svg +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/assets/codemble-mark-light.svg +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/components/Search.astro +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/components/Tatebanko.astro +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/architecture.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/build-from-source.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/contributing.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/correctness.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/early-testing.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/introduction.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/progress/m1-parser.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/progress/m10-polyglot-release.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/progress/m12-galaxy-look.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/progress/m2-galaxy.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/progress/m3-study.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/progress/m4-lens.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/progress/m5-checks.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/progress/m6-release.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/progress/m8-typescript.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/progress/m9-typescript-lens.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/roadmap.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/star-chart.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content/docs/study-panel.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/content.config.ts +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/pages/index.astro +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/styles/custom.css +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/styles/landing.css +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/docs-site/src/styles/tokens.css +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/scripts/dev.sh +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/scripts/record_demo.sh +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/concepts_sample.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/polyglot/python_worker.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/polyglot/src/broken.ts +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/polyglot/src/legacy.js +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/polyglot/src/local.js +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/polyglot/src/main.ts +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/polyglot/src/reexport.ts +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/polyglot/src/util.ts +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/polyglot/src/widget.tsx +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/repeated_calls.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/sampleproj/.gitignore +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/sampleproj/ambiguous.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/sampleproj/api.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/sampleproj/app.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/sampleproj/broken.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/sampleproj/cli.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/sampleproj/pkg/__init__.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/sampleproj/pkg/helpers.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/sampleproj/pkg/service.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/sampleproj/pkg/util.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/sampleproj/runner/__main__.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/fixtures/sampleproj/shared.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/test_graph_finalization.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/test_mapview.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/test_providers.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/test_structural.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/tests/test_typescript_tree_sitter.py +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/web/README.md +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/web/index.html +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/web/scripts/record_demo.mjs +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/web/src/ModeControl.jsx +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/web/src/StudyPanel.jsx +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/web/src/galaxyEffects.js +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/web/src/galaxyMaterials.js +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/web/src/main.jsx +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/web/src/tokens.css +0 -0
- {codemble-0.4.0 → codemble-0.5.0}/web/vite.config.js +0 -0
|
@@ -5,6 +5,82 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · Versioning:
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.5.0] - 2026-07-20
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Large projects now show a staged loading screen instead of a frozen tab.
|
|
12
|
+
Parsing runs on a worker thread; the app polls `GET /api/picker/progress` and
|
|
13
|
+
names the stage it is in — discovering, parsing, resolving, checks, layout —
|
|
14
|
+
with a real file count while files are read, and a narrated sub-step
|
|
15
|
+
(resolving imports, resolving calls, building the galaxy map, composing the
|
|
16
|
+
project) once the parse moves past counting files. A failed parse reports
|
|
17
|
+
the parser's own message and offers an in-app retry; cancelling stops the
|
|
18
|
+
parse at the next file boundary. A realistic 1,000-file Python project
|
|
19
|
+
parses and renders an interactive galaxy end to end; the parse itself is
|
|
20
|
+
1.56x faster after removing a parser hotspot (below), and the one-pass check
|
|
21
|
+
index builds a full 1,000-region suite in under a second.
|
|
22
|
+
- The over-cap prompt is actionable in-app: the busiest subdirectories are
|
|
23
|
+
buttons that navigate the picker, and a typed path field accepts any folder
|
|
24
|
+
inside your home directory. Outside-home paths are still refused.
|
|
25
|
+
- A **Clear this project's progress** control on the star chart, behind a
|
|
26
|
+
confirmation, scoped to the open project only.
|
|
27
|
+
- A non-interactive `codemble` run now prints the busiest-scope suggestions
|
|
28
|
+
with the scale refusal instead of a bare message.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
- The scale cap moved from 300 to 1,000 supported source files. LOD and
|
|
32
|
+
clustering remain Phase 2 work.
|
|
33
|
+
- `POST /api/picker/select` returns `202 {"state": "parsing"}` instead of
|
|
34
|
+
blocking until the graph is ready.
|
|
35
|
+
- `CODEMBLE_DATA_DIR` now relocates **everything** Codemble keeps under your home
|
|
36
|
+
directory — saved progress, the narration cache, and the `config` file — rather
|
|
37
|
+
than progress alone. All three resolve through one helper, so pointing the
|
|
38
|
+
variable somewhere else moves them together. Unset, the default is unchanged:
|
|
39
|
+
`~/.codemble`.
|
|
40
|
+
- Language focus now also projects onto the **2D Map**, matching the galaxy: a
|
|
41
|
+
focused language keeps its boxes, rows, and edges while the others are dropped,
|
|
42
|
+
and the survivors keep their backend-computed coordinates. The projection
|
|
43
|
+
happens in the frontend and never re-lays-out or mutates the immutable graph,
|
|
44
|
+
so focus finally means the same thing on both layers.
|
|
45
|
+
- The no-WebGL message now points the learner to the Map/Diagram layer, which
|
|
46
|
+
draws the same parser evidence as plain SVG and is one switch away, instead of
|
|
47
|
+
only stating that the galaxy needs WebGL.
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
- Check generation walked every graph edge up to four times per region, and
|
|
51
|
+
rebuilt the node lookup and the option pool per region — an O(regions × graph)
|
|
52
|
+
freeze at bind. One index is now built per bind in a single pass, measured
|
|
53
|
+
~16x faster at 1,000 files. A committed golden fixture proves the generated
|
|
54
|
+
suites and answers are byte-identical for the Python and mixed fixtures.
|
|
55
|
+
- `GET /api/graph` and `GET /api/map` re-hydrated progress and re-sorted every
|
|
56
|
+
node and edge on every request. The serialized document is cached and
|
|
57
|
+
invalidated on region light-up, Home selection, and project bind or reset —
|
|
58
|
+
measured ~25-26x faster warm.
|
|
59
|
+
- The Python adapter resolved a node's owning module with an O(definitions ×
|
|
60
|
+
modules) scan; it now walks the node id's own dotted prefixes in O(depth),
|
|
61
|
+
a further 1.56x on total parse wall-clock. Output is byte-identical.
|
|
62
|
+
- The test suite no longer reads the developer's `~/.codemble/config` or their
|
|
63
|
+
`ANTHROPIC_API_KEY` / `OPENAI_API_KEY`. Every server test that built an app
|
|
64
|
+
without an explicit study service inherited whatever the machine had
|
|
65
|
+
configured, so the same test could construct a live provider locally and none
|
|
66
|
+
in CI. The two tests that request `/explanation` would then make a real, billed
|
|
67
|
+
API call and cache the reply under the developer's home directory.
|
|
68
|
+
- First-run onboarding assumed the learner was on the galaxy, but Easy — the
|
|
69
|
+
default audience — opens on the 2D Map. The coach-marks and the footer control
|
|
70
|
+
hint now key on the active layer, so a learner on the Map reads map guidance
|
|
71
|
+
("click a box or row to study, switch tabs") instead of galaxy scroll/camera
|
|
72
|
+
controls that are not on screen.
|
|
73
|
+
- The audience-mode gate and the first-run coach-marks no longer open as two
|
|
74
|
+
stacked modals on a genuine first run; the coach-marks wait until a mode has
|
|
75
|
+
been chosen, so the gate resolves first.
|
|
76
|
+
- With no parser-recognisable entrypoint the "Change Home" control is not
|
|
77
|
+
rendered, so both Map tabs now state that reason instead of pointing at a
|
|
78
|
+
button that is not there. The has-candidates-but-unselected copy is unchanged.
|
|
79
|
+
- A project bind slow enough to outlast a parse cancellation could commit after
|
|
80
|
+
the picker had already been reset, resurrecting the just-released project or
|
|
81
|
+
clobbering the next selection. The commit now re-checks cancellation under the
|
|
82
|
+
same lock that performs it, so a stale bind can never rebind a released project.
|
|
83
|
+
|
|
8
84
|
## [0.4.0] - 2026-07-20
|
|
9
85
|
|
|
10
86
|
The galaxy read as flat spheres on a plain background, connections were hard to
|
|
@@ -194,9 +194,43 @@ switching, then the "living cosmos" visual overhaul with a 2D Map layer, then
|
|
|
194
194
|
the approved Non-Goal and binding relaxations. Phase A (the narration/mode/
|
|
195
195
|
connections surface and project switching) and Phase B (M12: call-depth
|
|
196
196
|
orbits, the 2D Map layer, and the living-cosmos visual overhaul) have both
|
|
197
|
-
since shipped
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
since shipped, and were released together as **v0.4.0** (tag `v0.4.0`, published
|
|
198
|
+
to PyPI, verified end to end from a clean `uvx codemble==0.4.0` install: the
|
|
199
|
+
wheel's SPA bundle is byte-identical to the tag, all 27 regions draw unclipped,
|
|
200
|
+
and the galaxy renders deep space with no console errors). Phase C (M13:
|
|
201
|
+
~1,000-file scale with staged parse progress) has since shipped from that
|
|
202
|
+
plan: parsing now runs on a worker thread behind a `202`-accepted picker
|
|
203
|
+
select and a polled `GET /api/picker/progress` through five honest stages,
|
|
204
|
+
cancellation is checked between files and a crashed worker reports as an
|
|
205
|
+
in-app error rather than a hung server, the scale cap moved 300 → 1,000 with
|
|
206
|
+
the over-cap prompt offering clickable busiest scopes plus a home-jailed typed
|
|
207
|
+
path, a one-pass check index replaced the per-region edge scans
|
|
208
|
+
(byte-identical suites, pinned by a golden fixture before the refactor),
|
|
209
|
+
`/api/graph` and `/api/map` responses are now cached with invalidation on
|
|
210
|
+
light-up, Home change, and binding, and a Clear this project's progress
|
|
211
|
+
control was added to the star chart. A dedicated verification pass at a
|
|
212
|
+
realistic ~1,000-file project then found the `resolving` stage — the slowest
|
|
213
|
+
one — showed no moving signal for most of the wait; the fix narrates its real
|
|
214
|
+
sub-steps instead of leaving the screen static, and a parser hotspot found
|
|
215
|
+
alongside it (an O(definitions × modules) module-resolution scan in the Python
|
|
216
|
+
adapter) was fixed too, together taking real parse wall-clock on a 1,000-file
|
|
217
|
+
Python project from roughly 11.5s to roughly 7.5s with byte-identical output.
|
|
218
|
+
Suite hermeticity was also closed on the read side: `CODEMBLE_DATA_DIR` now
|
|
219
|
+
relocates the narration cache and the `config` file as well as saved progress
|
|
220
|
+
through one `codemble/paths.py` helper, and the test suite clears every
|
|
221
|
+
provider variable `from_environment` reads, so a server test can no longer make
|
|
222
|
+
a real billed API call against a developer's exported key. A pre-release
|
|
223
|
+
re-audit then closed a cluster of first-run gaps that converged on the
|
|
224
|
+
Easy-default learner (who lands on the 2D Map): the coach-marks and footer now
|
|
225
|
+
teach the layer the learner is actually on, the audience gate and coach-marks
|
|
226
|
+
no longer stack as two modals, the no-entrypoint Map tabs stop pointing at a
|
|
227
|
+
Change Home button that isn't shown, language focus now filters the Map as a
|
|
228
|
+
frontend projection, and a parse `bind` that outlasts a cancel can no longer
|
|
229
|
+
rebind a released project. Phase C plus that gap-fix wave shipped as **v0.5.0**;
|
|
230
|
+
the parse work collided with an independent implementation of the same three
|
|
231
|
+
foundational commits on `main`, reconciled by taking the branch's verified
|
|
232
|
+
superset while preserving main's unique `CODEMBLE_DATA_DIR`/config-isolation
|
|
233
|
+
fix, which lived in files the branch never touched.
|
|
200
234
|
|
|
201
235
|
### M0 — Repo, docs & website scaffold ✅ (2026-07-19)
|
|
202
236
|
- [x] Root: README, LICENSE (Apache-2.0), CoC, SECURITY, CONTRIBUTING,
|
|
@@ -344,6 +378,24 @@ hash file content, never coordinates, so the orbit relayout did not re-dim any
|
|
|
344
378
|
region; reduced motion always yields the finished lit state with zero
|
|
345
379
|
animation.
|
|
346
380
|
|
|
381
|
+
### M13 — Galaxy UX Phase C: scale ✅ (2026-07-20)
|
|
382
|
+
- [x] Threaded parse behind `202` select, `GET /api/picker/progress`, and a
|
|
383
|
+
staged loading screen with real file counts
|
|
384
|
+
- [x] Cancellation checked between files; a crashed worker becomes an error
|
|
385
|
+
state, never a hung server
|
|
386
|
+
- [x] Scale cap 300 → 1,000; clickable busiest scopes plus a jailed path field;
|
|
387
|
+
suggestions in the non-TTY CLI refusal
|
|
388
|
+
- [x] One per-bind check index replacing the per-region edge scans, pinned by a
|
|
389
|
+
golden suite fixture
|
|
390
|
+
- [x] Cached `/api/graph` and `/api/map` documents invalidated on light-up,
|
|
391
|
+
Home, and binding
|
|
392
|
+
- [x] Terminal stage lines for `codemble <path>`; reset-progress control
|
|
393
|
+
|
|
394
|
+
**Acceptance:** a ~1,000-file project parses with live progress and reaches an
|
|
395
|
+
interactive galaxy; re-fetching the graph does not re-sort the world; the scale
|
|
396
|
+
prompt is actionable entirely in-app; generated check suites are byte-identical
|
|
397
|
+
to before the index change.
|
|
398
|
+
|
|
347
399
|
## Decision Log **[AGENT-MAINTAINED — append only]**
|
|
348
400
|
|
|
349
401
|
| Date | Decision | Why |
|
|
@@ -406,6 +458,10 @@ animation.
|
|
|
406
458
|
| 2026-07-20 | Bloom resolution is capped with `composer.setPixelRatio(1)`, not the `UnrealBloomPass` constructor | `EffectComposer.setSize` forwards the canvas size to every pass on resize, overwriting the constructor's `resolution`. The pixel ratio is the cap that survives |
|
|
407
459
|
| 2026-07-20 | **Corrects the row above**: bloom is capped by wrapping the bloom pass's own `setSize`, and the composer keeps the renderer's pixel ratio | The pixel ratio *did* cap bloom, but `EffectComposer.setSize` multiplies it into `renderTarget1/2` and every pass, so the whole scene rendered at 1x and upscaled — measured 1280x611 scene passes on a 2560x1221 buffer at dpr 2. Wrapping the one pass caps the one expensive thing: scene now 2560x1221, bloom mip 0 800x382 (1280x611 uncapped), `?benchmark` at 951 nodes unchanged at 928.8 → 961.5 fps median |
|
|
408
460
|
| 2026-07-20 | **Corrects "binding is one-shot"** (2026-07-19 picker row): binding is one-*at-a-time*. `serve_project` attaches `PickerConfig(browse_root=Path.home())` too, so a `codemble <path>` run also exposes the picker endpoints after a reset, and browse then enumerates non-hidden directories under `$HOME` | The Switch project control has to work without a process restart, which is what that config is for — but the earlier row still claimed a permanent 409 for the path-opened flow, and this file is the source of truth. The home jail and the Host-header allowlist are unchanged; only the "one-shot" claim was false. An app built with no `PickerConfig` at all remains genuinely one-shot and refuses reset |
|
|
461
|
+
| 2026-07-20 | `CODEMBLE_DATA_DIR` owns every home-directory path — progress, the narration cache, and the `config` file — through one `codemble/paths.py` helper; the test suite additionally clears every provider variable `StudyService.from_environment` reads | The variable redirected progress only, while `StudyService` hardcoded `Path.home()` for the other two, so `create_app`'s default study service read the developer's real config and `ANTHROPIC_API_KEY`. Two server tests GET `/explanation` and assert only that a `status` key came back — true of `no_key`, `ready`, and `error` alike — so on a machine with a key they made a real billed API call and cached the reply under the developer's home while still passing. Redirecting the directory does nothing about the process environment, which is why the suite must clear the keys as well. No new variable is introduced, the default stays `~/.codemble`, and explicit `environ`/`config_path`/`cache_root` arguments still win over both channels |
|
|
462
|
+
| 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
|
+
| 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
|
+
| 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 |
|
|
409
465
|
|
|
410
466
|
## Non-Goals — do NOT build (point here when asked)
|
|
411
467
|
|
|
@@ -446,7 +502,7 @@ animation.
|
|
|
446
502
|
|
|
447
503
|
## Edge cases & limits
|
|
448
504
|
|
|
449
|
-
- >~
|
|
505
|
+
- >~1,000 supported source files → prompt to scope to a subdirectory (LOD arrives Phase 2)
|
|
450
506
|
- No clear entrypoint → ranked candidates; user picks Home
|
|
451
507
|
- Syntax errors / partial parses → parse what you can, flag the rest, never crash
|
|
452
508
|
- Missing/invalid key → galaxy + structure + checks work; explanations show "add your key"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codemble
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -164,6 +164,17 @@ of your own code becomes a sky you understand.
|
|
|
164
164
|
and this one has no model configured at all.
|
|
165
165
|
</sub></p>
|
|
166
166
|
|
|
167
|
+
<p align="center">
|
|
168
|
+
<img src="https://github.com/udhawan97/Codemble/raw/main/docs-site/public/shots/loading.png" alt="Codemble's staged loading screen mapping a large project, with five named stages — finding source files, reading each file, connecting imports and calls, building graph-only checks, placing your galaxy — a live count reading 13 of 900 files, and a cancel button" width="900">
|
|
169
|
+
</p>
|
|
170
|
+
|
|
171
|
+
<p align="center"><sub>
|
|
172
|
+
Large projects (up to roughly 1,000 source files) parse in the background with
|
|
173
|
+
visible staged progress — a real file count while reading, named steps while
|
|
174
|
+
resolving — so a big project never looks like a frozen tab. Cancel any time to
|
|
175
|
+
pick another.
|
|
176
|
+
</sub></p>
|
|
177
|
+
|
|
167
178
|
## Two layers over one graph
|
|
168
179
|
|
|
169
180
|
Codemble draws the same parser evidence two ways, switchable in the header. The
|
|
@@ -259,9 +270,9 @@ game; only the optional prose narration is unavailable.
|
|
|
259
270
|
|
|
260
271
|
- **Supported source:** Python 3.11+, JavaScript/JSX, TypeScript/TSX, and mixed
|
|
261
272
|
projects. Unsupported languages stay outside the graph rather than being guessed.
|
|
262
|
-
- **Scale:** above roughly
|
|
263
|
-
the in-app picker
|
|
264
|
-
`codemble --path ./project/subdirectory`.
|
|
273
|
+
- **Scale:** above roughly 1,000 supported source files, choose a subdirectory —
|
|
274
|
+
the in-app picker offers the busiest scopes as buttons and accepts a typed
|
|
275
|
+
path, or pass `codemble --path ./project/subdirectory`.
|
|
265
276
|
- **Ambiguous Home:** choose a parser-ranked entrypoint in the app or pass
|
|
266
277
|
`--entrypoint NODE_ID`.
|
|
267
278
|
- **Broken source:** syntax errors remain visible; Codemble maps safe partial
|
|
@@ -132,6 +132,17 @@ of your own code becomes a sky you understand.
|
|
|
132
132
|
and this one has no model configured at all.
|
|
133
133
|
</sub></p>
|
|
134
134
|
|
|
135
|
+
<p align="center">
|
|
136
|
+
<img src="https://github.com/udhawan97/Codemble/raw/main/docs-site/public/shots/loading.png" alt="Codemble's staged loading screen mapping a large project, with five named stages — finding source files, reading each file, connecting imports and calls, building graph-only checks, placing your galaxy — a live count reading 13 of 900 files, and a cancel button" width="900">
|
|
137
|
+
</p>
|
|
138
|
+
|
|
139
|
+
<p align="center"><sub>
|
|
140
|
+
Large projects (up to roughly 1,000 source files) parse in the background with
|
|
141
|
+
visible staged progress — a real file count while reading, named steps while
|
|
142
|
+
resolving — so a big project never looks like a frozen tab. Cancel any time to
|
|
143
|
+
pick another.
|
|
144
|
+
</sub></p>
|
|
145
|
+
|
|
135
146
|
## Two layers over one graph
|
|
136
147
|
|
|
137
148
|
Codemble draws the same parser evidence two ways, switchable in the header. The
|
|
@@ -227,9 +238,9 @@ game; only the optional prose narration is unavailable.
|
|
|
227
238
|
|
|
228
239
|
- **Supported source:** Python 3.11+, JavaScript/JSX, TypeScript/TSX, and mixed
|
|
229
240
|
projects. Unsupported languages stay outside the graph rather than being guessed.
|
|
230
|
-
- **Scale:** above roughly
|
|
231
|
-
the in-app picker
|
|
232
|
-
`codemble --path ./project/subdirectory`.
|
|
241
|
+
- **Scale:** above roughly 1,000 supported source files, choose a subdirectory —
|
|
242
|
+
the in-app picker offers the busiest scopes as buttons and accepts a typed
|
|
243
|
+
path, or pass `codemble --path ./project/subdirectory`.
|
|
233
244
|
- **Ambiguous Home:** choose a parser-ranked entrypoint in the app or pass
|
|
234
245
|
`--entrypoint NODE_ID`.
|
|
235
246
|
- **Broken source:** syntax errors remain visible; Codemble maps safe partial
|
|
@@ -8,8 +8,8 @@ happened.
|
|
|
8
8
|
|
|
9
9
|
## Protect your project
|
|
10
10
|
|
|
11
|
-
- Use a local project with at most
|
|
12
|
-
subdirectory when Codemble prompts (`--path` from the CLI).
|
|
11
|
+
- Use a local project with at most 1,000 supported source files, or choose a
|
|
12
|
+
smaller subdirectory when Codemble prompts (`--path` from the CLI).
|
|
13
13
|
- Do not paste source code, API keys, credentials, or private project names into
|
|
14
14
|
feedback. Codemble itself runs locally and has no telemetry.
|
|
15
15
|
- An LLM key is optional. The galaxy, source, Lens, checks, and lighting work
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Per-file parse progress and cancellation, without widening the adapter seam.
|
|
2
|
+
|
|
3
|
+
``LanguageAdapter`` keeps its four public methods exactly as they are. A
|
|
4
|
+
parse that wants progress binds a hook for its own thread with
|
|
5
|
+
``reporting_files``; each adapter's private per-file helper calls
|
|
6
|
+
``note_file_parsed`` once per source file it finishes reading. That single
|
|
7
|
+
call site is also the only cancellation check point, so "between files" means
|
|
8
|
+
exactly one place in each adapter.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import threading
|
|
14
|
+
from collections.abc import Callable, Iterator
|
|
15
|
+
from contextlib import contextmanager
|
|
16
|
+
from typing import Protocol, runtime_checkable
|
|
17
|
+
|
|
18
|
+
_local = threading.local()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ParseCancelled(Exception):
|
|
22
|
+
"""A running parse was cancelled; no graph will be produced."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@runtime_checkable
|
|
26
|
+
class ParseProgress(Protocol):
|
|
27
|
+
"""The reporting surface ``ProjectParser`` writes one parse's state to."""
|
|
28
|
+
|
|
29
|
+
def stage(self, stage: str) -> None:
|
|
30
|
+
"""Report the stage now running."""
|
|
31
|
+
|
|
32
|
+
def files_total(self, total: int) -> None:
|
|
33
|
+
"""Report how many source files this parse will read."""
|
|
34
|
+
|
|
35
|
+
def file_parsed(self) -> None:
|
|
36
|
+
"""Report one finished file; raise ``ParseCancelled`` to stop."""
|
|
37
|
+
|
|
38
|
+
def detail(self, detail: str) -> None:
|
|
39
|
+
"""Report the sub-step now running within a stage.
|
|
40
|
+
|
|
41
|
+
The ``resolving`` stage is one label over several seconds of cross-file
|
|
42
|
+
work; a sub-step keeps the screen honestly changing instead of frozen.
|
|
43
|
+
It is advisory only: unlike ``file_parsed`` it never cancels, so a
|
|
44
|
+
reporter that ignores it loses nothing but the finer copy.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@contextmanager
|
|
49
|
+
def reporting_files(on_file: Callable[[], None] | None) -> Iterator[None]:
|
|
50
|
+
"""Bind ``on_file`` for this thread for the duration of one parse."""
|
|
51
|
+
|
|
52
|
+
previous = getattr(_local, "on_file", None)
|
|
53
|
+
_local.on_file = on_file
|
|
54
|
+
try:
|
|
55
|
+
yield
|
|
56
|
+
finally:
|
|
57
|
+
_local.on_file = previous
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def note_file_parsed() -> None:
|
|
61
|
+
"""Report one finished source file; a no-op when nobody is listening."""
|
|
62
|
+
|
|
63
|
+
on_file = getattr(_local, "on_file", None)
|
|
64
|
+
if on_file is not None:
|
|
65
|
+
on_file()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@contextmanager
|
|
69
|
+
def reporting_detail(on_detail: Callable[[str], None] | None) -> Iterator[None]:
|
|
70
|
+
"""Bind ``on_detail`` for this thread for the duration of one parse.
|
|
71
|
+
|
|
72
|
+
Separate from ``reporting_files`` because resolving sub-steps outlive the
|
|
73
|
+
file-reading loop: they run through cross-file resolution and composition.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
previous = getattr(_local, "on_detail", None)
|
|
77
|
+
_local.on_detail = on_detail
|
|
78
|
+
try:
|
|
79
|
+
yield
|
|
80
|
+
finally:
|
|
81
|
+
_local.on_detail = previous
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def note_detail(detail: str) -> None:
|
|
85
|
+
"""Report the resolving sub-step now running; a no-op with no listener."""
|
|
86
|
+
|
|
87
|
+
on_detail = getattr(_local, "on_detail", None)
|
|
88
|
+
if on_detail is not None:
|
|
89
|
+
on_detail(detail)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
__all__ = [
|
|
93
|
+
"ParseCancelled",
|
|
94
|
+
"ParseProgress",
|
|
95
|
+
"note_detail",
|
|
96
|
+
"note_file_parsed",
|
|
97
|
+
"reporting_detail",
|
|
98
|
+
"reporting_files",
|
|
99
|
+
]
|
|
@@ -13,6 +13,12 @@ from codemble.adapters.discovery import (
|
|
|
13
13
|
SourceOwnership,
|
|
14
14
|
discover_project_sources,
|
|
15
15
|
)
|
|
16
|
+
from codemble.adapters.parse_progress import (
|
|
17
|
+
ParseProgress,
|
|
18
|
+
note_detail,
|
|
19
|
+
reporting_detail,
|
|
20
|
+
reporting_files,
|
|
21
|
+
)
|
|
16
22
|
from codemble.graph.finalize import GraphFinalizationError, finalize_graph
|
|
17
23
|
|
|
18
24
|
|
|
@@ -26,10 +32,14 @@ class ProjectScaleError(ProjectParseError):
|
|
|
26
32
|
def __init__(self, intake: ProjectIntake, scale_cap: int) -> None:
|
|
27
33
|
self.intake = intake
|
|
28
34
|
self.scale_cap = scale_cap
|
|
35
|
+
scopes = ", ".join(
|
|
36
|
+
f"{directory} ({count})" for directory, count in intake.scope_counts()[:6]
|
|
37
|
+
)
|
|
38
|
+
suggestion = f" Busiest scopes: {scopes}." if scopes else ""
|
|
29
39
|
super().__init__(
|
|
30
40
|
f"found {len(intake.files)} supported source files; Codemble is capped at "
|
|
31
41
|
f"{scale_cap}. Re-run with `codemble --path PATH` to choose a project "
|
|
32
|
-
"subdirectory."
|
|
42
|
+
f"subdirectory.{suggestion}"
|
|
33
43
|
)
|
|
34
44
|
|
|
35
45
|
|
|
@@ -85,7 +95,9 @@ class ProjectParser:
|
|
|
85
95
|
|
|
86
96
|
return tuple(adapter.language for adapter in self._adapters)
|
|
87
97
|
|
|
88
|
-
|
|
98
|
+
# Raised from 300 with the Phase C threaded parse and staged loading
|
|
99
|
+
# screen; LOD and clustering remain Phase 2.
|
|
100
|
+
scale_cap = 1000
|
|
89
101
|
|
|
90
102
|
def intake(self, path: Path, *, explicit: bool = False) -> ProjectIntake:
|
|
91
103
|
"""Resolve one project scope and every adapter's owned files."""
|
|
@@ -133,30 +145,52 @@ class ProjectParser:
|
|
|
133
145
|
*,
|
|
134
146
|
entrypoint: str | None = None,
|
|
135
147
|
explicit: bool = False,
|
|
148
|
+
progress: ParseProgress | None = None,
|
|
136
149
|
) -> Graph:
|
|
137
150
|
"""Parse every detected language and return one deterministic graph."""
|
|
138
151
|
|
|
139
|
-
|
|
140
|
-
source
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
152
|
+
if isinstance(source, ProjectIntake):
|
|
153
|
+
intake = source
|
|
154
|
+
else:
|
|
155
|
+
if progress is not None:
|
|
156
|
+
progress.stage("discovering")
|
|
157
|
+
intake = self.intake(source, explicit=explicit)
|
|
158
|
+
owned = {
|
|
159
|
+
adapter.language: intake._files_for(adapter.language)
|
|
160
|
+
for adapter in self._adapters
|
|
161
|
+
}
|
|
162
|
+
if progress is not None:
|
|
163
|
+
# The counter totals the files adapters will actually read, which
|
|
164
|
+
# is what ``note_file_parsed`` counts. ``intake.files`` is the
|
|
165
|
+
# deduplicated union and would drift if two adapters ever shared
|
|
166
|
+
# an extension.
|
|
167
|
+
progress.files_total(sum(len(files) for files in owned.values()))
|
|
168
|
+
progress.stage("parsing")
|
|
144
169
|
graphs: list[Graph] = []
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
170
|
+
on_file = progress.file_parsed if progress is not None else None
|
|
171
|
+
# ``detail`` outlives the file-read loop: the adapters narrate their
|
|
172
|
+
# cross-file passes and composition narrates the merge, all under the
|
|
173
|
+
# single ``resolving`` stage the design spec fixes.
|
|
174
|
+
on_detail = getattr(progress, "detail", None) if progress is not None else None
|
|
175
|
+
with reporting_detail(on_detail), reporting_files(on_file):
|
|
176
|
+
for adapter in self._adapters:
|
|
177
|
+
files = owned[adapter.language]
|
|
178
|
+
if not files:
|
|
179
|
+
continue
|
|
180
|
+
try:
|
|
181
|
+
graphs.append(adapter.parse_files(intake.root, files))
|
|
182
|
+
except AdapterParseError as error:
|
|
183
|
+
raise ProjectParseError(str(error)) from error
|
|
184
|
+
if progress is not None:
|
|
185
|
+
progress.stage("resolving")
|
|
186
|
+
return _compose_graphs(tuple(graphs), intake.root, entrypoint)
|
|
154
187
|
|
|
155
188
|
def _compose_graphs(
|
|
156
189
|
graphs: tuple[Graph, ...],
|
|
157
190
|
project_root: Path,
|
|
158
191
|
entrypoint: str | None,
|
|
159
192
|
) -> Graph:
|
|
193
|
+
note_detail("Composing your project")
|
|
160
194
|
nodes: list[Node] = []
|
|
161
195
|
edges = []
|
|
162
196
|
annotations = []
|
|
@@ -12,6 +12,7 @@ from pathlib import Path
|
|
|
12
12
|
|
|
13
13
|
from codemble.adapters.base import AdapterParseError, ConceptAnnotation, Edge, Graph, Node
|
|
14
14
|
from codemble.adapters.discovery import SourceDiscoveryError, discover_source_files
|
|
15
|
+
from codemble.adapters.parse_progress import note_detail, note_file_parsed
|
|
15
16
|
from codemble.graph.finalize import GraphFinalizationError, finalize_graph
|
|
16
17
|
|
|
17
18
|
_APP_FACTORIES = {"FastAPI", "Flask", "Typer"}
|
|
@@ -350,6 +351,10 @@ class PythonAstAdapter:
|
|
|
350
351
|
module_bindings: dict[str, list[_ImportBinding]] = defaultdict(list)
|
|
351
352
|
scope_bindings: dict[str, list[_ImportBinding]] = defaultdict(list)
|
|
352
353
|
|
|
354
|
+
# The stage counter is full once the last file is read; from here the
|
|
355
|
+
# resolving stage does its cross-file work, so it narrates the real
|
|
356
|
+
# passes rather than sitting on one frozen label.
|
|
357
|
+
note_detail("Resolving imports")
|
|
353
358
|
for parsed in parsed_files:
|
|
354
359
|
if parsed.tree is None:
|
|
355
360
|
continue
|
|
@@ -371,6 +376,7 @@ class PythonAstAdapter:
|
|
|
371
376
|
_, bindings = _resolve_import(parsed, syntax, modules, node_by_id)
|
|
372
377
|
scope_bindings[definition.node_id].extend(bindings)
|
|
373
378
|
|
|
379
|
+
note_detail("Resolving calls")
|
|
374
380
|
call_edges: list[Edge] = []
|
|
375
381
|
children_by_parent: dict[str, list[Node]] = defaultdict(list)
|
|
376
382
|
for definition in definitions:
|
|
@@ -401,6 +407,7 @@ class PythonAstAdapter:
|
|
|
401
407
|
)
|
|
402
408
|
)
|
|
403
409
|
|
|
410
|
+
note_detail("Reading language concepts")
|
|
404
411
|
all_edges = [*import_edges, *call_edges]
|
|
405
412
|
annotations: list[ConceptAnnotation] = []
|
|
406
413
|
for parsed in parsed_files:
|
|
@@ -430,6 +437,7 @@ class PythonAstAdapter:
|
|
|
430
437
|
parsed.relative_path for parsed in parsed_files if parsed.tree is None
|
|
431
438
|
),
|
|
432
439
|
)
|
|
440
|
+
note_detail("Building the galaxy map")
|
|
433
441
|
try:
|
|
434
442
|
return finalize_graph(draft, entrypoint=entrypoint)
|
|
435
443
|
except GraphFinalizationError as error:
|
|
@@ -488,7 +496,7 @@ def _parse_file(path: Path, project_root: Path) -> _ParsedFile:
|
|
|
488
496
|
except (SyntaxError, UnicodeDecodeError):
|
|
489
497
|
source = raw.decode("utf-8", errors="replace")
|
|
490
498
|
tree = None
|
|
491
|
-
|
|
499
|
+
parsed = _ParsedFile(
|
|
492
500
|
path=path,
|
|
493
501
|
relative_path=relative.as_posix(),
|
|
494
502
|
module=_module_name(relative, project_root),
|
|
@@ -496,6 +504,8 @@ def _parse_file(path: Path, project_root: Path) -> _ParsedFile:
|
|
|
496
504
|
digest=digest,
|
|
497
505
|
tree=tree,
|
|
498
506
|
)
|
|
507
|
+
note_file_parsed()
|
|
508
|
+
return parsed
|
|
499
509
|
|
|
500
510
|
|
|
501
511
|
def _module_name(relative: Path, project_root: Path) -> str:
|
|
@@ -757,10 +767,18 @@ def _call_leaf_name(expression: ast.expr) -> str | None:
|
|
|
757
767
|
|
|
758
768
|
|
|
759
769
|
def _module_from_node_id(node_id: str, modules: set[str]) -> str:
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
770
|
+
# Longest module that is a dotted prefix of ``node_id``. Walking the id's
|
|
771
|
+
# own prefixes (longest first) is byte-identical to scanning every module,
|
|
772
|
+
# but O(id depth) set lookups instead of O(modules) startswith calls -- the
|
|
773
|
+
# difference between 47M string scans and a few thousand on a 1k-file parse.
|
|
774
|
+
candidate = node_id
|
|
775
|
+
while True:
|
|
776
|
+
if candidate in modules:
|
|
777
|
+
return candidate
|
|
778
|
+
cut = candidate.rfind(".")
|
|
779
|
+
if cut == -1:
|
|
780
|
+
raise ValueError(f"no module owns node id: {node_id}")
|
|
781
|
+
candidate = candidate[:cut]
|
|
764
782
|
|
|
765
783
|
|
|
766
784
|
__all__ = ["PythonAstAdapter", "PythonParseError"]
|
|
@@ -20,6 +20,7 @@ from codemble.adapters.base import (
|
|
|
20
20
|
Node,
|
|
21
21
|
)
|
|
22
22
|
from codemble.adapters.discovery import SourceDiscoveryError, discover_source_files
|
|
23
|
+
from codemble.adapters.parse_progress import note_file_parsed
|
|
23
24
|
from codemble.graph.finalize import GraphFinalizationError, finalize_graph
|
|
24
25
|
|
|
25
26
|
_JAVASCRIPT_EXTENSIONS = frozenset({".js", ".jsx", ".mjs", ".cjs"})
|
|
@@ -344,7 +345,7 @@ def _parse_file(path: Path, project_root: Path) -> _ParsedFile:
|
|
|
344
345
|
relative = path.relative_to(project_root).as_posix()
|
|
345
346
|
language = "javascript" if path.suffix.lower() in _JAVASCRIPT_EXTENSIONS else "typescript"
|
|
346
347
|
parser = Parser(_language_for(path.suffix.lower()))
|
|
347
|
-
|
|
348
|
+
parsed = _ParsedFile(
|
|
348
349
|
path=path,
|
|
349
350
|
project_root=project_root,
|
|
350
351
|
relative_path=relative,
|
|
@@ -355,6 +356,8 @@ def _parse_file(path: Path, project_root: Path) -> _ParsedFile:
|
|
|
355
356
|
digest=hashlib.sha256(raw).hexdigest(),
|
|
356
357
|
tree=parser.parse(raw),
|
|
357
358
|
)
|
|
359
|
+
note_file_parsed()
|
|
360
|
+
return parsed
|
|
358
361
|
|
|
359
362
|
|
|
360
363
|
def _language_for(extension: str) -> Language:
|