codeplain 0.3.7__tar.gz → 0.3.8__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 (257) hide show
  1. codeplain-0.3.8/.github/workflows/publish-to-pypi.yml +70 -0
  2. {codeplain-0.3.7 → codeplain-0.3.8}/PKG-INFO +4 -4
  3. {codeplain-0.3.7 → codeplain-0.3.8}/cli_output/render_summary.py +2 -2
  4. {codeplain-0.3.7 → codeplain-0.3.8}/cli_output/status.py +2 -2
  5. {codeplain-0.3.7 → codeplain-0.3.8}/codeplain_REST_api.py +6 -2
  6. {codeplain-0.3.7 → codeplain-0.3.8}/install/bash/examples.sh +5 -11
  7. {codeplain-0.3.7 → codeplain-0.3.8}/install/bash/install.sh +3 -1
  8. {codeplain-0.3.7 → codeplain-0.3.8}/install/powershell/examples.ps1 +5 -11
  9. {codeplain-0.3.7 → codeplain-0.3.8}/install/powershell/install.ps1 +5 -1
  10. {codeplain-0.3.7 → codeplain-0.3.8}/plain2code.py +1 -0
  11. {codeplain-0.3.7 → codeplain-0.3.8}/plain2code_console.py +32 -21
  12. {codeplain-0.3.7 → codeplain-0.3.8}/plain2code_events.py +1 -0
  13. {codeplain-0.3.7 → codeplain-0.3.8}/plain2code_logger.py +3 -7
  14. {codeplain-0.3.7 → codeplain-0.3.8}/plain2code_state.py +17 -1
  15. {codeplain-0.3.7 → codeplain-0.3.8}/plain2code_utils.py +17 -13
  16. {codeplain-0.3.7 → codeplain-0.3.8}/pyproject.toml +8 -5
  17. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/actions/create_dist.py +2 -2
  18. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/actions/exit_with_error.py +2 -1
  19. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/actions/fix_conformance_test.py +4 -2
  20. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/actions/render_functional_requirement.py +4 -3
  21. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/render_context.py +5 -3
  22. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/render_utils.py +14 -8
  23. {codeplain-0.3.7 → codeplain-0.3.8}/requirements.txt +3 -3
  24. codeplain-0.3.8/system_config.py +68 -0
  25. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_cli_output.py +2 -2
  26. codeplain-0.3.8/tests/test_console_log_styles.py +112 -0
  27. codeplain-0.3.8/tests/test_plain2code_state.py +36 -0
  28. codeplain-0.3.8/tests/test_plain2code_utils.py +36 -0
  29. codeplain-0.3.8/tests/test_tui_usage.py +209 -0
  30. codeplain-0.3.8/tests/test_usage_summary.py +28 -0
  31. {codeplain-0.3.7 → codeplain-0.3.8}/tui/components.py +29 -20
  32. {codeplain-0.3.7 → codeplain-0.3.8}/tui/plain2code_tui.py +71 -2
  33. {codeplain-0.3.7 → codeplain-0.3.8}/tui/styles.css +4 -0
  34. {codeplain-0.3.7 → codeplain-0.3.8}/tui/widget_helpers.py +20 -1
  35. codeplain-0.3.8/usage_summary.py +30 -0
  36. codeplain-0.3.7/.claude/settings.local.json +0 -66
  37. codeplain-0.3.7/.github/workflows/publish-to-pypi.yml +0 -109
  38. codeplain-0.3.7/.zed/settings.json +0 -46
  39. codeplain-0.3.7/_version.py +0 -1
  40. codeplain-0.3.7/system_config.py +0 -34
  41. {codeplain-0.3.7 → codeplain-0.3.8}/.claude/settings.json +0 -0
  42. {codeplain-0.3.7 → codeplain-0.3.8}/.flake8 +0 -0
  43. {codeplain-0.3.7 → codeplain-0.3.8}/.github/dependabot.yml +0 -0
  44. {codeplain-0.3.7 → codeplain-0.3.8}/.github/workflows/e2e.yml +0 -0
  45. {codeplain-0.3.7 → codeplain-0.3.8}/.github/workflows/lint-and-test.yml +0 -0
  46. {codeplain-0.3.7 → codeplain-0.3.8}/.github/workflows/nofity-slack-on-main-merge.yml +0 -0
  47. {codeplain-0.3.7 → codeplain-0.3.8}/.github/workflows/publish-install-script.yml +0 -0
  48. {codeplain-0.3.7 → codeplain-0.3.8}/.gitignore +0 -0
  49. {codeplain-0.3.7 → codeplain-0.3.8}/.vscode/launch.json +0 -0
  50. {codeplain-0.3.7 → codeplain-0.3.8}/.vscode/settings.json +0 -0
  51. {codeplain-0.3.7 → codeplain-0.3.8}/CLAUDE.md +0 -0
  52. {codeplain-0.3.7 → codeplain-0.3.8}/LICENSE +0 -0
  53. {codeplain-0.3.7 → codeplain-0.3.8}/README.md +0 -0
  54. {codeplain-0.3.7 → codeplain-0.3.8}/change_detection.py +0 -0
  55. {codeplain-0.3.7 → codeplain-0.3.8}/cli_output/__init__.py +0 -0
  56. {codeplain-0.3.7 → codeplain-0.3.8}/cli_output/dry_run.py +0 -0
  57. {codeplain-0.3.7 → codeplain-0.3.8}/concept_utils.py +0 -0
  58. {codeplain-0.3.7 → codeplain-0.3.8}/config/__init__.py +0 -0
  59. {codeplain-0.3.7 → codeplain-0.3.8}/config/system_config.yaml +0 -0
  60. {codeplain-0.3.7 → codeplain-0.3.8}/diff_utils.py +0 -0
  61. {codeplain-0.3.7 → codeplain-0.3.8}/docs/generate_cli.py +0 -0
  62. {codeplain-0.3.7 → codeplain-0.3.8}/docs/plain2code_cli.md +0 -0
  63. {codeplain-0.3.7 → codeplain-0.3.8}/docs/starting_a_plain_project_from_scratch.md +0 -0
  64. {codeplain-0.3.7 → codeplain-0.3.8}/event_bus.py +0 -0
  65. {codeplain-0.3.7 → codeplain-0.3.8}/examples/example_hello_world_golang/config.yaml +0 -0
  66. {codeplain-0.3.7 → codeplain-0.3.8}/examples/example_hello_world_golang/harness_tests/hello_world_test.go +0 -0
  67. {codeplain-0.3.7 → codeplain-0.3.8}/examples/example_hello_world_golang/hello_world_golang.plain +0 -0
  68. {codeplain-0.3.7 → codeplain-0.3.8}/examples/example_hello_world_golang/run.sh +0 -0
  69. {codeplain-0.3.7 → codeplain-0.3.8}/examples/example_hello_world_python/config.yaml +0 -0
  70. {codeplain-0.3.7 → codeplain-0.3.8}/examples/example_hello_world_python/harness_tests/hello_world_display/test_hello_world.py +0 -0
  71. {codeplain-0.3.7 → codeplain-0.3.8}/examples/example_hello_world_python/hello_world_python.plain +0 -0
  72. {codeplain-0.3.7 → codeplain-0.3.8}/examples/example_hello_world_python/run.sh +0 -0
  73. {codeplain-0.3.7 → codeplain-0.3.8}/examples/example_hello_world_react/config.yaml +0 -0
  74. {codeplain-0.3.7 → codeplain-0.3.8}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/e2e/hello_world.cy.ts +0 -0
  75. {codeplain-0.3.7 → codeplain-0.3.8}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/support/e2e.ts +0 -0
  76. {codeplain-0.3.7 → codeplain-0.3.8}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress.config.ts +0 -0
  77. {codeplain-0.3.7 → codeplain-0.3.8}/examples/example_hello_world_react/harness_tests/hello_world_display/package.json +0 -0
  78. {codeplain-0.3.7 → codeplain-0.3.8}/examples/example_hello_world_react/harness_tests/hello_world_display/tsconfig.json +0 -0
  79. {codeplain-0.3.7 → codeplain-0.3.8}/examples/example_hello_world_react/hello_world_react.plain +0 -0
  80. {codeplain-0.3.7 → codeplain-0.3.8}/examples/example_hello_world_react/run.sh +0 -0
  81. {codeplain-0.3.7 → codeplain-0.3.8}/examples/run.sh +0 -0
  82. {codeplain-0.3.7 → codeplain-0.3.8}/file_utils.py +0 -0
  83. {codeplain-0.3.7 → codeplain-0.3.8}/git_utils.py +0 -0
  84. {codeplain-0.3.7 → codeplain-0.3.8}/install/bash/walkthrough.sh +0 -0
  85. {codeplain-0.3.7 → codeplain-0.3.8}/install/powershell/walkthrough.ps1 +0 -0
  86. {codeplain-0.3.7 → codeplain-0.3.8}/memory_management.py +0 -0
  87. {codeplain-0.3.7 → codeplain-0.3.8}/module_renderer.py +0 -0
  88. {codeplain-0.3.7 → codeplain-0.3.8}/partial_rendering.py +0 -0
  89. {codeplain-0.3.7 → codeplain-0.3.8}/path_resolution.py +0 -0
  90. {codeplain-0.3.7 → codeplain-0.3.8}/plain2code_arguments.py +0 -0
  91. {codeplain-0.3.7 → codeplain-0.3.8}/plain2code_cli.md +0 -0
  92. {codeplain-0.3.7 → codeplain-0.3.8}/plain2code_exceptions.py +0 -0
  93. {codeplain-0.3.7 → codeplain-0.3.8}/plain2code_nodes.py +0 -0
  94. {codeplain-0.3.7 → codeplain-0.3.8}/plain2code_read_config.py +0 -0
  95. {codeplain-0.3.7 → codeplain-0.3.8}/plain2code_telemetry.py +0 -0
  96. {codeplain-0.3.7 → codeplain-0.3.8}/plain_file.py +0 -0
  97. {codeplain-0.3.7 → codeplain-0.3.8}/plain_modules.py +0 -0
  98. {codeplain-0.3.7 → codeplain-0.3.8}/plain_spec.py +0 -0
  99. {codeplain-0.3.7 → codeplain-0.3.8}/pytest.ini +0 -0
  100. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/__init__.py +0 -0
  101. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/actions/analyze_specification_ambiguity.py +0 -0
  102. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/actions/base_action.py +0 -0
  103. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/actions/commit_conformance_tests_changes.py +0 -0
  104. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/actions/commit_implementation_code_changes.py +0 -0
  105. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/actions/finish_functional_requirement.py +0 -0
  106. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/actions/fix_unit_tests.py +0 -0
  107. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/actions/prepare_repositories.py +0 -0
  108. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/actions/prepare_testing_environment.py +0 -0
  109. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/actions/refactor_code.py +0 -0
  110. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/actions/render_conformance_tests.py +0 -0
  111. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/actions/run_conformance_tests.py +0 -0
  112. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/actions/run_unit_tests.py +0 -0
  113. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/actions/summarize_conformance_tests.py +0 -0
  114. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/code_renderer.py +0 -0
  115. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/conformance_tests.py +0 -0
  116. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/implementation_code_helpers.py +0 -0
  117. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/render_types.py +0 -0
  118. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/state_machine_config.py +0 -0
  119. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/states.py +0 -0
  120. {codeplain-0.3.7 → codeplain-0.3.8}/render_machine/triggers.py +0 -0
  121. {codeplain-0.3.7 → codeplain-0.3.8}/resources/codeplain_overview.png +0 -0
  122. {codeplain-0.3.7 → codeplain-0.3.8}/resources/plain_example.png +0 -0
  123. {codeplain-0.3.7 → codeplain-0.3.8}/standard_template_library/golang-console-app-template.plain +0 -0
  124. {codeplain-0.3.7 → codeplain-0.3.8}/standard_template_library/python-console-app-template.plain +0 -0
  125. {codeplain-0.3.7 → codeplain-0.3.8}/standard_template_library/typescript-react-app-boilerplate.plain +0 -0
  126. {codeplain-0.3.7 → codeplain-0.3.8}/standard_template_library/typescript-react-app-template.plain +0 -0
  127. {codeplain-0.3.7 → codeplain-0.3.8}/test_scripts/run_conformance_tests_cypress.ps1 +0 -0
  128. {codeplain-0.3.7 → codeplain-0.3.8}/test_scripts/run_conformance_tests_cypress.sh +0 -0
  129. {codeplain-0.3.7 → codeplain-0.3.8}/test_scripts/run_conformance_tests_golang.ps1 +0 -0
  130. {codeplain-0.3.7 → codeplain-0.3.8}/test_scripts/run_conformance_tests_golang.sh +0 -0
  131. {codeplain-0.3.7 → codeplain-0.3.8}/test_scripts/run_conformance_tests_python.ps1 +0 -0
  132. {codeplain-0.3.7 → codeplain-0.3.8}/test_scripts/run_conformance_tests_python.sh +0 -0
  133. {codeplain-0.3.7 → codeplain-0.3.8}/test_scripts/run_unittests_flutter.ps1 +0 -0
  134. {codeplain-0.3.7 → codeplain-0.3.8}/test_scripts/run_unittests_flutter.sh +0 -0
  135. {codeplain-0.3.7 → codeplain-0.3.8}/test_scripts/run_unittests_golang.ps1 +0 -0
  136. {codeplain-0.3.7 → codeplain-0.3.8}/test_scripts/run_unittests_golang.sh +0 -0
  137. {codeplain-0.3.7 → codeplain-0.3.8}/test_scripts/run_unittests_python.ps1 +0 -0
  138. {codeplain-0.3.7 → codeplain-0.3.8}/test_scripts/run_unittests_python.sh +0 -0
  139. {codeplain-0.3.7 → codeplain-0.3.8}/test_scripts/run_unittests_react.ps1 +0 -0
  140. {codeplain-0.3.7 → codeplain-0.3.8}/test_scripts/run_unittests_react.sh +0 -0
  141. {codeplain-0.3.7 → codeplain-0.3.8}/tests/__init__.py +0 -0
  142. {codeplain-0.3.7 → codeplain-0.3.8}/tests/conftest.py +0 -0
  143. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/acceptance_tests_warning/main_requiring_acceptance_tests.plain +0 -0
  144. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/acceptance_tests_warning/required_with_acceptance_tests.plain +0 -0
  145. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/imports/circular_imports_1.plain +0 -0
  146. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/imports/circular_imports_2.plain +0 -0
  147. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/imports/circular_imports_main.plain +0 -0
  148. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/imports/diamond_import_1.plain +0 -0
  149. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/imports/diamond_import_2.plain +0 -0
  150. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/imports/diamond_import_common.plain +0 -0
  151. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/imports/diamond_imports_main.plain +0 -0
  152. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/imports/import_with_functionalities.plain +0 -0
  153. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/imports/imports_module_with_functionalities.plain +0 -0
  154. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/imports/non_existent_import.plain +0 -0
  155. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/partial_rendering/pr_code_var.plain +0 -0
  156. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/partial_rendering/pr_implement.plain +0 -0
  157. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/partial_rendering/pr_leaf.plain +0 -0
  158. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/partial_rendering/pr_middle.plain +0 -0
  159. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/partial_rendering/pr_root.plain +0 -0
  160. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/partial_rendering/pr_solo.plain +0 -0
  161. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfile/add_new_task_modal_specification.yaml +0 -0
  162. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfile/duplicate_specification_heading.plain +0 -0
  163. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfile/empty_functional_specs_section.plain +0 -0
  164. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfile/invalid_specification_order.plain +0 -0
  165. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfile/missing_non_functional_requirements.plain +0 -0
  166. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfile/no_functional_specs_section.plain +0 -0
  167. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfile/plain_source_with_absolute_link.plain +0 -0
  168. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfile/plain_source_with_url_link.plain +0 -0
  169. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfile/task_list_ui_specification.yaml +0 -0
  170. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfile/task_manager_with_reference_links.plain +0 -0
  171. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfile/without_non_functional_requirement.plain +0 -0
  172. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/concept_validation_acceptance_tests.plain +0 -0
  173. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/concept_validation_acceptance_tests_nondefined.plain +0 -0
  174. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/concept_validation_defined_nondefined.plain +0 -0
  175. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/concept_validation_defined_nondefined_2.plain +0 -0
  176. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/concept_validation_definition.plain +0 -0
  177. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/concept_validation_noconcepts.plain +0 -0
  178. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/concept_validation_nonconcept.plain +0 -0
  179. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/concept_validation_nondefined.plain +0 -0
  180. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/concept_validation_redefinition.plain +0 -0
  181. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/concept_validation_several_concepts.plain +0 -0
  182. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/concept_validation_valid.plain +0 -0
  183. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/exported_concepts.plain +0 -0
  184. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/exported_concepts_base.plain +0 -0
  185. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/exported_concepts_example.plain +0 -0
  186. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/exported_concepts_missing.plain +0 -0
  187. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/exported_concepts_missing_example.plain +0 -0
  188. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/exported_concepts_nested.plain +0 -0
  189. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/exported_concepts_nested_example.plain +0 -0
  190. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/exported_concepts_transitive_example.plain +0 -0
  191. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/exported_concepts_transitive_l1.plain +0 -0
  192. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/exported_concepts_transitive_l2.plain +0 -0
  193. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/plain_file_parser_with_comments.plain +0 -0
  194. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/plain_file_with_comments_indented.plain +0 -0
  195. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/regular_plain_source.plain +0 -0
  196. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/required_concepts.plain +0 -0
  197. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/required_concepts_defs.plain +0 -0
  198. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/required_concepts_example.plain +0 -0
  199. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/required_concepts_l1.plain +0 -0
  200. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/required_concepts_l2.plain +0 -0
  201. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/required_concepts_missing.plain +0 -0
  202. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/required_concepts_module.plain +0 -0
  203. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/required_concepts_partial.plain +0 -0
  204. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/required_concepts_partial_duplicate.plain +0 -0
  205. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/topological_sort.plain +0 -0
  206. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/plainfileparser/topological_sort_not_referenced.plain +0 -0
  207. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/requires/circular_requires_main.plain +0 -0
  208. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/requires/circular_requires_sub.plain +0 -0
  209. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/requires/diamond_requires_1.plain +0 -0
  210. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/requires/diamond_requires_2.plain +0 -0
  211. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/requires/diamond_requires_common.plain +0 -0
  212. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/requires/diamond_requires_main.plain +0 -0
  213. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/requires/independent_requires_1.plain +0 -0
  214. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/requires/independent_requires_2.plain +0 -0
  215. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/requires/independent_requires_main.plain +0 -0
  216. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/requires/non_existent_require.plain +0 -0
  217. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/requires/normal_requires_1.plain +0 -0
  218. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/requires/normal_requires_2.plain +0 -0
  219. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/requires/normal_requires_common.plain +0 -0
  220. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/requires/normal_requires_main.plain +0 -0
  221. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/sample_base64_image.txt +0 -0
  222. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/simple.plain +0 -0
  223. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/templates/block_level_include.plain +0 -0
  224. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/templates/code_variables.plain +0 -0
  225. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/templates/header.plain +0 -0
  226. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/templates/implement.plain +0 -0
  227. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/templates/implement_2.plain +0 -0
  228. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/templates/template_include.plain +0 -0
  229. {codeplain-0.3.7 → codeplain-0.3.8}/tests/data/templates/test_hardest_problem.plain +0 -0
  230. {codeplain-0.3.7 → codeplain-0.3.8}/tests/e2e/Dockerfile +0 -0
  231. {codeplain-0.3.7 → codeplain-0.3.8}/tests/e2e/conftest.py +0 -0
  232. {codeplain-0.3.7 → codeplain-0.3.8}/tests/e2e/test_hello_world_python.py +0 -0
  233. {codeplain-0.3.7 → codeplain-0.3.8}/tests/e2e/test_hello_world_python_windows.py +0 -0
  234. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_arg_provenance.py +0 -0
  235. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_change_detection.py +0 -0
  236. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_file_utils.py +0 -0
  237. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_folder_path_resolution.py +0 -0
  238. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_git_utils.py +0 -0
  239. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_imports.py +0 -0
  240. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_log_file_name_resolution.py +0 -0
  241. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_logging_config_path_resolution.py +0 -0
  242. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_partial_rendering.py +0 -0
  243. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_path_resolution.py +0 -0
  244. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_plain2code.py +0 -0
  245. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_plain_modules.py +0 -0
  246. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_plainfile.py +0 -0
  247. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_plainfileparser.py +0 -0
  248. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_plainspec.py +0 -0
  249. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_requires.py +0 -0
  250. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_resolve_config_file.py +0 -0
  251. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_script_path_resolution.py +0 -0
  252. {codeplain-0.3.7 → codeplain-0.3.8}/tests/test_telemetry.py +0 -0
  253. {codeplain-0.3.7 → codeplain-0.3.8}/tui/__init__.py +0 -0
  254. {codeplain-0.3.7 → codeplain-0.3.8}/tui/models.py +0 -0
  255. {codeplain-0.3.7 → codeplain-0.3.8}/tui/plain_module_render_choice_tui.py +0 -0
  256. {codeplain-0.3.7 → codeplain-0.3.8}/tui/spinner.py +0 -0
  257. {codeplain-0.3.7 → codeplain-0.3.8}/tui/state_handlers.py +0 -0
