teddy-cli 0.1.17.dev1290__tar.gz → 0.1.17.dev1292__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.
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/PKG-INFO +1 -1
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/pyproject.toml +1 -1
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/__main__.py +30 -2
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/session_cli_handlers.py +37 -11
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/container.py +19 -1
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/drift_report.py +1 -1
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/inbound/init.py +2 -2
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/prompt_manager.py +3 -3
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/session_manager.py +1 -1
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/init_service.py +69 -13
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/prompt_manager.py +34 -24
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/session_orchestrator.py +7 -8
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/session_service.py +12 -11
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/interrupt_guard.py +13 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/paths.py +24 -2
- teddy_cli-0.1.17.dev1292/src/teddy_executor/core/utils/session_notices.py +42 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/prompts.py +17 -5
- teddy_cli-0.1.17.dev1292/src/teddy_executor/resources/MRP.md +314 -0
- teddy_cli-0.1.17.dev1292/src/teddy_executor/resources/config/prompts/architect.md +57 -0
- teddy_cli-0.1.17.dev1292/src/teddy_executor/resources/config/prompts/assistant.md +37 -0
- teddy_cli-0.1.17.dev1292/src/teddy_executor/resources/config/prompts/debugger.md +89 -0
- teddy_cli-0.1.17.dev1292/src/teddy_executor/resources/config/prompts/developer.md +136 -0
- teddy_cli-0.1.17.dev1292/src/teddy_executor/resources/config/prompts/pathfinder.md +59 -0
- teddy_cli-0.1.17.dev1292/src/teddy_executor/resources/config/prompts/prototyper.md +51 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/ci.md +0 -1
- teddy_cli-0.1.17.dev1290/src/teddy_executor/resources/MRP.xml +0 -266
- teddy_cli-0.1.17.dev1290/src/teddy_executor/resources/config/prompts/architect.xml +0 -43
- teddy_cli-0.1.17.dev1290/src/teddy_executor/resources/config/prompts/assistant.xml +0 -31
- teddy_cli-0.1.17.dev1290/src/teddy_executor/resources/config/prompts/debugger.xml +0 -75
- teddy_cli-0.1.17.dev1290/src/teddy_executor/resources/config/prompts/developer.xml +0 -119
- teddy_cli-0.1.17.dev1290/src/teddy_executor/resources/config/prompts/pathfinder.xml +0 -45
- teddy_cli-0.1.17.dev1290/src/teddy_executor/resources/config/prompts/prototyper.xml +0 -41
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/.gitignore +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/LICENSE +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/__init__.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/__init__.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/__init__.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/cli_formatter.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/cli_helpers.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/textual_plan_reviewer.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_app.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_editor.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_execution.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_helpers.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_logic.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_previews.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_widgets.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/__init__.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/console_interactor.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/console_interactor_ask_loop.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/console_interactor_helpers.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/console_tooling.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/filesystem_helpers.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/litellm_adapter.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/local_file_system_adapter.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/local_repo_tree_generator.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/openrouter_hydrator.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/shell_adapter.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/shell_command_builder.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/suppressed_logging.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/system_environment_adapter.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/system_environment_inspector.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/system_time_adapter.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/terminal_quit_key_listener.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/web_scraper_adapter.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/web_searcher_adapter.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/yaml_config_adapter.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/__init__.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/__init__.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/__init__.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/action_ports.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/change_set.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/exceptions.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/execution_report.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/orchestrator_ports.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/plan.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/planning_ports.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/project_context.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/report_assembly_data.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/session.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/shell_output.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/web_search_results.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/__init__.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/inbound/__init__.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/inbound/edit_simulator.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/inbound/get_context_use_case.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/inbound/plan_parser.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/inbound/plan_reviewer.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/inbound/plan_validator.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/inbound/planning_use_case.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/inbound/run_plan_use_case.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/__init__.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/config_service.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/environment_inspector.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/execution_report_assembler.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/file_system_manager.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/llm_client.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/markdown_report_formatter.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/quit_key_listener.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/repo_tree_generator.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/session_loop_guard.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/session_repository.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/shell_executor.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/system_environment.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/time_service.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/user_interactor.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/web_scraper.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/web_searcher.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/__init__.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/action_changeset_builder.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/action_diff_manager.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/action_dispatcher.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/action_executor.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/action_factory.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/action_parser_complex.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/action_parser_strategies.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/context_service.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/edit_simulator.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/execution_orchestrator.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/execution_report_assembler.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/markdown_plan_parser.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/markdown_report_formatter.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/parser_infrastructure.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/parser_metadata.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/parser_reporting.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/plan_validator.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/planning_service.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/session_lifecycle_manager.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/session_loop_guard.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/session_planner.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/session_pruning_service.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/session_replanner.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/session_repository.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/templates/execution_report.md.j2 +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/update_checker.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/validation_rules/__init__.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/validation_rules/edit.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/validation_rules/edit_matcher.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/validation_rules/edit_matcher_heuristics.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/validation_rules/execute.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/validation_rules/filesystem.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/validation_rules/helpers.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/validation_rules/message.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/__init__.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/diff.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/io.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/markdown.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/serialization.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/stdin_ownership.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/string.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/terminal.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/registries/__init__.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/registries/infrastructure.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/registries/reviewer.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/registries/validators.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/__init__.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/config/__init__.py +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/config/config.yaml +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/config/init.context +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/ARCHITECTURE.md +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/PROJECT.md +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/case-file.md +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/component-design.md +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/makefile.md +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/milestone.md +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/pre-commit.md +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/specification-document.md +0 -0
- {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/vertical-slice.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "teddy-cli"
|
|
3
|
-
version = "0.1.17.
|
|
3
|
+
version = "0.1.17.dev1292"
|
|
4
4
|
description = "A local-first, file-based AI coding workflow that applies the UNIX philosophy to AI collaboration."
|
|
5
5
|
authors = [{ name = "Raphael Atteritano" }]
|
|
6
6
|
license = { text = "AGPL-3.0-only" }
|
|
@@ -112,6 +112,31 @@ def _resolve_setup_api_key(
|
|
|
112
112
|
return system_env.isatty() and not pipeline
|
|
113
113
|
|
|
114
114
|
|
|
115
|
+
def _run_init_setup_gates(container) -> None:
|
|
116
|
+
"""Run the interactive setup gates after ``teddy init`` scaffolding.
|
|
117
|
+
|
|
118
|
+
Bare ``teddy init`` and ``teddy init config`` scaffold (or reset) the
|
|
119
|
+
``.teddy`` configuration, then run the SAME one-time setup as ``teddy
|
|
120
|
+
start``: the required LLM API-key prompt (when the key is missing and the
|
|
121
|
+
configured model requires one) and the optional editor selection (when no
|
|
122
|
+
editor is configured). Both gates reuse the existing preflight helpers, so
|
|
123
|
+
no prompt logic is duplicated. The gates are TTY-gated: a non-interactive
|
|
124
|
+
run (stdin is not a TTY) prompts for neither, keeping automated and CI
|
|
125
|
+
invocations clean.
|
|
126
|
+
"""
|
|
127
|
+
from teddy_executor.adapters.inbound.session_cli_handlers import (
|
|
128
|
+
_ensure_api_key_configured,
|
|
129
|
+
_validate_editor_config,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
system_env = container.resolve(ISystemEnvironment)
|
|
133
|
+
if not system_env.isatty():
|
|
134
|
+
return
|
|
135
|
+
config_service = container.resolve(IConfigService)
|
|
136
|
+
_ensure_api_key_configured(config_service, setup_api_key=True)
|
|
137
|
+
_validate_editor_config(container)
|
|
138
|
+
|
|
139
|
+
|
|
115
140
|
def _ensure_project_initialized(container, root_dir: str | None = None) -> None:
|
|
116
141
|
"""Lazily performs project anchoring and initialization.
|
|
117
142
|
|
|
@@ -275,12 +300,13 @@ def init_callback(ctx: typer.Context):
|
|
|
275
300
|
init_use_case = container.resolve(IInitUseCase)
|
|
276
301
|
summary = init_use_case.ensure_initialized()
|
|
277
302
|
typer.echo(f"TeDDy initialized in .teddy folder. {summary}")
|
|
303
|
+
_run_init_setup_gates(container)
|
|
278
304
|
|
|
279
305
|
|
|
280
306
|
@init_app.command()
|
|
281
307
|
def prompts():
|
|
282
308
|
"""
|
|
283
|
-
Overwrites bundled prompt
|
|
309
|
+
Overwrites bundled prompt Markdown files in .teddy/prompts/ with defaults.
|
|
284
310
|
"""
|
|
285
311
|
from teddy_executor.core.ports.inbound.init import IInitUseCase
|
|
286
312
|
|
|
@@ -301,6 +327,7 @@ def config():
|
|
|
301
327
|
init_use_case = container.resolve(IInitUseCase)
|
|
302
328
|
status = init_use_case.ensure_config_initialized(overwrite=True)
|
|
303
329
|
typer.echo(status)
|
|
330
|
+
_run_init_setup_gates(container)
|
|
304
331
|
|
|
305
332
|
|
|
306
333
|
@init_app.command()
|
|
@@ -427,7 +454,8 @@ def get_prompt(
|
|
|
427
454
|
Retrieves and displays the composed system prompt for a specified agent.
|
|
428
455
|
|
|
429
456
|
The output matches the system prompt the agent actually receives: the
|
|
430
|
-
agent-
|
|
457
|
+
agent-specific content and the shared Markdown MRP base prompt, separated
|
|
458
|
+
by a Markdown horizontal rule.
|
|
431
459
|
"""
|
|
432
460
|
from teddy_executor.adapters.inbound.cli_helpers import echo_and_copy
|
|
433
461
|
from teddy_executor.core.ports.outbound.prompt_manager import IPromptManager
|
|
@@ -17,7 +17,8 @@ from teddy_executor.core.ports.outbound.user_interactor import IUserInteractor
|
|
|
17
17
|
from teddy_executor.core.ports.outbound.session_loop_guard import ISessionLoopGuard
|
|
18
18
|
from teddy_executor.core.ports.outbound.config_service import IConfigService
|
|
19
19
|
from teddy_executor.core.ports.outbound.session_repository import ISessionRepository
|
|
20
|
-
from teddy_executor.core.utils.interrupt_guard import
|
|
20
|
+
from teddy_executor.core.utils.interrupt_guard import InterruptGuard
|
|
21
|
+
from teddy_executor.core.utils.session_notices import format_interrupt_notice
|
|
21
22
|
from teddy_executor.core.utils.string import canonical_agent_name, slugify
|
|
22
23
|
from teddy_executor.adapters.inbound.cli_formatter import format_project_context
|
|
23
24
|
from teddy_executor.adapters.inbound.cli_helpers import (
|
|
@@ -358,7 +359,10 @@ def _orchestrate_session_loop( # noqa: PLR0913
|
|
|
358
359
|
# the orchestrator's turn-transition path. EXIT the session so the
|
|
359
360
|
# user regains control instead of auto-advancing to the next turn.
|
|
360
361
|
if interrupt_guard.interrupted.is_set():
|
|
361
|
-
typer.secho(
|
|
362
|
+
typer.secho(
|
|
363
|
+
format_interrupt_notice(interrupt_guard.source),
|
|
364
|
+
fg=typer.colors.YELLOW,
|
|
365
|
+
)
|
|
362
366
|
break
|
|
363
367
|
|
|
364
368
|
cumulative_cost = float(report.metadata.get("cumulative_cost", 0.0))
|
|
@@ -376,7 +380,10 @@ def _orchestrate_session_loop( # noqa: PLR0913
|
|
|
376
380
|
# Immediate-exit semantics (Task Brief): Ctrl+C while nothing is in
|
|
377
381
|
# flight (prompts, planning) terminates the session with a notice —
|
|
378
382
|
# no report is generated and nothing is mutated.
|
|
379
|
-
typer.secho(
|
|
383
|
+
typer.secho(
|
|
384
|
+
format_interrupt_notice(interrupt_guard.source),
|
|
385
|
+
fg=typer.colors.YELLOW,
|
|
386
|
+
)
|
|
380
387
|
finally:
|
|
381
388
|
# Caller-owned restoration contract: restore the SIGINT disposition
|
|
382
389
|
# that preceded the guard's install() when the turn loop ends. Stop the
|
|
@@ -704,14 +711,11 @@ def _run_cli_preflight_check(
|
|
|
704
711
|
from teddy_executor.core.ports.outbound.prompt_manager import IPromptManager
|
|
705
712
|
|
|
706
713
|
# API-key setup runs BEFORE the LLM client is resolved so its transient
|
|
707
|
-
# config-service instance reads the freshly-persisted on-disk state.
|
|
714
|
+
# config-service instance reads the freshly-persisted on-disk state. The
|
|
715
|
+
# gate itself is single-sourced in ``_ensure_api_key_configured`` so the
|
|
716
|
+
# ``init`` command shares identical gating.
|
|
708
717
|
config_service = container.resolve(IConfigService)
|
|
709
|
-
|
|
710
|
-
setup_api_key
|
|
711
|
-
and _is_llm_api_key_missing(config_service)
|
|
712
|
-
and _llm_model_requires_api_key(config_service)
|
|
713
|
-
):
|
|
714
|
-
_prompt_for_api_key(config_service)
|
|
718
|
+
_ensure_api_key_configured(config_service, setup_api_key)
|
|
715
719
|
|
|
716
720
|
llm_client = container.resolve(ILlmClient)
|
|
717
721
|
# Perform local validation only to ensure fast CLI startup.
|
|
@@ -743,6 +747,27 @@ def _run_cli_preflight_check(
|
|
|
743
747
|
raise ConfigurationError(error_msg)
|
|
744
748
|
|
|
745
749
|
|
|
750
|
+
def _ensure_api_key_configured(
|
|
751
|
+
config_service: IConfigService,
|
|
752
|
+
setup_api_key: Optional[bool],
|
|
753
|
+
) -> None:
|
|
754
|
+
"""Runs the interactive API-key gate when the key is missing and required.
|
|
755
|
+
|
|
756
|
+
The gate fires only when ``setup_api_key`` is truthy, the resolved
|
|
757
|
+
``llm.api_key`` is absent or blank, AND the configured model actually
|
|
758
|
+
requires a provider key (key-requiredness is delegated to litellm). It is
|
|
759
|
+
ADDITIVE and inert by default (``setup_api_key`` of ``None``/``False`` ->
|
|
760
|
+
skip), so callers that never opt in are unaffected. Single-sourced so
|
|
761
|
+
``_run_cli_preflight_check`` and the ``init`` command share identical gating.
|
|
762
|
+
"""
|
|
763
|
+
if (
|
|
764
|
+
setup_api_key
|
|
765
|
+
and _is_llm_api_key_missing(config_service)
|
|
766
|
+
and _llm_model_requires_api_key(config_service)
|
|
767
|
+
):
|
|
768
|
+
_prompt_for_api_key(config_service)
|
|
769
|
+
|
|
770
|
+
|
|
746
771
|
def _is_llm_api_key_missing(config_service: IConfigService) -> bool:
|
|
747
772
|
"""True when the resolved ``llm.api_key`` is absent or blank.
|
|
748
773
|
|
|
@@ -801,7 +826,8 @@ def _prompt_for_api_key(config_service: IConfigService) -> None:
|
|
|
801
826
|
typer.secho("LLM API Key", fg=typer.colors.CYAN, bold=True, err=True)
|
|
802
827
|
typer.echo("", err=True)
|
|
803
828
|
typer.secho(
|
|
804
|
-
"\u26a0 No LLM API key configured. Paste your
|
|
829
|
+
"\u26a0 No LLM API key configured. Paste your key below.\n"
|
|
830
|
+
"OpenRouter is the default provider. To use a different provider, change llm.model in .teddy/config.yaml.",
|
|
805
831
|
fg=typer.colors.YELLOW,
|
|
806
832
|
err=True,
|
|
807
833
|
)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import os
|
|
4
|
+
import signal
|
|
3
5
|
import time
|
|
4
6
|
|
|
5
7
|
import punq
|
|
@@ -31,6 +33,20 @@ def create_container() -> punq.Container:
|
|
|
31
33
|
return container
|
|
32
34
|
|
|
33
35
|
|
|
36
|
+
def _record_quit_source(container: punq.Container) -> None:
|
|
37
|
+
"""Record the bare-`q` source on the shared guard, then self-deliver SIGINT.
|
|
38
|
+
|
|
39
|
+
Invoked from the quit-key listener's reader thread when `q` is pressed.
|
|
40
|
+
Recording the source lets the boundary notice name `q` instead of the
|
|
41
|
+
default `Ctrl+C`; the self-delivered SIGINT then drives the shared guard's
|
|
42
|
+
already-tested WAITING/EXECUTING branch unchanged.
|
|
43
|
+
"""
|
|
44
|
+
from teddy_executor.core.utils.interrupt_guard import InterruptGuard
|
|
45
|
+
|
|
46
|
+
container.resolve(InterruptGuard).record_source("q")
|
|
47
|
+
os.kill(os.getpid(), signal.SIGINT)
|
|
48
|
+
|
|
49
|
+
|
|
34
50
|
def _register_services(container: punq.Container) -> None:
|
|
35
51
|
"""Registers core application services."""
|
|
36
52
|
from teddy_executor.core.ports.inbound.edit_simulator import IEditSimulator
|
|
@@ -176,7 +192,9 @@ def _register_orchestration_services(container: punq.Container) -> None:
|
|
|
176
192
|
# so ONE instance serves the boundary's install/restore window.
|
|
177
193
|
container.register(
|
|
178
194
|
IQuitKeyListener,
|
|
179
|
-
factory=lambda: TerminalQuitKeyListener(
|
|
195
|
+
factory=lambda: TerminalQuitKeyListener(
|
|
196
|
+
on_quit=lambda: _record_quit_source(container)
|
|
197
|
+
),
|
|
180
198
|
scope=punq.Scope.singleton,
|
|
181
199
|
)
|
|
182
200
|
container.register(
|
|
@@ -13,7 +13,7 @@ from dataclasses import dataclass
|
|
|
13
13
|
class DriftReport:
|
|
14
14
|
"""Immutable summary of user files that diverge from bundled defaults.
|
|
15
15
|
|
|
16
|
-
Each field holds the *basenames* (e.g. ``pathfinder.
|
|
16
|
+
Each field holds the *basenames* (e.g. ``pathfinder.md``) of the affected
|
|
17
17
|
files, preserving the canonical manifest order for deterministic output.
|
|
18
18
|
"""
|
|
19
19
|
|
{teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/inbound/init.py
RENAMED
|
@@ -23,7 +23,7 @@ class IInitUseCase(ABC):
|
|
|
23
23
|
@abstractmethod
|
|
24
24
|
def ensure_prompts_initialized(self, overwrite: bool = False) -> str:
|
|
25
25
|
"""
|
|
26
|
-
Ensures prompt
|
|
26
|
+
Ensures prompt Markdown files are present in the .teddy/prompts/ directory.
|
|
27
27
|
|
|
28
28
|
Args:
|
|
29
29
|
overwrite: If True, always overwrite existing prompt files with defaults.
|
|
@@ -69,7 +69,7 @@ class IInitUseCase(ABC):
|
|
|
69
69
|
Compares the user's prompts and templates against the bundled defaults.
|
|
70
70
|
|
|
71
71
|
Returns:
|
|
72
|
-
A DriftReport enumerating which ``.teddy/prompts/*.
|
|
72
|
+
A DriftReport enumerating which ``.teddy/prompts/*.md`` and
|
|
73
73
|
``docs/templates/*.md`` files are edited or missing relative to the
|
|
74
74
|
bundled defaults.
|
|
75
75
|
"""
|
|
@@ -41,9 +41,9 @@ class IPromptManager(ABC):
|
|
|
41
41
|
def compose_system_prompt(self, _agent_name: str) -> Optional[str]:
|
|
42
42
|
"""Composes the full system prompt for an agent without a turn path.
|
|
43
43
|
|
|
44
|
-
Resolves the agent-specific content and assembles it (agent
|
|
45
|
-
|
|
46
|
-
content can be resolved.
|
|
44
|
+
Resolves the agent-specific content and assembles it (agent content +
|
|
45
|
+
``---`` separator + shared Markdown MRP base prompt). Returns ``None``
|
|
46
|
+
when no content can be resolved.
|
|
47
47
|
"""
|
|
48
48
|
...
|
|
49
49
|
|
|
@@ -129,6 +129,6 @@ class ISessionManager(Protocol):
|
|
|
129
129
|
"""
|
|
130
130
|
Permanently changes the session's agent. Updates meta.yaml
|
|
131
131
|
with the new agent_name and replaces the session's prompt
|
|
132
|
-
|
|
132
|
+
file with the new agent's prompt from .teddy/prompts/.
|
|
133
133
|
"""
|
|
134
134
|
...
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
3
|
from collections.abc import Callable, Sequence
|
|
4
|
+
from dataclasses import dataclass
|
|
4
5
|
from importlib import resources
|
|
6
|
+
from pathlib import Path
|
|
5
7
|
|
|
6
8
|
import yaml
|
|
7
9
|
|
|
@@ -53,20 +55,33 @@ _TEMPLATE_FILES = [
|
|
|
53
55
|
"pre-commit.md",
|
|
54
56
|
]
|
|
55
57
|
|
|
56
|
-
# Bundled
|
|
58
|
+
# Bundled Markdown prompts copied into a project's ``.teddy/prompts/`` by
|
|
57
59
|
# ``_init_prompts`` and compared by ``check_drift``. Kept as a fixed manifest
|
|
58
60
|
# (mirroring ``_TEMPLATE_FILES``) so the copy routine and the drift check share
|
|
59
61
|
# one single source of truth.
|
|
60
62
|
_PROMPT_FILES = [
|
|
61
|
-
"architect.
|
|
62
|
-
"assistant.
|
|
63
|
-
"debugger.
|
|
64
|
-
"developer.
|
|
65
|
-
"pathfinder.
|
|
66
|
-
"prototyper.
|
|
63
|
+
"architect.md",
|
|
64
|
+
"assistant.md",
|
|
65
|
+
"debugger.md",
|
|
66
|
+
"developer.md",
|
|
67
|
+
"pathfinder.md",
|
|
68
|
+
"prototyper.md",
|
|
67
69
|
]
|
|
68
70
|
|
|
69
71
|
|
|
72
|
+
@dataclass(frozen=True)
|
|
73
|
+
class _CopyPolicy:
|
|
74
|
+
"""Optional copy behaviours for ``_copy_bundled_files``.
|
|
75
|
+
|
|
76
|
+
Groups the two optional behaviours (the config copy's create-only files and
|
|
77
|
+
the prompt copy's stem identity) so the shared helper keeps a small
|
|
78
|
+
signature, mirroring the project's ``*Ports`` dataclasses.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
create_only: frozenset[str] = frozenset()
|
|
82
|
+
stem_identity: bool = False
|
|
83
|
+
|
|
84
|
+
|
|
70
85
|
class InitService(IInitUseCase):
|
|
71
86
|
"""
|
|
72
87
|
Service for initializing projects.
|
|
@@ -126,13 +141,30 @@ class InitService(IInitUseCase):
|
|
|
126
141
|
return f"overwritten ({count} files)"
|
|
127
142
|
return f"updated ({count} files)"
|
|
128
143
|
|
|
144
|
+
@staticmethod
|
|
145
|
+
def _stem_key(filename: str) -> str:
|
|
146
|
+
"""Returns a filename's case-folded stem for identity comparison."""
|
|
147
|
+
return Path(filename).stem.casefold()
|
|
148
|
+
|
|
149
|
+
def _prune_same_stem(self, dest_dir: str, filename: str) -> None:
|
|
150
|
+
"""Removes same-stem files with a different name (stem identity).
|
|
151
|
+
|
|
152
|
+
After writing ``filename`` into ``dest_dir``, any other entry sharing its
|
|
153
|
+
stem (e.g. a stale ``.xml`` beside the new ``.md``) is removed so two
|
|
154
|
+
same-stem files never coexist after a write.
|
|
155
|
+
"""
|
|
156
|
+
stem = self._stem_key(filename)
|
|
157
|
+
for name in self._file_system.list_directory(dest_dir):
|
|
158
|
+
if name != filename and self._stem_key(name) == stem:
|
|
159
|
+
self._file_system.remove_file(f"{dest_dir}/{name}")
|
|
160
|
+
|
|
129
161
|
def _copy_bundled_files(
|
|
130
162
|
self,
|
|
131
163
|
dest_dir: str,
|
|
132
164
|
filenames: Sequence[str],
|
|
133
165
|
resolve_content: Callable[[str], str | None],
|
|
134
166
|
overwrite: bool,
|
|
135
|
-
|
|
167
|
+
policy: _CopyPolicy = _CopyPolicy(),
|
|
136
168
|
) -> str:
|
|
137
169
|
"""Copies bundled resources into ``dest_dir`` via the file-system port.
|
|
138
170
|
|
|
@@ -141,17 +173,36 @@ class InitService(IInitUseCase):
|
|
|
141
173
|
in ``create_only``, which are written only when absent. Content is
|
|
142
174
|
supplied by ``resolve_content``; a ``None`` result silently skips a
|
|
143
175
|
missing bundled resource. Returns the shared copy-result status string.
|
|
176
|
+
|
|
177
|
+
When ``stem_identity`` is set (prompt copies only), a destination entry
|
|
178
|
+
sharing an entry's stem under ANY extension counts as present, and an
|
|
179
|
+
overwrite removes any same-stem file with a different name so a stale
|
|
180
|
+
``.xml`` is replaced by the new ``.md`` instead of being left beside it.
|
|
144
181
|
"""
|
|
182
|
+
create_only = policy.create_only
|
|
183
|
+
stem_identity = policy.stem_identity
|
|
145
184
|
if not self._file_system.path_exists(dest_dir):
|
|
146
185
|
self._file_system.create_directory(dest_dir)
|
|
186
|
+
same_stems = (
|
|
187
|
+
{
|
|
188
|
+
self._stem_key(name)
|
|
189
|
+
for name in self._file_system.list_directory(dest_dir)
|
|
190
|
+
}
|
|
191
|
+
if stem_identity
|
|
192
|
+
else set()
|
|
193
|
+
)
|
|
147
194
|
count = 0
|
|
148
195
|
for fname in filenames:
|
|
149
196
|
target_path = f"{dest_dir}/{fname}"
|
|
150
|
-
exists = self._file_system.path_exists(target_path)
|
|
197
|
+
exists = self._file_system.path_exists(target_path) or (
|
|
198
|
+
stem_identity and self._stem_key(fname) in same_stems
|
|
199
|
+
)
|
|
151
200
|
if not exists or (overwrite and fname not in create_only):
|
|
152
201
|
content = resolve_content(fname)
|
|
153
202
|
if content is not None:
|
|
154
203
|
self._file_system.write_file(target_path, content)
|
|
204
|
+
if stem_identity:
|
|
205
|
+
self._prune_same_stem(dest_dir, fname)
|
|
155
206
|
count += 1
|
|
156
207
|
return self._format_init_status(count, overwrite)
|
|
157
208
|
|
|
@@ -188,11 +239,15 @@ class InitService(IInitUseCase):
|
|
|
188
239
|
filenames=["config.yaml", ".gitignore", "init.context", ".env"],
|
|
189
240
|
resolve_content=self._get_default_content,
|
|
190
241
|
overwrite=overwrite,
|
|
191
|
-
create_only=_CREATE_ONLY_CONFIG_FILES,
|
|
242
|
+
policy=_CopyPolicy(create_only=_CREATE_ONLY_CONFIG_FILES),
|
|
192
243
|
)
|
|
193
244
|
|
|
194
245
|
def _init_prompts(self, overwrite: bool = False) -> str:
|
|
195
|
-
"""Copies bundled
|
|
246
|
+
"""Copies bundled Markdown prompts to .teddy/prompts/.
|
|
247
|
+
|
|
248
|
+
A destination file sharing a prompt's stem under any extension counts as
|
|
249
|
+
present; on ``overwrite`` the ``.md`` is written and any stale same-stem
|
|
250
|
+
file (e.g. a pre-migration ``.xml``) is removed.
|
|
196
251
|
|
|
197
252
|
Args:
|
|
198
253
|
overwrite: If True, always overwrite existing files. If False, only write missing ones.
|
|
@@ -205,6 +260,7 @@ class InitService(IInitUseCase):
|
|
|
205
260
|
filenames=_PROMPT_FILES,
|
|
206
261
|
resolve_content=lambda fname: self._get_default_content(f"prompts/{fname}"),
|
|
207
262
|
overwrite=overwrite,
|
|
263
|
+
policy=_CopyPolicy(stem_identity=True),
|
|
208
264
|
)
|
|
209
265
|
|
|
210
266
|
def _init_templates(self, overwrite: bool = False) -> str:
|
|
@@ -247,7 +303,7 @@ class InitService(IInitUseCase):
|
|
|
247
303
|
|
|
248
304
|
def ensure_prompts_initialized(self, overwrite: bool = True) -> str:
|
|
249
305
|
"""
|
|
250
|
-
Ensures prompt
|
|
306
|
+
Ensures prompt Markdown files are present in the .teddy/prompts/ directory.
|
|
251
307
|
|
|
252
308
|
Args:
|
|
253
309
|
overwrite: If True, always overwrite existing prompt files with defaults.
|
|
@@ -322,7 +378,7 @@ class InitService(IInitUseCase):
|
|
|
322
378
|
def check_drift(self) -> DriftReport:
|
|
323
379
|
"""Compares the user's prompts and templates against bundled defaults.
|
|
324
380
|
|
|
325
|
-
Reports which ``.teddy/prompts/*.
|
|
381
|
+
Reports which ``.teddy/prompts/*.md`` and ``docs/templates/*.md`` files
|
|
326
382
|
have been EDITED away from their bundled default and which are MISSING,
|
|
327
383
|
so the CLI preflight can advise ``teddy init prompts`` /
|
|
328
384
|
``teddy init templates``. An absent ``.teddy/prompts/`` directory simply
|
|
@@ -8,15 +8,15 @@ import yaml
|
|
|
8
8
|
from teddy_executor.core.ports.outbound.file_system_manager import IFileSystemManager
|
|
9
9
|
from teddy_executor.core.ports.outbound.user_interactor import IUserInteractor
|
|
10
10
|
from teddy_executor.core.ports.outbound.prompt_manager import IPromptManager
|
|
11
|
+
from teddy_executor.core.utils.paths import prefer_markdown
|
|
11
12
|
from teddy_executor.core.utils.serialization import scrub_dict_for_serialization
|
|
12
|
-
from teddy_executor.core.utils.string import canonical_agent_name
|
|
13
13
|
|
|
14
14
|
# The bundled Markdown Response Protocol base prompt. It lives OUTSIDE
|
|
15
15
|
# ``config/prompts/`` so it is never grouped with the user-overridable agent
|
|
16
16
|
# prompts, and is loaded as a package resource rather than via the file system
|
|
17
17
|
# port.
|
|
18
18
|
_MRP_PACKAGE = "teddy_executor.resources"
|
|
19
|
-
_MRP_FILENAME = "MRP.
|
|
19
|
+
_MRP_FILENAME = "MRP.md"
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class PromptManager(IPromptManager):
|
|
@@ -34,7 +34,7 @@ class PromptManager(IPromptManager):
|
|
|
34
34
|
self._user_interactor = user_interactor
|
|
35
35
|
# Injectable resource seam: a Traversable (or any object exposing the
|
|
36
36
|
# ``Traversable`` contract — ``is_file`` + ``read_text``) that anchors
|
|
37
|
-
# ``MRP.
|
|
37
|
+
# ``MRP.md``. When ``None``, the bundled package resource is resolved
|
|
38
38
|
# lazily. Tests inject a real ``pathlib.Path`` to avoid global patching.
|
|
39
39
|
self._mrp_resource_root = mrp_resource_root
|
|
40
40
|
|
|
@@ -87,16 +87,26 @@ class PromptManager(IPromptManager):
|
|
|
87
87
|
return resolved
|
|
88
88
|
|
|
89
89
|
def _find_prompt_file(self, directory: str, agent_name: str) -> Optional[str]:
|
|
90
|
-
"""Searches a directory for a file with the given agent name
|
|
90
|
+
"""Searches a directory for a file with the given agent name.
|
|
91
|
+
|
|
92
|
+
Extension-agnostic, but when several files share the stem the Markdown
|
|
93
|
+
file wins so a pre-authored ``.md`` prompt overrides a stale ``.xml``
|
|
94
|
+
regardless of directory listing order.
|
|
95
|
+
"""
|
|
91
96
|
if not self._file_system_manager.path_exists(directory):
|
|
92
97
|
return None
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
matches = [
|
|
99
|
+
f
|
|
100
|
+
for f in self._file_system_manager.list_directory(directory)
|
|
101
|
+
if Path(f).stem.casefold() == agent_name.casefold()
|
|
102
|
+
]
|
|
103
|
+
preferred = prefer_markdown(matches)
|
|
104
|
+
if preferred is None:
|
|
105
|
+
return None
|
|
106
|
+
return f"{directory}/{preferred}"
|
|
97
107
|
|
|
98
108
|
def _resolve_agent_prompt_content(self, agent_name: str, turn_path: Path) -> str:
|
|
99
|
-
"""Resolves the agent-specific prompt
|
|
109
|
+
"""Resolves the agent-specific prompt content from the canonical
|
|
100
110
|
``.teddy/prompts/`` directory (the user-editable source).
|
|
101
111
|
|
|
102
112
|
Returns an empty string (with a warning) when no prompt file is found,
|
|
@@ -155,27 +165,27 @@ class PromptManager(IPromptManager):
|
|
|
155
165
|
# contract (no header, no MRP) rather than fabricating a prompt.
|
|
156
166
|
return ""
|
|
157
167
|
|
|
158
|
-
return self._assemble_composed_prompt(
|
|
168
|
+
return self._assemble_composed_prompt(content)
|
|
159
169
|
|
|
160
|
-
def _assemble_composed_prompt(self,
|
|
161
|
-
"""Assembles a composed system prompt from
|
|
170
|
+
def _assemble_composed_prompt(self, content: str) -> str:
|
|
171
|
+
"""Assembles a composed system prompt from agent content.
|
|
162
172
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
already carries its own
|
|
166
|
-
``.teddy/prompts/`` override)
|
|
173
|
+
Appends the shared MRP base prompt AFTER the agent-specific content,
|
|
174
|
+
separated by a Markdown horizontal rule (``---``) padded by a blank line
|
|
175
|
+
above and below. A resolved prompt that already carries its own
|
|
176
|
+
``<response_format>`` (e.g. a customized ``.teddy/prompts/`` override) is
|
|
177
|
+
returned UNCHANGED, without the MRP block or the separator.
|
|
167
178
|
"""
|
|
168
|
-
assembled = f"Agent Name: {canonical_agent_name(agent_name)}\n\n{content}"
|
|
169
|
-
|
|
170
179
|
# Legacy compatibility: a resolved prompt that already carries its own
|
|
171
180
|
# response format (e.g. a customized ``.teddy/prompts/`` override) is
|
|
172
|
-
# returned without the shared MRP block
|
|
181
|
+
# returned unchanged, without the shared MRP block.
|
|
173
182
|
if "<response_format>" in content:
|
|
174
|
-
return
|
|
183
|
+
return content
|
|
175
184
|
|
|
176
|
-
# Both the agent
|
|
177
|
-
#
|
|
178
|
-
|
|
185
|
+
# Both the agent content and the MRP block live inside the assembled
|
|
186
|
+
# ``<system>`` prompt; MRP is appended AFTER the agent content, separated
|
|
187
|
+
# by a Markdown horizontal rule padded by a blank line above and below.
|
|
188
|
+
return f"{content}\n\n---\n\n{self._load_mrp_base_prompt()}"
|
|
179
189
|
|
|
180
190
|
def compose_system_prompt(self, agent_name: str) -> Optional[str]:
|
|
181
191
|
"""Composes the full system prompt for an agent WITHOUT a turn path.
|
|
@@ -187,7 +197,7 @@ class PromptManager(IPromptManager):
|
|
|
187
197
|
content = self.get_prompt_content(agent_name)
|
|
188
198
|
if not content:
|
|
189
199
|
return None
|
|
190
|
-
return self._assemble_composed_prompt(
|
|
200
|
+
return self._assemble_composed_prompt(content)
|
|
191
201
|
|
|
192
202
|
def log_telemetry(self, token_count: Any, turn_cost: Any) -> float:
|
|
193
203
|
def safe_float(v: Any, default: float = 0.0) -> float:
|
|
@@ -14,6 +14,7 @@ from teddy_executor.core.ports.inbound.run_plan_use_case import IRunPlanUseCase
|
|
|
14
14
|
from teddy_executor.core.ports.outbound.file_system_manager import IFileSystemManager
|
|
15
15
|
from teddy_executor.core.services.session_replanner import SessionReplanner
|
|
16
16
|
from teddy_executor.core.utils.io import Tee as _Tee
|
|
17
|
+
from teddy_executor.core.utils.session_notices import format_termination_notice
|
|
17
18
|
from teddy_executor.core.utils.terminal import emit_user_message
|
|
18
19
|
import typer
|
|
19
20
|
|
|
@@ -304,12 +305,10 @@ class SessionOrchestrator(IRunPlanUseCase):
|
|
|
304
305
|
)
|
|
305
306
|
if user_reply is not None and not user_reply.strip():
|
|
306
307
|
typer.secho(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
typer.secho(
|
|
312
|
-
"To continue the session, use `teddy resume [session_path]`.",
|
|
308
|
+
format_termination_notice(
|
|
309
|
+
"Session terminated (empty message)."
|
|
310
|
+
),
|
|
311
|
+
fg=typer.colors.YELLOW,
|
|
313
312
|
err=True,
|
|
314
313
|
)
|
|
315
314
|
return None # type: ignore
|
|
@@ -334,9 +333,9 @@ class SessionOrchestrator(IRunPlanUseCase):
|
|
|
334
333
|
elif is_session and plan_path:
|
|
335
334
|
report = self._handle_aborted_session(report, plan)
|
|
336
335
|
if report is None:
|
|
337
|
-
typer.secho("\nSession terminated.", fg=typer.colors.RED, err=True)
|
|
338
336
|
typer.secho(
|
|
339
|
-
"
|
|
337
|
+
format_termination_notice("Session terminated."),
|
|
338
|
+
fg=typer.colors.YELLOW,
|
|
340
339
|
err=True,
|
|
341
340
|
)
|
|
342
341
|
return None # type: ignore
|
|
@@ -17,7 +17,7 @@ from teddy_executor.core.ports.outbound.session_manager import (
|
|
|
17
17
|
from teddy_executor.core.ports.outbound.session_repository import ISessionRepository
|
|
18
18
|
from teddy_executor.core.ports.outbound.prompt_manager import IPromptManager
|
|
19
19
|
from teddy_executor.core.ports.outbound.time_service import ITimeService
|
|
20
|
-
from teddy_executor.core.utils.paths import normalize_resource_path
|
|
20
|
+
from teddy_executor.core.utils.paths import normalize_resource_path, prefer_markdown
|
|
21
21
|
from teddy_executor.core.utils.string import canonical_agent_name, slugify
|
|
22
22
|
|
|
23
23
|
|
|
@@ -65,7 +65,7 @@ class SessionService(ISessionManager):
|
|
|
65
65
|
# 2. Prompt population — compose the full system prompt and persist it.
|
|
66
66
|
# `_resolve_agent_prompt` validates the agent and resolves the target
|
|
67
67
|
# filename; `PromptManager.fetch_system_prompt` assembles the composed
|
|
68
|
-
# system prompt (agent
|
|
68
|
+
# system prompt (agent content + `---` separator + MRP). Because the
|
|
69
69
|
# session root is still empty here, this forces the compose path from
|
|
70
70
|
# `.teddy/prompts/`. Persisting the composed value makes the session
|
|
71
71
|
# root hold the exact prompt sent to the model, which later turns reuse
|
|
@@ -166,11 +166,12 @@ class SessionService(ISessionManager):
|
|
|
166
166
|
)
|
|
167
167
|
raise ValueError(msg)
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
169
|
+
matches = [
|
|
170
|
+
f
|
|
171
|
+
for f in self._file_system_manager.list_directory(prompts_dir)
|
|
172
|
+
if Path(f).stem.casefold() == agent_name.casefold()
|
|
173
|
+
]
|
|
174
|
+
prompt_filename = prefer_markdown(matches)
|
|
174
175
|
|
|
175
176
|
if prompt_filename is None:
|
|
176
177
|
available = self._prompt_manager.get_available_agents()
|
|
@@ -523,9 +524,9 @@ class SessionService(ISessionManager):
|
|
|
523
524
|
def set_session_agent(self, session_name: str, agent_name: str) -> None:
|
|
524
525
|
"""
|
|
525
526
|
Permanently changes the session's agent. Updates meta.yaml with the
|
|
526
|
-
new agent_name and replaces the session's prompt
|
|
527
|
-
RECOMPOSED system prompt for the new agent (agent
|
|
528
|
-
|
|
527
|
+
new agent_name and replaces the session's prompt file with the
|
|
528
|
+
RECOMPOSED system prompt for the new agent (agent content + `---`
|
|
529
|
+
separator + MRP), so later turns reuse it verbatim (Option A).
|
|
529
530
|
"""
|
|
530
531
|
session_root = f".teddy/sessions/{session_name}"
|
|
531
532
|
latest_turn_path = self.get_latest_turn(session_name)
|
|
@@ -537,7 +538,7 @@ class SessionService(ISessionManager):
|
|
|
537
538
|
|
|
538
539
|
# 2. Resolve the target filename (validates the agent) and recompose the
|
|
539
540
|
# full system prompt. Option A: switching agents must recompose
|
|
540
|
-
# (agent
|
|
541
|
+
# (agent content + `---` separator + MRP) via the prompt manager and
|
|
541
542
|
# persist THAT composed value -- never the raw `.teddy/prompts/`
|
|
542
543
|
# content -- so later turns reuse it verbatim. Because the session
|
|
543
544
|
# root does not yet hold the new agent's file, the prompt manager
|