codeplain 0.3.11.dev22__tar.gz → 0.3.12__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.
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/PKG-INFO +1 -1
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/codeplain_REST_api.py +82 -33
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/docs/plain2code_cli.md +2 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/plain2code.py +20 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/plain2code_arguments.py +2 -1
- codeplain-0.3.12/render_machine/actions/fix_unit_tests.py +256 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/actions/run_unit_tests.py +9 -0
- codeplain-0.3.12/render_machine/agent_tools.py +270 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/render_context.py +9 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/render_types.py +45 -0
- codeplain-0.3.12/tests/test_agent_tools.py +166 -0
- codeplain-0.3.12/tests/test_fix_unit_tests_action.py +239 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_fix_unit_tests_conformance_context.py +120 -25
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_render_finished_report.py +3 -0
- codeplain-0.3.12/tests/test_terminal_attachment.py +295 -0
- codeplain-0.3.11.dev22/render_machine/actions/fix_unit_tests.py +0 -79
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/.claude/settings.json +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/.flake8 +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/.github/dependabot.yml +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/.github/workflows/e2e.yml +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/.github/workflows/lint-and-test.yml +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/.github/workflows/lint-powershell.yml +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/.github/workflows/nofity-slack-on-main-merge.yml +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/.github/workflows/publish-dev-to-pypi.yml +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/.github/workflows/publish-install-script.yml +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/.github/workflows/publish-to-pypi.yml +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/.gitignore +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/.vscode/launch.json +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/.vscode/settings.json +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/CLAUDE.md +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/CONTRIBUTING.md +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/LICENSE +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/README.md +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/change_detection.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/cli_output/__init__.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/cli_output/dry_run.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/cli_output/render_summary.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/cli_output/status.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/concept_utils.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/config/__init__.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/config/system_config.yaml +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/conformance_fix_journal.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/diff_utils.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/docs/generate_cli.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/docs/starting_a_plain_project_from_scratch.md +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/event_bus.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/examples/example_hello_world_golang/config.yaml +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/examples/example_hello_world_golang/harness_tests/hello_world_test.go +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/examples/example_hello_world_golang/hello_world_golang.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/examples/example_hello_world_golang/run.sh +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/examples/example_hello_world_python/config.yaml +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/examples/example_hello_world_python/harness_tests/hello_world_display/test_hello_world.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/examples/example_hello_world_python/hello_world_python.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/examples/example_hello_world_python/run.sh +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/examples/example_hello_world_react/config.yaml +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/e2e/hello_world.cy.ts +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress/support/e2e.ts +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/examples/example_hello_world_react/harness_tests/hello_world_display/cypress.config.ts +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/examples/example_hello_world_react/harness_tests/hello_world_display/package.json +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/examples/example_hello_world_react/harness_tests/hello_world_display/tsconfig.json +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/examples/example_hello_world_react/hello_world_react.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/examples/example_hello_world_react/run.sh +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/examples/run.sh +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/file_utils.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/git_preflight.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/git_utils.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/install/bash/examples.sh +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/install/bash/install.sh +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/install/bash/walkthrough.sh +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/install/powershell/examples.ps1 +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/install/powershell/install.ps1 +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/install/powershell/walkthrough.ps1 +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/memory_management.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/metadata_utils.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/module_renderer.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/partial_rendering.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/path_resolution.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/plain2code_cli.md +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/plain2code_console.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/plain2code_events.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/plain2code_exceptions.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/plain2code_logger.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/plain2code_nodes.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/plain2code_read_config.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/plain2code_state.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/plain2code_telemetry.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/plain2code_utils.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/plain_file.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/plain_modules.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/plain_spec.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/pyproject.toml +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/pytest.ini +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/__init__.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/actions/analyze_specification_ambiguity.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/actions/base_action.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/actions/commit_conformance_tests_changes.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/actions/commit_implementation_code_changes.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/actions/create_dist.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/actions/distill_conformance_test_memory.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/actions/exit_with_error.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/actions/finish_functional_requirement.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/actions/fix_conformance_test.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/actions/prepare_repositories.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/actions/prepare_testing_environment.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/actions/refactor_code.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/actions/render_conformance_tests.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/actions/render_functional_requirement.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/actions/run_conformance_tests.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/actions/summarize_conformance_tests.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/code_renderer.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/conformance_tests.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/implementation_code_helpers.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/render_utils.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/state_machine_config.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/states.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/render_machine/triggers.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/requirements.txt +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/resources/codeplain_overview.png +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/resources/plain_example.png +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/standard_template_library/golang-console-app-template.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/standard_template_library/python-console-app-template.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/standard_template_library/typescript-react-app-boilerplate.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/standard_template_library/typescript-react-app-template.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/system_config.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/test_scripts/run_conformance_tests_cypress.ps1 +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/test_scripts/run_conformance_tests_cypress.sh +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/test_scripts/run_conformance_tests_golang.ps1 +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/test_scripts/run_conformance_tests_golang.sh +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/test_scripts/run_conformance_tests_python.ps1 +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/test_scripts/run_conformance_tests_python.sh +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/test_scripts/run_unittests_flutter.ps1 +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/test_scripts/run_unittests_flutter.sh +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/test_scripts/run_unittests_golang.ps1 +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/test_scripts/run_unittests_golang.sh +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/test_scripts/run_unittests_python.ps1 +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/test_scripts/run_unittests_python.sh +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/test_scripts/run_unittests_react.ps1 +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/test_scripts/run_unittests_react.sh +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/__init__.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/conftest.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/acceptance_tests_warning/main_requiring_acceptance_tests.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/acceptance_tests_warning/required_with_acceptance_tests.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/imports/circular_imports_1.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/imports/circular_imports_2.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/imports/circular_imports_main.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/imports/diamond_import_1.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/imports/diamond_import_2.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/imports/diamond_import_common.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/imports/diamond_imports_main.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/imports/import_with_functionalities.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/imports/imports_module_with_functionalities.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/imports/non_existent_import.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/partial_rendering/pr_code_var.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/partial_rendering/pr_implement.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/partial_rendering/pr_leaf.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/partial_rendering/pr_middle.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/partial_rendering/pr_root.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/partial_rendering/pr_solo.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfile/add_new_task_modal_specification.yaml +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfile/duplicate_specification_heading.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfile/empty_functional_specs_section.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfile/invalid_specification_order.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfile/missing_non_functional_requirements.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfile/no_functional_specs_section.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfile/plain_source_with_absolute_link.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfile/plain_source_with_missing_link.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfile/plain_source_with_url_link.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfile/task_list_ui_specification.yaml +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfile/task_manager_with_reference_links.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfile/without_non_functional_requirement.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/concept_validation_acceptance_tests.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/concept_validation_acceptance_tests_nondefined.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/concept_validation_defined_nondefined.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/concept_validation_defined_nondefined_2.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/concept_validation_definition.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/concept_validation_noconcepts.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/concept_validation_nonconcept.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/concept_validation_nondefined.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/concept_validation_redefinition.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/concept_validation_several_concepts.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/concept_validation_valid.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/cyclic_definitions.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts_base.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts_defaults_exported.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts_example.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts_imported_exported.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts_inherited_reexported.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts_malformed.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts_missing_definition.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts_missing_definition_example.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts_multiple_per_bullet.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts_multiple_per_bullet_undefined.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts_nested.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts_nested_example.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts_no_definitions.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts_not_declared.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts_not_declared_example.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts_transitive_example.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts_transitive_l1.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/exported_concepts_transitive_l2.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/plain_file_parser_with_comments.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/plain_file_with_comments_indented.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/regular_plain_source.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/required_concepts.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/required_concepts_defs.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/required_concepts_example.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/required_concepts_l1.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/required_concepts_l2.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/required_concepts_missing.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/required_concepts_module.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/required_concepts_partial.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/required_concepts_partial_duplicate.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/requires_without_definitions_example.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/topological_sort.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/plainfileparser/topological_sort_not_referenced.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/requires/circular_requires_main.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/requires/circular_requires_sub.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/requires/diamond_requires_1.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/requires/diamond_requires_2.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/requires/diamond_requires_common.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/requires/diamond_requires_main.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/requires/independent_requires_1.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/requires/independent_requires_2.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/requires/independent_requires_main.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/requires/non_existent_require.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/requires/normal_requires_1.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/requires/normal_requires_2.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/requires/normal_requires_common.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/requires/normal_requires_main.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/sample_base64_image.txt +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/simple.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/templates/block_level_include.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/templates/code_variables.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/templates/header.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/templates/implement.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/templates/implement_2.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/templates/template_include.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/data/templates/test_hardest_problem.plain +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/e2e/Dockerfile +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/e2e/conftest.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/e2e/test_hello_world_python.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/e2e/test_hello_world_python_windows.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_arg_provenance.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_change_detection.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_cli_output.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_conformance_fix_journal.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_console_log_styles.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_distill_conformance_test_memory.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_dry_run_link_validation.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_file_utils.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_folder_path_resolution.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_git_preflight.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_git_utils.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_headless_logging.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_imports.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_log_file_name_resolution.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_logging_config_path_resolution.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_partial_rendering.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_path_resolution.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_plain2code.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_plain2code_state.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_plain2code_utils.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_plain_modules.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_plainfile.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_plainfileparser.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_plainspec.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_prepare_repositories_layout.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_requires.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_resolve_config_file.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_script_path_resolution.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_system_config.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_telemetry.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_tui_components.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_tui_usage.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tests/test_usage_summary.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tui/__init__.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tui/components.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tui/models.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tui/plain2code_tui.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tui/plain_module_render_choice_tui.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tui/spinner.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tui/state_handlers.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tui/styles.css +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/tui/widget_helpers.py +0 -0
- {codeplain-0.3.11.dev22 → codeplain-0.3.12}/usage_summary.py +0 -0
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import socket
|
|
1
2
|
import time
|
|
2
3
|
from typing import Optional
|
|
3
4
|
|
|
4
5
|
import requests
|
|
6
|
+
from requests.adapters import HTTPAdapter
|
|
5
7
|
from requests.exceptions import ConnectionError, RequestException, Timeout
|
|
8
|
+
from urllib3.connection import HTTPConnection
|
|
6
9
|
|
|
7
10
|
import plain2code_exceptions
|
|
8
11
|
from plain2code_console import RETRY_COLOR
|
|
@@ -15,6 +18,57 @@ RETRY_DELAY = 3
|
|
|
15
18
|
# at exit, and requests waits indefinitely by default.
|
|
16
19
|
REPORT_TIMEOUT_SECONDS = 5
|
|
17
20
|
|
|
21
|
+
# A render call can leave the connection with nothing on it for several minutes while the
|
|
22
|
+
# service works on the answer. Network equipment between the two, such as the address
|
|
23
|
+
# translation a CI runner sits behind, drops a connection that quiet and tells neither end.
|
|
24
|
+
# The answer then has nowhere to go and the client waits for it for as long as it is allowed
|
|
25
|
+
# to run. These probes put a packet on the connection often enough to keep it, and they end
|
|
26
|
+
# it when the other end has really gone.
|
|
27
|
+
KEEPALIVE_IDLE_SECONDS = 60
|
|
28
|
+
KEEPALIVE_INTERVAL_SECONDS = 30
|
|
29
|
+
KEEPALIVE_FAILED_PROBES = 4
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def keepalive_socket_options():
|
|
33
|
+
"""The socket options that keep a long, quiet request alive, for the platforms that have them."""
|
|
34
|
+
options = list(HTTPConnection.default_socket_options)
|
|
35
|
+
options.append((socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1))
|
|
36
|
+
# The option that sets the idle time is named differently per platform, and Windows has
|
|
37
|
+
# none of the three. Whatever is missing is left out; the keepalive itself still applies.
|
|
38
|
+
for idle_option in ("TCP_KEEPIDLE", "TCP_KEEPALIVE"):
|
|
39
|
+
if hasattr(socket, idle_option):
|
|
40
|
+
options.append((socket.IPPROTO_TCP, getattr(socket, idle_option), KEEPALIVE_IDLE_SECONDS))
|
|
41
|
+
break
|
|
42
|
+
if hasattr(socket, "TCP_KEEPINTVL"):
|
|
43
|
+
options.append((socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, KEEPALIVE_INTERVAL_SECONDS))
|
|
44
|
+
if hasattr(socket, "TCP_KEEPCNT"):
|
|
45
|
+
options.append((socket.IPPROTO_TCP, socket.TCP_KEEPCNT, KEEPALIVE_FAILED_PROBES))
|
|
46
|
+
return options
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class KeepaliveAdapter(HTTPAdapter):
|
|
50
|
+
"""Opens every connection with keepalive on, whether it goes direct or through a proxy."""
|
|
51
|
+
|
|
52
|
+
def init_poolmanager(self, *args, **kwargs):
|
|
53
|
+
kwargs["socket_options"] = keepalive_socket_options()
|
|
54
|
+
return super().init_poolmanager(*args, **kwargs)
|
|
55
|
+
|
|
56
|
+
def proxy_manager_for(self, proxy, **proxy_kwargs):
|
|
57
|
+
# A request that goes through a proxy is served from a separate pool, which the
|
|
58
|
+
# options above never reach. A render behind a proxy is as quiet as any other and
|
|
59
|
+
# needs the same keepalive.
|
|
60
|
+
proxy_kwargs["socket_options"] = keepalive_socket_options()
|
|
61
|
+
return super().proxy_manager_for(proxy, **proxy_kwargs)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def new_session():
|
|
65
|
+
"""A requests session whose connections carry keepalive."""
|
|
66
|
+
session = requests.Session()
|
|
67
|
+
session.mount("https://", KeepaliveAdapter())
|
|
68
|
+
session.mount("http://", KeepaliveAdapter())
|
|
69
|
+
return session
|
|
70
|
+
|
|
71
|
+
|
|
18
72
|
RETRY_ERROR_CODES = [
|
|
19
73
|
"LLMInternalError",
|
|
20
74
|
]
|
|
@@ -36,6 +90,7 @@ class CodeplainAPI:
|
|
|
36
90
|
def __init__(self, api_key, console):
|
|
37
91
|
self.api_key = api_key
|
|
38
92
|
self.console = console
|
|
93
|
+
self.session = new_session()
|
|
39
94
|
|
|
40
95
|
@property
|
|
41
96
|
def api_url(self):
|
|
@@ -139,7 +194,7 @@ class CodeplainAPI:
|
|
|
139
194
|
|
|
140
195
|
for attempt in range(num_retries + 1):
|
|
141
196
|
try:
|
|
142
|
-
response =
|
|
197
|
+
response = self.session.post(endpoint_url, headers=headers, json=payload, timeout=timeout)
|
|
143
198
|
|
|
144
199
|
try:
|
|
145
200
|
response_json = response.json()
|
|
@@ -253,38 +308,6 @@ class CodeplainAPI:
|
|
|
253
308
|
|
|
254
309
|
return self.post_request(endpoint_url, headers, payload, run_state)
|
|
255
310
|
|
|
256
|
-
def fix_unittests_issue(
|
|
257
|
-
self,
|
|
258
|
-
frid,
|
|
259
|
-
plain_source_tree,
|
|
260
|
-
linked_resources,
|
|
261
|
-
existing_files_content,
|
|
262
|
-
module_name: str,
|
|
263
|
-
required_modules,
|
|
264
|
-
unittests_issue,
|
|
265
|
-
run_state: RunState,
|
|
266
|
-
conformance_tests_fixes: list[dict] | None = None,
|
|
267
|
-
):
|
|
268
|
-
endpoint_url = f"{self.api_url}/fix_unittests_issue"
|
|
269
|
-
headers = {"X-API-Key": self.api_key, "Content-Type": "application/json"}
|
|
270
|
-
|
|
271
|
-
payload = {
|
|
272
|
-
"frid": frid,
|
|
273
|
-
"plain_source_tree": plain_source_tree,
|
|
274
|
-
"linked_resources": linked_resources,
|
|
275
|
-
"existing_files_content": existing_files_content,
|
|
276
|
-
"module_name": module_name,
|
|
277
|
-
"required_modules": required_modules,
|
|
278
|
-
"unittests_issue": unittests_issue,
|
|
279
|
-
"unittest_batch_id": run_state.unittest_batch_id,
|
|
280
|
-
}
|
|
281
|
-
# Implementation code changes made by the conformance tests fixer right before this unit tests run;
|
|
282
|
-
# only sent when unit tests are processed inside the conformance tests phase.
|
|
283
|
-
if conformance_tests_fixes is not None:
|
|
284
|
-
payload["conformance_tests_fixes"] = conformance_tests_fixes
|
|
285
|
-
|
|
286
|
-
return self.post_request(endpoint_url, headers, payload, run_state)
|
|
287
|
-
|
|
288
311
|
def distill_conformance_test_memory(
|
|
289
312
|
self,
|
|
290
313
|
frid,
|
|
@@ -558,3 +581,29 @@ class CodeplainAPI:
|
|
|
558
581
|
}
|
|
559
582
|
|
|
560
583
|
return self.post_request(endpoint_url, headers, payload, run_state)
|
|
584
|
+
|
|
585
|
+
def agent_start(self, task_type: str, task_params: dict, frid: str, module_name: str, run_state: RunState):
|
|
586
|
+
"""Start a server-side agent session; returns the first turn (tool calls or completion)."""
|
|
587
|
+
endpoint_url = f"{self.api_url}/agent/start"
|
|
588
|
+
headers = {"X-API-Key": self.api_key, "Content-Type": "application/json"}
|
|
589
|
+
payload = {
|
|
590
|
+
"task_type": task_type,
|
|
591
|
+
"task_params": task_params,
|
|
592
|
+
"frid": frid,
|
|
593
|
+
"module_name": module_name,
|
|
594
|
+
}
|
|
595
|
+
return self.post_request(endpoint_url, headers, payload, run_state)
|
|
596
|
+
|
|
597
|
+
def agent_continue(
|
|
598
|
+
self, session_id: str, tool_results: list[dict], frid: str, module_name: str, run_state: RunState
|
|
599
|
+
):
|
|
600
|
+
"""Feed tool results into an agent session and run its next turn."""
|
|
601
|
+
endpoint_url = f"{self.api_url}/agent/continue"
|
|
602
|
+
headers = {"X-API-Key": self.api_key, "Content-Type": "application/json"}
|
|
603
|
+
payload = {
|
|
604
|
+
"session_id": session_id,
|
|
605
|
+
"tool_results": tool_results,
|
|
606
|
+
"frid": frid,
|
|
607
|
+
"module_name": module_name,
|
|
608
|
+
}
|
|
609
|
+
return self.post_request(endpoint_url, headers, payload, run_state)
|
|
@@ -121,6 +121,8 @@ options:
|
|
|
121
121
|
--headless Run in headless mode: no TUI. Progress is written to
|
|
122
122
|
the log file and streamed to stdout as it happens; the
|
|
123
123
|
specification text itself stays in the log file only.
|
|
124
|
+
Used automatically when stdin, stdout or stderr is not
|
|
125
|
+
a terminal.
|
|
124
126
|
--status Display account status including user information, API
|
|
125
127
|
key label, and rendering credits. Does not render any
|
|
126
128
|
code.
|
|
@@ -161,6 +161,17 @@ def setup_logging(
|
|
|
161
161
|
return logging.getLevelName(configured_log_level)
|
|
162
162
|
|
|
163
163
|
|
|
164
|
+
def attached_to_terminal() -> bool:
|
|
165
|
+
"""Whether a person at a terminal can see and drive the TUI.
|
|
166
|
+
|
|
167
|
+
The TUI reads keys from stdin and draws on stderr; the exit summary goes to
|
|
168
|
+
stdout. Started from a script or an agent, stdin is usually at end-of-file,
|
|
169
|
+
and Textual's input thread then reads EOF in a tight loop at 100% CPU while
|
|
170
|
+
a stopped render waits for an Enter nobody can press.
|
|
171
|
+
"""
|
|
172
|
+
return all(stream is not None and stream.isatty() for stream in (sys.stdin, sys.stdout, sys.stderr))
|
|
173
|
+
|
|
174
|
+
|
|
164
175
|
def _check_connection(codeplainAPI: codeplain_api.CodeplainAPI) -> Optional[str]:
|
|
165
176
|
"""Check API connectivity, validate API key and client version, and return the user email."""
|
|
166
177
|
response = codeplainAPI.connection_check(system_config.client_version)
|
|
@@ -473,12 +484,21 @@ def main(): # noqa: C901
|
|
|
473
484
|
|
|
474
485
|
run_state = RunState(spec_filename=args.filename, replay_with=args.replay_with)
|
|
475
486
|
|
|
487
|
+
# Without a terminal nobody can watch or drive the TUI, so render as --headless does:
|
|
488
|
+
# progress on stdout, and a non-zero exit status when the render stops.
|
|
489
|
+
no_terminal = not args.headless and not attached_to_terminal()
|
|
490
|
+
if no_terminal:
|
|
491
|
+
args.headless = True
|
|
492
|
+
|
|
476
493
|
if args.headless:
|
|
477
494
|
# Suppress Rich console output.
|
|
478
495
|
console.quiet = True
|
|
479
496
|
|
|
480
497
|
default_log_level = setup_logging(args, event_bus, run_state, args.log_to_file, args.log_file_name, args.headless)
|
|
481
498
|
|
|
499
|
+
if no_terminal:
|
|
500
|
+
console.info("No terminal attached, so rendering without the TUI, as with --headless.")
|
|
501
|
+
|
|
482
502
|
initialize_telemetry()
|
|
483
503
|
|
|
484
504
|
exc_info = None
|
|
@@ -428,7 +428,8 @@ def create_parser():
|
|
|
428
428
|
action="store_true",
|
|
429
429
|
default=False,
|
|
430
430
|
help="Run in headless mode: no TUI. Progress is written to the log file and streamed "
|
|
431
|
-
"to stdout as it happens; the specification text itself stays in the log file only."
|
|
431
|
+
"to stdout as it happens; the specification text itself stays in the log file only. "
|
|
432
|
+
"Used automatically when stdin, stdout or stderr is not a terminal.",
|
|
432
433
|
)
|
|
433
434
|
|
|
434
435
|
parser.add_argument(
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from http import HTTPStatus
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
import file_utils
|
|
8
|
+
import plain_spec
|
|
9
|
+
from plain2code_console import console
|
|
10
|
+
from plain2code_exceptions import InternalClientError
|
|
11
|
+
from render_machine import agent_tools
|
|
12
|
+
from render_machine.actions.base_action import BaseAction
|
|
13
|
+
from render_machine.render_context import RenderContext
|
|
14
|
+
|
|
15
|
+
TASK_TYPE = "fix_unit_tests"
|
|
16
|
+
SUBMIT_FIX_TOOL = "submit_fix"
|
|
17
|
+
# Upper bound on LLM turns spent on one fix attempt; the server bounds the whole session.
|
|
18
|
+
MAX_AGENT_TURNS_PER_ATTEMPT = 40
|
|
19
|
+
# Answer to a submit_fix whose fix was accepted in an earlier unit-test loop of the conformance phase.
|
|
20
|
+
ACCEPTED_THEN_CHANGED_MESSAGE = (
|
|
21
|
+
"Your fix was accepted: the unit tests passed. Afterwards the implementation code was changed to fix the "
|
|
22
|
+
"conformance tests{see_below}, and the unit tests now fail again. Files may have changed since you last read "
|
|
23
|
+
"them."
|
|
24
|
+
)
|
|
25
|
+
# Seeding the first turn: the build folder's file list and the files changed for the FRID.
|
|
26
|
+
MAX_FILE_TREE_ENTRIES = 500
|
|
27
|
+
MAX_RELEVANT_FILES_CHARS = 60_000
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class FixUnitTests(BaseAction):
|
|
31
|
+
"""Fix failing unit tests with a server-side agent session that spans the fix attempts.
|
|
32
|
+
|
|
33
|
+
The first failure starts a session; the agent then drives read/grep/edit/run tool calls
|
|
34
|
+
(executed here) until it calls submit_fix. The state machine re-runs the unit tests and,
|
|
35
|
+
if they still fail, the next execution of this action answers that submit_fix call with
|
|
36
|
+
the new failure output inside the same session, so earlier attempts stay in context.
|
|
37
|
+
|
|
38
|
+
During the conformance phase the session also spans unit-test loops: after the conformance
|
|
39
|
+
tests fixer changes implementation code, the unit tests fail again and the same session is
|
|
40
|
+
continued, told what the conformance tests fixer changed (which it must preserve).
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
SUCCESSFUL_OUTCOME = "unit_tests_fix_generated"
|
|
44
|
+
|
|
45
|
+
def execute(self, render_context: RenderContext, previous_action_payload: Any | None):
|
|
46
|
+
if not previous_action_payload or not previous_action_payload.get("previous_unittests_issue"):
|
|
47
|
+
raise InternalClientError(
|
|
48
|
+
"Internal client error: Previous action payload does not contain previous unit tests issue."
|
|
49
|
+
)
|
|
50
|
+
unittests_issue = previous_action_payload["previous_unittests_issue"]
|
|
51
|
+
context = render_context.unit_tests_running_context
|
|
52
|
+
session = render_context.unit_tests_agent_session
|
|
53
|
+
api = render_context.codeplain_api
|
|
54
|
+
frid, module_name = render_context.frid_context.frid, render_context.module_name
|
|
55
|
+
changed_files_before = set(context.changed_files)
|
|
56
|
+
log_path = render_context.script_execution_history.latest_unit_test_output_path
|
|
57
|
+
if log_path:
|
|
58
|
+
agent_tools.register_log_path(log_path, render_context)
|
|
59
|
+
conformance_tests_fixes = self._get_conformance_tests_fixes(render_context)
|
|
60
|
+
new_conformance_tests_fixes = conformance_tests_fixes[session.conformance_fixes_handed_off :]
|
|
61
|
+
if new_conformance_tests_fixes:
|
|
62
|
+
console.info(
|
|
63
|
+
f"Unit tests are fixed while preserving {len(new_conformance_tests_fixes)} implementation code "
|
|
64
|
+
"change(s) made to fix the conformance tests."
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
response = None
|
|
68
|
+
if session.session_id is not None:
|
|
69
|
+
if context.agent_used_in_this_loop:
|
|
70
|
+
console.info(f"Continuing agent session {session.session_id} with the new unit tests failure.")
|
|
71
|
+
output = "The fix was applied, but the unit tests still fail."
|
|
72
|
+
else:
|
|
73
|
+
# The session's last fix was accepted in an earlier unit-test loop of this conformance
|
|
74
|
+
# phase; since then the conformance tests fixer changed the code and the tests fail again.
|
|
75
|
+
console.info(
|
|
76
|
+
f"Continuing agent session {session.session_id}: the unit tests fail again after the "
|
|
77
|
+
"implementation was changed to fix the conformance tests."
|
|
78
|
+
)
|
|
79
|
+
output = ACCEPTED_THEN_CHANGED_MESSAGE.format(
|
|
80
|
+
see_below=" (see the Conformance Tests Fix below)" if new_conformance_tests_fixes else ""
|
|
81
|
+
)
|
|
82
|
+
# Files changed outside the session, so earlier read results are stale.
|
|
83
|
+
context.tool_result_cache.clear()
|
|
84
|
+
submit_result: dict = {
|
|
85
|
+
"call_id": session.pending_submit_call_id,
|
|
86
|
+
"output": output + agent_tools.full_log_pointer(log_path),
|
|
87
|
+
"test_output": unittests_issue,
|
|
88
|
+
}
|
|
89
|
+
if new_conformance_tests_fixes:
|
|
90
|
+
submit_result["conformance_tests_fixes"] = new_conformance_tests_fixes
|
|
91
|
+
tool_results = session.pending_tool_results + [submit_result]
|
|
92
|
+
session.pending_tool_results, session.pending_submit_call_id = [], None
|
|
93
|
+
response = self._continue_session(render_context, session.session_id, tool_results)
|
|
94
|
+
if response is None:
|
|
95
|
+
console.info("Starting an agent session to fix the unit tests.")
|
|
96
|
+
# Cached read results point at earlier turns, which a new session does not have.
|
|
97
|
+
context.tool_result_cache.clear()
|
|
98
|
+
response = api.agent_start(
|
|
99
|
+
TASK_TYPE,
|
|
100
|
+
self._build_task_params(render_context, unittests_issue, conformance_tests_fixes),
|
|
101
|
+
frid,
|
|
102
|
+
module_name,
|
|
103
|
+
render_context.run_state,
|
|
104
|
+
)
|
|
105
|
+
session.session_id = response["session_id"]
|
|
106
|
+
assert session.session_id is not None
|
|
107
|
+
session.conformance_fixes_handed_off = len(conformance_tests_fixes)
|
|
108
|
+
context.agent_used_in_this_loop = True
|
|
109
|
+
|
|
110
|
+
submitted = False
|
|
111
|
+
turns = 0
|
|
112
|
+
while response.get("status") == "tool_calls" and turns < MAX_AGENT_TURNS_PER_ATTEMPT:
|
|
113
|
+
turns += 1
|
|
114
|
+
calls = response["calls"]
|
|
115
|
+
submit_call = next((call for call in calls if call["name"] == SUBMIT_FIX_TOOL), None)
|
|
116
|
+
tool_results = agent_tools.execute_calls(
|
|
117
|
+
[call for call in calls if call is not submit_call], render_context
|
|
118
|
+
)
|
|
119
|
+
if submit_call is not None:
|
|
120
|
+
session.pending_submit_call_id = submit_call["id"]
|
|
121
|
+
session.pending_tool_results = tool_results
|
|
122
|
+
submitted = True
|
|
123
|
+
console.info(f"Agent submitted a fix: {submit_call['args'].get('changes_made', '')}")
|
|
124
|
+
break
|
|
125
|
+
response = api.agent_continue(session.session_id, tool_results, frid, module_name, render_context.run_state)
|
|
126
|
+
|
|
127
|
+
if not submitted:
|
|
128
|
+
# The session ended without a submission (finished in text, failed, or used up this
|
|
129
|
+
# attempt's turn budget) — a fresh session is started if the tests still fail.
|
|
130
|
+
status = response.get("status")
|
|
131
|
+
if status == "failed":
|
|
132
|
+
console.warning(f"Agent session failed: {response.get('error', 'unknown error')}")
|
|
133
|
+
elif status == "tool_calls":
|
|
134
|
+
console.warning(f"Agent used {MAX_AGENT_TURNS_PER_ATTEMPT} turns without submitting a fix.")
|
|
135
|
+
session.reset()
|
|
136
|
+
|
|
137
|
+
console.print_files(
|
|
138
|
+
"Files changed while fixing unit tests:",
|
|
139
|
+
render_context.build_folder,
|
|
140
|
+
{path: "" for path in sorted(context.changed_files - changed_files_before)},
|
|
141
|
+
style=console.OUTPUT_STYLE,
|
|
142
|
+
)
|
|
143
|
+
return self.SUCCESSFUL_OUTCOME, None
|
|
144
|
+
|
|
145
|
+
@staticmethod
|
|
146
|
+
def _get_conformance_tests_fixes(render_context: RenderContext) -> list[dict]:
|
|
147
|
+
"""Implementation code changes the conformance tests fixer made during this conformance phase, oldest
|
|
148
|
+
first. Empty outside the conformance phase (the implementation and refactoring unit-test loops)."""
|
|
149
|
+
conformance_tests_running_context = getattr(render_context, "conformance_tests_running_context", None)
|
|
150
|
+
if conformance_tests_running_context is None:
|
|
151
|
+
return []
|
|
152
|
+
return list(getattr(conformance_tests_running_context, "implementation_code_fixes", None) or [])
|
|
153
|
+
|
|
154
|
+
@staticmethod
|
|
155
|
+
def _continue_session(render_context: RenderContext, session_id: str, tool_results: list[dict]) -> dict | None:
|
|
156
|
+
"""Continue the session; None if the server no longer has it (expired), so a new one is started."""
|
|
157
|
+
try:
|
|
158
|
+
return render_context.codeplain_api.agent_continue(
|
|
159
|
+
session_id,
|
|
160
|
+
tool_results,
|
|
161
|
+
render_context.frid_context.frid,
|
|
162
|
+
render_context.module_name,
|
|
163
|
+
render_context.run_state,
|
|
164
|
+
)
|
|
165
|
+
except requests.exceptions.HTTPError as e:
|
|
166
|
+
if e.response is None or e.response.status_code != HTTPStatus.NOT_FOUND:
|
|
167
|
+
raise
|
|
168
|
+
console.warning(f"Agent session {session_id} has expired; starting a new one.")
|
|
169
|
+
render_context.unit_tests_agent_session.reset()
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
@staticmethod
|
|
173
|
+
def _build_task_params(
|
|
174
|
+
render_context: RenderContext, unittests_issue: str, conformance_tests_fixes: list[dict]
|
|
175
|
+
) -> dict:
|
|
176
|
+
frid = render_context.frid_context.frid
|
|
177
|
+
specifications, _ = plain_spec.get_specifications_for_frid(render_context.plain_source_tree, frid)
|
|
178
|
+
context = render_context.unit_tests_running_context
|
|
179
|
+
# The files the conformance tests fixes changed are always seeded, so the agent sees what to preserve.
|
|
180
|
+
conformance_fix_files = {name for fix in conformance_tests_fixes for name in (fix or {}).get("code_diff") or {}}
|
|
181
|
+
task_params = {
|
|
182
|
+
"definitions": "\n".join(specifications.get(plain_spec.DEFINITIONS, [])),
|
|
183
|
+
"non_functional_requirements": "\n".join(specifications.get(plain_spec.NON_FUNCTIONAL_REQUIREMENTS, [])),
|
|
184
|
+
"functional_requirements": FixUnitTests._functional_requirements_section(render_context, specifications),
|
|
185
|
+
"linked_resources": render_context.frid_context.linked_resources,
|
|
186
|
+
"build_folder": render_context.build_folder,
|
|
187
|
+
"module_name": render_context.module_name,
|
|
188
|
+
"unittests_script_content": FixUnitTests._read_script(render_context.unittests_script),
|
|
189
|
+
"unittests_issue": unittests_issue,
|
|
190
|
+
"unittests_log_path": render_context.script_execution_history.latest_unit_test_output_path,
|
|
191
|
+
"file_tree": FixUnitTests._file_tree(render_context.build_folder),
|
|
192
|
+
"relevant_files": FixUnitTests._relevant_files(
|
|
193
|
+
render_context.build_folder,
|
|
194
|
+
render_context.frid_context.changed_files | context.changed_files | conformance_fix_files,
|
|
195
|
+
),
|
|
196
|
+
}
|
|
197
|
+
if conformance_tests_fixes:
|
|
198
|
+
task_params["conformance_tests_fixes"] = conformance_tests_fixes
|
|
199
|
+
session = render_context.unit_tests_agent_session
|
|
200
|
+
if session.previous_session_id:
|
|
201
|
+
task_params["previous_session_id"] = session.previous_session_id
|
|
202
|
+
return task_params
|
|
203
|
+
|
|
204
|
+
@staticmethod
|
|
205
|
+
def _file_tree(build_folder: str) -> str:
|
|
206
|
+
paths = []
|
|
207
|
+
for root, dirs, files in os.walk(build_folder):
|
|
208
|
+
dirs[:] = sorted(d for d in dirs if d not in agent_tools.GREP_EXCLUDED_DIRS and not d.startswith("."))
|
|
209
|
+
paths.extend(os.path.relpath(os.path.join(root, name), build_folder) for name in sorted(files))
|
|
210
|
+
if len(paths) > MAX_FILE_TREE_ENTRIES:
|
|
211
|
+
return "\n".join(paths[:MAX_FILE_TREE_ENTRIES]) + "\n... [more files not listed]"
|
|
212
|
+
return "\n".join(paths)
|
|
213
|
+
|
|
214
|
+
@staticmethod
|
|
215
|
+
def _relevant_files(build_folder: str, file_names: set[str]) -> dict[str, str]:
|
|
216
|
+
"""Contents of the given build-relative files, smallest first, within MAX_RELEVANT_FILES_CHARS."""
|
|
217
|
+
contents = {}
|
|
218
|
+
for name in file_names:
|
|
219
|
+
full_path = os.path.join(build_folder, name)
|
|
220
|
+
if os.path.isfile(full_path):
|
|
221
|
+
with open(full_path, "r", encoding="utf-8", errors="replace") as f:
|
|
222
|
+
contents[name] = f.read()
|
|
223
|
+
relevant, total = {}, 0
|
|
224
|
+
for name in sorted(contents, key=lambda n: (len(contents[n]), n)):
|
|
225
|
+
if total + len(contents[name]) > MAX_RELEVANT_FILES_CHARS:
|
|
226
|
+
break
|
|
227
|
+
relevant[name] = contents[name]
|
|
228
|
+
total += len(contents[name])
|
|
229
|
+
return dict(sorted(relevant.items()))
|
|
230
|
+
|
|
231
|
+
@staticmethod
|
|
232
|
+
def _functional_requirements_section(render_context: RenderContext, specifications: dict) -> str:
|
|
233
|
+
sections = []
|
|
234
|
+
for module_name, functionalities in render_context.get_required_modules_functionalities().items():
|
|
235
|
+
sections.append(
|
|
236
|
+
f"### Module: {module_name} (Already Implemented, for context)\n" + "\n".join(functionalities)
|
|
237
|
+
)
|
|
238
|
+
current = specifications.get(plain_spec.FUNCTIONAL_REQUIREMENTS, [])
|
|
239
|
+
if len(current) > 1:
|
|
240
|
+
sections.append(
|
|
241
|
+
f"### Module: {render_context.module_name} (Already Implemented, for context)\n"
|
|
242
|
+
+ "\n".join(current[:-1])
|
|
243
|
+
)
|
|
244
|
+
if current:
|
|
245
|
+
sections.append(f"### Module: {render_context.module_name} (Currently Being Implemented)\n{current[-1]}")
|
|
246
|
+
return "\n\n".join(sections)
|
|
247
|
+
|
|
248
|
+
@staticmethod
|
|
249
|
+
def _read_script(script: str | None) -> str:
|
|
250
|
+
if not script:
|
|
251
|
+
return ""
|
|
252
|
+
try:
|
|
253
|
+
with open(file_utils.add_current_path_if_no_path(script), "r", encoding="utf-8") as f:
|
|
254
|
+
return f.read()
|
|
255
|
+
except OSError:
|
|
256
|
+
return ""
|
|
@@ -16,6 +16,15 @@ class RunUnitTests(BaseAction):
|
|
|
16
16
|
UNRECOVERABLE_ERROR_OUTCOME = "unrecoverable_error_occurred"
|
|
17
17
|
|
|
18
18
|
def execute(self, render_context: RenderContext, _previous_action_payload: Any | None):
|
|
19
|
+
context = render_context.unit_tests_running_context
|
|
20
|
+
if context.verified_passing:
|
|
21
|
+
# The fixing agent's own run passed and no file changed since; running again is redundant.
|
|
22
|
+
context.verified_passing = False
|
|
23
|
+
console.info("Unit tests already passed in the fixing agent's last run; not running them again.")
|
|
24
|
+
render_context.script_execution_history.latest_unit_test_output_path = context.verified_passing_log_path
|
|
25
|
+
render_context.script_execution_history.should_update_script_outputs = True
|
|
26
|
+
return self.SUCCESSFUL_OUTCOME, None
|
|
27
|
+
|
|
19
28
|
unittests_script = os.path.normpath(render_context.unittests_script)
|
|
20
29
|
|
|
21
30
|
console.info(
|