@@ -0,0 +1,70 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ # Least privilege by default: jobs get no GITHUB_TOKEN scopes unless they
8
+ # opt in below.
9
+ permissions: {}
10
+
11
+ jobs:
12
+ build:
13
+ name: Build distribution
14
+ runs-on: ubuntu-latest
15
+ permissions:
16
+ contents: write
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ with:
21
+ # hatch-vcs derives the version from git tags, so we need full history.
22
+ fetch-depth: 0
23
+
24
+ - name: Install uv
25
+ # uv provisions its own Python (honoring requires-python), so no
26
+ # separate actions/setup-python step is needed.
27
+ run: |
28
+ curl -LsSf https://astral.sh/uv/install.sh | sh
29
+ echo "$HOME/.local/bin" >> $GITHUB_PATH
30
+
31
+ - name: Build package
32
+ # hatch-vcs reads the version straight from the release tag.
33
+ run: |
34
+ uv build
35
+ echo "Built distributions:"
36
+ ls -l dist/
37
+
38
+ - name: Store the distribution packages
39
+ uses: actions/upload-artifact@v4
40
+ with:
41
+ name: python-package-distributions
42
+ path: dist/
43
+
44
+ - name: Upload assets to GitHub Release
45
+ env:
46
+ GH_TOKEN: ${{ github.token }}
47
+ run: |
48
+ gh release upload ${{ github.ref_name }} dist/* --clobber
49
+
50
+ publish-to-pypi:
51
+ name: Publish to PyPI
52
+ needs: build
53
+ runs-on: ubuntu-latest
54
+ environment:
55
+ name: pypi
56
+ url: https://pypi.org/p/codeplain
57
+ permissions:
58
+ # Mint a short-lived OIDC token so PyPI Trusted Publishing can
59
+ # authenticate this run — no long-lived API token needed.
60
+ id-token: write
61
+
62
+ steps:
63
+ - name: Download distribution packages
64
+ uses: actions/download-artifact@v4
65
+ with:
66
+ name: python-package-distributions
67
+ path: dist/
68
+
69
+ - name: Publish to PyPI
70
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeplain
3
- Version: 0.3.7
3
+ Version: 0.3.8
4
4
  Summary: Transform plain language specifications into working code
5
5
  License-File: LICENSE
6
6
  Classifier: Environment :: Console
@@ -8,7 +8,7 @@ Classifier: Intended Audience :: Developers
8
8
  Classifier: Operating System :: OS Independent
9
9
  Classifier: Topic :: Software Development :: Code Generators
10
10
  Requires-Python: ==3.11.*
11
- Requires-Dist: gitpython==3.1.50
11
+ Requires-Dist: gitpython==3.1.52
12
12
  Requires-Dist: mistletoe==1.5.1
13
13
  Requires-Dist: networkx==3.6.1
14
14
  Requires-Dist: python-frontmatter==1.3.0
@@ -16,7 +16,7 @@ Requires-Dist: python-liquid2==0.4.0
16
16
  Requires-Dist: pyyaml==6.0.3
17
17
  Requires-Dist: requests==2.34.2
18
18
  Requires-Dist: rich==15.0.0
19
- Requires-Dist: sentry-sdk==2.63.0
19
+ Requires-Dist: sentry-sdk==2.66.0
20
20
  Requires-Dist: textual>=7.5.0
21
21
  Requires-Dist: tiktoken==0.13.0
22
22
  Requires-Dist: transitions==0.9.3
@@ -24,7 +24,7 @@ Provides-Extra: dev
24
24
  Requires-Dist: black==26.5.1; extra == 'dev'
25
25
  Requires-Dist: flake8==7.3.0; extra == 'dev'
26
26
  Requires-Dist: isort==8.0.1; extra == 'dev'
27
- Requires-Dist: mypy==2.1.0; extra == 'dev'
27
+ Requires-Dist: mypy==2.3.0; extra == 'dev'
28
28
  Requires-Dist: pytest==9.1.1; extra == 'dev'
29
29
  Description-Content-Type: text/markdown
30
30
 
@@ -4,7 +4,7 @@ from typing import Optional
4
4
 
5
5
  from plain2code_console import console
6
6
  from plain2code_state import RunState
7
- from plain2code_utils import format_duration_hms
7
+ from usage_summary import format_usage_summary
8
8
 
9
9
 
10
10
  def print_exit_summary(
@@ -24,7 +24,7 @@ def print_exit_summary(
24
24
  msg += f" [#8E8F91]render id:\t\t\t[#FFFFFF]{run_state.render_id}\n"
25
25
  msg += f" [#8E8F91]input file:\t\t\t[#FFFFFF]{spec_filename}\n"
26
26
  msg += f" [#8E8F91]generated code folder:\t[#FFFFFF]{run_state.render_generated_code_path or '-'}\n\n"
27
- msg += f"[#8E8F91]functionalities [#FFFFFF]{run_state.rendered_functionalities} [#8E8F91]used credits [#FFFFFF]{run_state.rendered_functionalities} [#8E8F91]render time [#FFFFFF]{format_duration_hms(run_state.render_time_accumulated)}\n"
27
+ msg += format_usage_summary(run_state.rendered_functionalities, run_state.render_time_accumulated) + "\n"
28
28
  console.print(msg)
29
29
 
30
30
  if not run_state.render_succeeded and error_message:
@@ -32,7 +32,7 @@ def _display_credit_line(plan_credits: dict) -> None:
32
32
  # If naive datetime, assume UTC
33
33
  if dt.tzinfo is None:
34
34
  dt = dt.replace(tzinfo=timezone.utc)
35
- formatted_date = dt.strftime("%b %-d, %Y")
35
+ formatted_date = f"{dt.strftime('%b')} {dt.day}, {dt.year}"
36
36
 
37
37
  # Check if expired
38
38
  now = datetime.now(timezone.utc)
@@ -62,7 +62,7 @@ def _display_bucket_credit_line(bucket: dict, label: str) -> None:
62
62
  # If naive datetime, assume UTC
63
63
  if dt.tzinfo is None:
64
64
  dt = dt.replace(tzinfo=timezone.utc)
65
- formatted_date = dt.strftime("%b %-d, %Y")
65
+ formatted_date = f"{dt.strftime('%b')} {dt.day}, {dt.year}"
66
66
 
67
67
  # Check if expired
68
68
  now = datetime.now(timezone.utc)
@@ -5,6 +5,7 @@ import requests
5
5
  from requests.exceptions import ConnectionError, RequestException, Timeout
6
6
 
7
7
  import plain2code_exceptions
8
+ from plain2code_console import RETRY_COLOR
8
9
  from plain2code_state import RunState
9
10
 
10
11
  MAX_RETRIES = 4
@@ -67,8 +68,11 @@ class CodeplainAPI:
67
68
  connection_error_type = "Network error" if is_connection_error else "Error"
68
69
  if attempt < num_retries:
69
70
  if not silent:
70
- self.console.debug(f"{connection_error_type} on attempt {attempt + 1}/{num_retries + 1}: {error}")
71
- self.console.debug(f"Retrying in {retry_delay} seconds...")
71
+ self.console.debug(
72
+ f" {connection_error_type} on attempt {attempt + 1}/{num_retries + 1}: {error}. "
73
+ f"Retrying in {retry_delay} seconds...",
74
+ color=RETRY_COLOR,
75
+ )
72
76
  time.sleep(retry_delay)
73
77
  # Exponential backoff
74
78
  return retry_delay * 2
@@ -13,7 +13,7 @@ NC="${NC:-\033[0m}"
13
13
 
14
14
  # Examples configuration
15
15
  EXAMPLES_FOLDER_NAME="plainlang-examples"
16
- EXAMPLES_DOWNLOAD_URL="https://github.com/Codeplain-ai/plainlang-examples/archive/refs/tags/0.1.zip"
16
+ EXAMPLES_DOWNLOAD_URL="https://codeplain.ai/examples/unix"
17
17
 
18
18
  # Show current directory and ask for extraction path
19
19
  CURRENT_DIR=$(pwd)
@@ -55,18 +55,12 @@ if [ "$SKIP_DOWNLOAD" = false ]; then
55
55
  if [ $? -eq 0 ] && [ -s "$TEMP_ZIP" ]; then
56
56
  echo -e " ${GRAY}Extracting to ${EXTRACT_PATH}...${NC}"
57
57
 
58
- # Extract the zip file
59
- unzip -q -o "$TEMP_ZIP" -d "$EXTRACT_PATH" 2>/dev/null
58
+ # Extract the zip file (contents are at the zip root, so extract into the target folder)
59
+ EXTRACTED_DIR="${EXTRACT_PATH}/${EXAMPLES_FOLDER_NAME}"
60
+ rm -rf "$EXTRACTED_DIR" 2>/dev/null # Remove existing if present
61
+ unzip -q -o "$TEMP_ZIP" -d "$EXTRACTED_DIR" 2>/dev/null
60
62
 
61
63
  if [ $? -eq 0 ]; then
62
- # Find and rename extracted directory to remove version number
63
- EXTRACTED_DIR="${EXTRACT_PATH}/${EXAMPLES_FOLDER_NAME}"
64
- VERSIONED_DIR=$(find "$EXTRACT_PATH" -maxdepth 1 -type d -name "${EXAMPLES_FOLDER_NAME}-*" | head -1)
65
- if [ -n "$VERSIONED_DIR" ]; then
66
- rm -rf "$EXTRACTED_DIR" 2>/dev/null # Remove existing if present
67
- mv "$VERSIONED_DIR" "$EXTRACTED_DIR"
68
- fi
69
-
70
64
  # Remove the .gitignore file from the root of the extracted directory
71
65
  if [ -f "${EXTRACTED_DIR}/.gitignore" ]; then
72
66
  rm -f "${EXTRACTED_DIR}/.gitignore"
@@ -396,10 +396,12 @@ if [ -n "${CODEPLAIN_API_KEY:-}" ]; then
396
396
  # Ensure the freshly installed tool is findable in this session.
397
397
  export PATH="$HOME/.local/bin:$PATH"
398
398
  echo -e "${GRAY}Verifying your installation...${NC}"
399
- if codeplain --status > /dev/null 2>&1; then
399
+ if verify_output=$(codeplain --status 2>&1); then
400
400
  echo -e "${GREEN}✓${NC} Installation verified."
401
401
  else
402
402
  echo -e "${RED}Something went wrong during installation.${NC}"
403
+ echo -e "${GRAY}Output of 'codeplain --status':${NC}"
404
+ echo "$verify_output"
403
405
  echo -e "${GRAY}Please restart your terminal and try again, or reinstall with:${NC}"
404
406
  echo -e " uv tool install --force codeplain"
405
407
  exit 1
@@ -11,7 +11,7 @@ if (-not $env:NC) { $NC = "$([char]27)[0m" } else
11
11
 
12
12
  # Examples configuration
13
13
  $EXAMPLES_FOLDER_NAME = "plainlang-examples"
14
- $EXAMPLES_DOWNLOAD_URL = "https://github.com/Codeplain-ai/plainlang-examples/archive/refs/tags/0.1.zip"
14
+ $EXAMPLES_DOWNLOAD_URL = "https://codeplain.ai/examples/windows"
15
15
 
16
16
  # Show current directory and ask for extraction path
17
17
  $CURRENT_DIR = Get-Location
@@ -58,18 +58,12 @@ if (-not $SKIP_DOWNLOAD) {
58
58
  Write-Host " ${GRAY}Extracting to ${EXTRACT_PATH}...${NC}"
59
59
 
60
60
  try {
61
- Expand-Archive -Path $TEMP_ZIP -DestinationPath $EXTRACT_PATH -Force
62
-
63
- # Find and rename extracted directory to remove version number
61
+ # Extract the zip file (contents are at the zip root, so extract into the target folder)
64
62
  $EXTRACTED_DIR = Join-Path $EXTRACT_PATH $EXAMPLES_FOLDER_NAME
65
- $VERSIONED_DIR = Get-ChildItem -Path $EXTRACT_PATH -Directory -Filter "${EXAMPLES_FOLDER_NAME}-*" | Select-Object -First 1
66
-
67
- if ($VERSIONED_DIR) {
68
- if (Test-Path $EXTRACTED_DIR) {
69
- Remove-Item -Path $EXTRACTED_DIR -Recurse -Force -ErrorAction SilentlyContinue
70
- }
71
- Rename-Item -Path $VERSIONED_DIR.FullName -NewName $EXAMPLES_FOLDER_NAME
63
+ if (Test-Path $EXTRACTED_DIR) {
64
+ Remove-Item -Path $EXTRACTED_DIR -Recurse -Force -ErrorAction SilentlyContinue
72
65
  }
66
+ Expand-Archive -Path $TEMP_ZIP -DestinationPath $EXTRACTED_DIR -Force
73
67
 
74
68
  # Remove the .gitignore file from the root of the extracted directory
75
69
  $GITIGNORE_PATH = Join-Path $EXTRACTED_DIR ".gitignore"
@@ -426,16 +426,20 @@ if ($downloadExamples -notmatch '^[Nn]$') {
426
426
  if ($env:CODEPLAIN_API_KEY) {
427
427
  Write-Host "${GRAY}Verifying your installation...${NC}"
428
428
  $verifyOk = $false
429
+ $verifyOutput = ""
429
430
  try {
430
- & codeplain --status *> $null
431
+ $verifyOutput = & codeplain --status 2>&1 | Out-String
431
432
  $verifyOk = ($LASTEXITCODE -eq 0)
432
433
  } catch {
434
+ $verifyOutput = $_ | Out-String
433
435
  $verifyOk = $false
434
436
  }
435
437
  if ($verifyOk) {
436
438
  Write-Host "${GREEN}✓${NC} Installation verified."
437
439
  } else {
438
440
  Write-Host "${RED}Something went wrong during installation.${NC}"
441
+ Write-Host "${GRAY}Output of 'codeplain --status':${NC}"
442
+ Write-Host $verifyOutput
439
443
  Write-Host "${GRAY}Please restart your terminal and try again, or reinstall with:${NC}"
440
444
  Write-Host " uv tool install --force codeplain"
441
445
  exit 1
@@ -286,6 +286,7 @@ def render( # noqa: C901
286
286
  render_thread = threading.Thread(target=run_render, daemon=True)
287
287
  app = Plain2CodeTUI(
288
288
  event_bus=event_bus,
289
+ run_state=run_state,
289
290
  on_ready=render_thread.start,
290
291
  render_id=run_state.render_id,
291
292
  unittests_script=args.unittests_script,
@@ -11,6 +11,12 @@ CHARACTERS_TO_TOKENS_RULE_OF_THUMB_RATIO = 4
11
11
 
12
12
  logger = logging.getLogger(plain2code_logger.LOGGER_NAME)
13
13
 
14
+ # Colors for log messages, applied by the terminal and the TUI via the "color"
15
+ # parameter of the console methods. The file log always receives plain text.
16
+ RETRY_COLOR = "#FFB454" # Amber
17
+ SUCCESS_COLOR = "#79FC96" # Green
18
+ MUTED_COLOR = "#888888" # Grey
19
+
14
20
 
15
21
  class Plain2CodeConsole(Console):
16
22
  INFO_STYLE = Style()
@@ -33,30 +39,37 @@ class Plain2CodeConsole(Console):
33
39
  logger.debug(f"Exception: {e}")
34
40
  self.llm_encoding = None
35
41
 
36
- def info(self, *args, **kwargs):
37
- logger.info(" ".join(map(str, args)))
38
- super().print(*args, **kwargs, style=self.INFO_STYLE)
42
+ def info(self, *args, color=None, **kwargs):
43
+ self._log_and_print(logging.INFO, self.INFO_STYLE, args, color, kwargs)
39
44
 
40
- def warning(self, *args, **kwargs):
41
- logger.warning(" ".join(map(str, args)))
42
- super().print(*args, **kwargs, style=self.WARNING_STYLE)
45
+ def warning(self, *args, color=None, **kwargs):
46
+ self._log_and_print(logging.WARNING, self.WARNING_STYLE, args, color, kwargs)
43
47
 
44
- def error(self, *args, **kwargs):
45
- logger.error(" ".join(map(str, args)))
46
- super().print(*args, **kwargs, style=self.ERROR_STYLE)
48
+ def error(self, *args, color=None, **kwargs):
49
+ self._log_and_print(logging.ERROR, self.ERROR_STYLE, args, color, kwargs)
47
50
 
48
- def input(self, *args, **kwargs):
51
+ def input(self, *args, color=None, **kwargs):
49
52
  # We also log input as info so it shows in the toggled view
50
- logger.info(" ".join(map(str, args)))
51
- super().print(*args, **kwargs, style=self.INPUT_STYLE)
53
+ self._log_and_print(logging.INFO, self.INPUT_STYLE, args, color, kwargs)
54
+
55
+ def output(self, *args, color=None, **kwargs):
56
+ self._log_and_print(logging.INFO, self.OUTPUT_STYLE, args, color, kwargs)
52
57
 
53
- def output(self, *args, **kwargs):
54
- logger.info(" ".join(map(str, args)))
55
- super().print(*args, **kwargs, style=self.OUTPUT_STYLE)
58
+ def debug(self, *args, color=None, **kwargs):
59
+ self._log_and_print(logging.DEBUG, self.DEBUG_STYLE, args, color, kwargs)
56
60
 
57
- def debug(self, *args, **kwargs):
58
- logger.debug(" ".join(map(str, args)))
59
- super().print(*args, **kwargs, style=self.DEBUG_STYLE)
61
+ def _log_and_print(self, level, base_style, args, color, kwargs):
62
+ """Log the plain message text, then print it styled to the terminal.
63
+
64
+ The optional color is applied by the terminal (via style) and forwarded to
65
+ the TUI as the "log_color" record attribute; the file log stays plain text.
66
+ """
67
+ logger.log(level, " ".join(map(str, args)), extra={"log_color": color})
68
+ style = base_style + Style(color=color) if color else base_style
69
+ # Log messages must render exactly as logged: don't interpret square brackets
70
+ # in interpolated content (error texts, file names) as Rich markup.
71
+ kwargs.setdefault("markup", False)
72
+ super().print(*args, **kwargs, style=style)
60
73
 
61
74
  def print_list(self, items, style=None):
62
75
  for item in items:
@@ -130,9 +143,7 @@ class Plain2CodeConsole(Console):
130
143
  for resource_name in resources_list:
131
144
  if resource_name["target"] in linked_resources:
132
145
  file_tokens = self._count_tokens(linked_resources[resource_name["target"]])
133
- self.debug(
134
- f"- {resource_name['text']} [#4169E1]({resource_name['target']}, {file_tokens} tokens)[/#4169E1]"
135
- )
146
+ self.debug(f"- {resource_name['text']} ({resource_name['target']}, {file_tokens} tokens)")
136
147
 
137
148
  self.input()
138
149
 
@@ -44,6 +44,7 @@ class LogMessageEmitted(BaseEvent):
44
44
  level: str # e.g., "INFO", "DEBUG", "ERROR"
45
45
  message: str # The actual log message
46
46
  timestamp: str # Formatted timestamp
47
+ log_color: Optional[str] = None # Color for the message (e.g. "#FFB454"); the file log stays plain text
47
48
 
48
49
 
49
50
  @dataclass
@@ -1,5 +1,4 @@
1
1
  import logging
2
- import time
3
2
 
4
3
  from event_bus import EventBus
5
4
  from plain2code_events import LogMessageEmitted
@@ -42,9 +41,7 @@ class ElapsedTimeFormatter(logging.Formatter):
42
41
  def format(self, record):
43
42
  # Calculate elapsed time the same way as LoggingHandler does for the TUI
44
43
  try:
45
- offset_seconds = self.run_state.render_time_accumulated + int(
46
- time.monotonic() - self.run_state.last_render_start_timestamp
47
- )
44
+ offset_seconds = self.run_state.get_live_render_time()
48
45
  except Exception:
49
46
  # If RunState is not available or there's any error, default to 00:00:00
50
47
  offset_seconds = 0
@@ -74,9 +71,7 @@ class LoggingHandler(logging.Handler):
74
71
 
75
72
  def emit(self, record):
76
73
  try:
77
- offset_seconds = self.run_state.render_time_accumulated + int(
78
- time.monotonic() - self.run_state.last_render_start_timestamp
79
- )
74
+ offset_seconds = self.run_state.get_live_render_time()
80
75
 
81
76
  hours = offset_seconds // 3600
82
77
  minutes = (offset_seconds % 3600) // 60
@@ -88,6 +83,7 @@ class LoggingHandler(logging.Handler):
88
83
  level=record.levelname,
89
84
  message=record.getMessage(),
90
85
  timestamp=timestamp,
86
+ log_color=getattr(record, "log_color", None),
91
87
  )
92
88
  self.event_bus.publish(event)
93
89
  except RuntimeError:
@@ -48,7 +48,23 @@ class RunState:
48
48
  self.rendered_functionalities += 1
49
49
 
50
50
  def add_to_render_time(self):
51
- self.render_time_accumulated += int(time.monotonic() - self.last_render_start_timestamp)
51
+ now = time.monotonic()
52
+ self.render_time_accumulated += int(now - self.last_render_start_timestamp)
53
+ # Reset the segment start so get_live_render_time() does not re-count the
54
+ # span that was just banked (e.g. after a render completes).
55
+ self.last_render_start_timestamp = now
56
+
57
+ def get_live_render_time(self) -> int:
58
+ """Render time so far in whole seconds, including the in-progress segment.
59
+
60
+ This is the single source of truth for elapsed render time, shared by the
61
+ log timestamps, the TUI usage line, and the post-render summary. Pause time
62
+ is excluded because ``add_to_render_time`` banks the elapsed span at the
63
+ start of a pause and ``set_last_render_start_timestamp`` restarts the
64
+ segment on resume. The value is only transiently inflated while the code
65
+ sits inside the pause loop itself, which no consumer samples.
66
+ """
67
+ return self.render_time_accumulated + int(time.monotonic() - self.last_render_start_timestamp)
52
68
 
53
69
  def set_last_render_start_timestamp(self):
54
70
  self.last_render_start_timestamp = time.monotonic()
@@ -16,19 +16,23 @@ def find_large_base64_blob(text: str) -> Optional[str]:
16
16
  return match.group(0) if match else None
17
17
 
18
18
 
19
- def format_duration_hms(total_seconds: int) -> str:
20
- """Format a duration in seconds as hours, minutes, and seconds (e.g. ``1h 2m 3.45s``, ``45.67s``)."""
21
- if total_seconds < 0:
22
- total_seconds = 0
23
- h = int(total_seconds // 3600)
24
- m = int((total_seconds % 3600) // 60)
25
- s = total_seconds % 60
26
- if h:
27
- return f"{h}h {m}m {s}s"
28
- if m:
29
- return f"{m}m {s}s"
30
- text = f"{s}".rstrip("0").rstrip(".")
31
- return f"{text}s" if text else "0s"
19
+ def format_duration_hms(total_seconds: float) -> str:
20
+ """Format a whole-second duration compactly (e.g. ``10s``, ``5m 49s``, ``1h 2m``).
21
+
22
+ Fractional seconds are truncated. Durations under a minute render as ``{s}s``,
23
+ under an hour as ``{m}m {s}s``, and beyond as ``{h}h {m}m``.
24
+ """
25
+ elapsed = int(total_seconds)
26
+ if elapsed < 0:
27
+ elapsed = 0
28
+ if elapsed < 60:
29
+ return f"{elapsed}s"
30
+ minutes = elapsed // 60
31
+ seconds = elapsed % 60
32
+ if minutes < 60:
33
+ return f"{minutes}m {seconds}s"
34
+ hours = minutes // 60
35
+ return f"{hours}h {minutes % 60}m"
32
36
 
33
37
 
34
38
  AMBIGUITY_CAUSES = {
@@ -1,5 +1,5 @@
1
1
  [build-system]
2
- requires = ["hatchling"]
2
+ requires = ["hatchling", "hatch-vcs"]
3
3
  build-backend = "hatchling.build"
4
4
 
5
5
  [project]
@@ -20,13 +20,13 @@ dependencies = [
20
20
  "requests==2.34.2",
21
21
  "tiktoken==0.13.0",
22
22
  "PyYAML==6.0.3",
23
- "gitpython==3.1.50",
23
+ "gitpython==3.1.52",
24
24
  "transitions==0.9.3",
25
25
  "textual>=7.5.0",
26
26
  "rich==15.0.0",
27
27
  "python-frontmatter==1.3.0",
28
28
  "networkx==3.6.1",
29
- "sentry-sdk==2.63.0",
29
+ "sentry-sdk==2.66.0",
30
30
  ]
31
31
 
32
32
  [project.optional-dependencies]
@@ -35,14 +35,17 @@ dev = [
35
35
  "flake8==7.3.0",
36
36
  "black==26.5.1",
37
37
  "isort==8.0.1",
38
- "mypy==2.1.0",
38
+ "mypy==2.3.0",
39
39
  ]
40
40
 
41
41
  [project.scripts]
42
42
  codeplain = "plain2code:main"
43
43
 
44
+ # Derive the version from the git tag (e.g. v0.3.8 -> 0.3.8). The version is
45
+ # baked into the package metadata at build time; system_config.py reads it back
46
+ # from that metadata (with a git-tag fallback for uninstalled source checkouts).
44
47
  [tool.hatch.version]
45
- path = "_version.py"
48
+ source = "vcs"
46
49
 
47
50
  [tool.hatch.build.targets.wheel]
48
51
  include = [
@@ -1,7 +1,7 @@
1
1
  from typing import Any
2
2
 
3
3
  import file_utils
4
- from plain2code_console import console
4
+ from plain2code_console import SUCCESS_COLOR, console
5
5
  from render_machine.actions.base_action import BaseAction
6
6
  from render_machine.render_context import RenderContext
7
7
 
@@ -21,6 +21,6 @@ class CreateDist(BaseAction):
21
21
  render_context.conformance_tests.get_module_conformance_tests_folder(render_context.module_name),
22
22
  render_context.conformance_tests_dest,
23
23
  )
24
- console.info(f"[#79FC96]Render of module {render_context.module_name} completed successfully.[/#79FC96]")
24
+ console.info(f"Render of module {render_context.module_name} completed successfully.", color=SUCCESS_COLOR)
25
25
 
26
26
  return self.SUCCESSFUL_OUTCOME, None
@@ -20,7 +20,8 @@ class ExitWithError(BaseAction):
20
20
 
21
21
  if render_context.frid_context is not None:
22
22
  console.info(
23
- f"To continue rendering from the last successfully rendered functionality, provide the [red]--render-from {render_context.frid_context.frid}[/red] flag."
23
+ f"To continue rendering from the last successfully rendered functionality, "
24
+ f"provide the --render-from {render_context.frid_context.frid} flag."
24
25
  )
25
26
 
26
27
  if render_context.run_state.render_id is not None:
@@ -4,7 +4,7 @@ import diff_utils
4
4
  import file_utils
5
5
  import plain_spec
6
6
  from memory_management import MemoryManager
7
- from plain2code_console import console
7
+ from plain2code_console import RETRY_COLOR, console
8
8
  from plain2code_exceptions import InternalClientError
9
9
  from render_machine.actions.base_action import BaseAction
10
10
  from render_machine.implementation_code_helpers import ImplementationCodeHelpers
@@ -144,7 +144,9 @@ class FixConformanceTest(BaseAction):
144
144
  render_context.conformance_tests_running_context.conflicting_module_name = current_testing_module_name
145
145
  render_context.conformance_tests_running_context.conflicting_frid = current_testing_frid
146
146
  console.info(
147
- f"Potential conflicting functionalities detected while fixing conformance tests for functionality {current_testing_frid} in module {current_testing_module_name}."
147
+ f"Potential conflicting functionalities detected while fixing conformance tests "
148
+ f"for functionality {current_testing_frid} in module {current_testing_module_name}.",
149
+ color=RETRY_COLOR,
148
150
  )
149
151
 
150
152
  if issue_reason_code == self.ISSUE_REASON_CODE_CONFORMANCE_TESTS:
@@ -3,7 +3,7 @@ from typing import Any
3
3
  import file_utils
4
4
  import render_machine.render_utils as render_utils
5
5
  from memory_management import MemoryManager
6
- from plain2code_console import console
6
+ from plain2code_console import RETRY_COLOR, console
7
7
  from plain2code_exceptions import FunctionalRequirementTooComplex
8
8
  from render_machine.actions.base_action import BaseAction
9
9
  from render_machine.implementation_code_helpers import ImplementationCodeHelpers
@@ -28,8 +28,9 @@ class RenderFunctionalRequirement(BaseAction):
28
28
 
29
29
  if render_context.frid_context.functional_requirement_render_attempts > 1:
30
30
  console.info(
31
- f"Unittests could not be fixed after rendering the functionality. "
32
- f"Restarting rendering functionality {render_context.frid_context.frid} from scratch."
31
+ f"Unittests could not be fixed after rendering the functionality. "
32
+ f"Restarting rendering functionality {render_context.frid_context.frid} from scratch.",
33
+ color=RETRY_COLOR,
33
34
  )
34
35
 
35
36
  render_utils.revert_changes_for_frid(render_context)
@@ -7,7 +7,7 @@ import git_utils
7
7
  import plain_spec
8
8
  from codeplain_REST_api import CodeplainAPI
9
9
  from event_bus import EventBus
10
- from plain2code_console import console
10
+ from plain2code_console import RETRY_COLOR, console
11
11
  from plain2code_events import RenderContextSnapshot
12
12
  from plain2code_state import RunState
13
13
  from plain_modules import PlainModule
@@ -283,9 +283,11 @@ class RenderContext:
283
283
  self.dispatch_error(error_msg)
284
284
  else:
285
285
  console.info(
286
- f"Failed to adjust the unit tests after implementation code was updated while fixing the conformance tests for functionality {self.frid_context.frid}."
286
+ f"Failed to adjust the unit tests after implementation code was updated while fixing the "
287
+ f"conformance tests for functionality {self.frid_context.frid}. "
288
+ f"Restarting rendering the functionality {self.frid_context.frid} from scratch.",
289
+ color=RETRY_COLOR,
287
290
  )
288
- console.info(f"Restarting rendering the functionality {self.frid_context.frid} from scratch.")
289
291
  self.machine.dispatch(triggers.RESTART_FRID_PROCESSING)
290
292
 
291
293
  def _on_unit_test_limit_exceeded_in_refactoring(self):