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.
Files changed (168) hide show
  1. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/PKG-INFO +1 -1
  2. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/pyproject.toml +1 -1
  3. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/__main__.py +30 -2
  4. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/session_cli_handlers.py +37 -11
  5. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/container.py +19 -1
  6. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/drift_report.py +1 -1
  7. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/inbound/init.py +2 -2
  8. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/prompt_manager.py +3 -3
  9. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/session_manager.py +1 -1
  10. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/init_service.py +69 -13
  11. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/prompt_manager.py +34 -24
  12. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/session_orchestrator.py +7 -8
  13. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/session_service.py +12 -11
  14. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/interrupt_guard.py +13 -0
  15. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/paths.py +24 -2
  16. teddy_cli-0.1.17.dev1292/src/teddy_executor/core/utils/session_notices.py +42 -0
  17. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/prompts.py +17 -5
  18. teddy_cli-0.1.17.dev1292/src/teddy_executor/resources/MRP.md +314 -0
  19. teddy_cli-0.1.17.dev1292/src/teddy_executor/resources/config/prompts/architect.md +57 -0
  20. teddy_cli-0.1.17.dev1292/src/teddy_executor/resources/config/prompts/assistant.md +37 -0
  21. teddy_cli-0.1.17.dev1292/src/teddy_executor/resources/config/prompts/debugger.md +89 -0
  22. teddy_cli-0.1.17.dev1292/src/teddy_executor/resources/config/prompts/developer.md +136 -0
  23. teddy_cli-0.1.17.dev1292/src/teddy_executor/resources/config/prompts/pathfinder.md +59 -0
  24. teddy_cli-0.1.17.dev1292/src/teddy_executor/resources/config/prompts/prototyper.md +51 -0
  25. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/ci.md +0 -1
  26. teddy_cli-0.1.17.dev1290/src/teddy_executor/resources/MRP.xml +0 -266
  27. teddy_cli-0.1.17.dev1290/src/teddy_executor/resources/config/prompts/architect.xml +0 -43
  28. teddy_cli-0.1.17.dev1290/src/teddy_executor/resources/config/prompts/assistant.xml +0 -31
  29. teddy_cli-0.1.17.dev1290/src/teddy_executor/resources/config/prompts/debugger.xml +0 -75
  30. teddy_cli-0.1.17.dev1290/src/teddy_executor/resources/config/prompts/developer.xml +0 -119
  31. teddy_cli-0.1.17.dev1290/src/teddy_executor/resources/config/prompts/pathfinder.xml +0 -45
  32. teddy_cli-0.1.17.dev1290/src/teddy_executor/resources/config/prompts/prototyper.xml +0 -41
  33. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/.gitignore +0 -0
  34. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/LICENSE +0 -0
  35. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/__init__.py +0 -0
  36. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/__init__.py +0 -0
  37. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/__init__.py +0 -0
  38. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/cli_formatter.py +0 -0
  39. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/cli_helpers.py +0 -0
  40. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/textual_plan_reviewer.py +0 -0
  41. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_app.py +0 -0
  42. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_editor.py +0 -0
  43. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_execution.py +0 -0
  44. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_helpers.py +0 -0
  45. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_logic.py +0 -0
  46. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_previews.py +0 -0
  47. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_widgets.py +0 -0
  48. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/__init__.py +0 -0
  49. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/console_interactor.py +0 -0
  50. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/console_interactor_ask_loop.py +0 -0
  51. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/console_interactor_helpers.py +0 -0
  52. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/console_tooling.py +0 -0
  53. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/filesystem_helpers.py +0 -0
  54. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/litellm_adapter.py +0 -0
  55. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/local_file_system_adapter.py +0 -0
  56. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/local_repo_tree_generator.py +0 -0
  57. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/openrouter_hydrator.py +0 -0
  58. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/shell_adapter.py +0 -0
  59. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/shell_command_builder.py +0 -0
  60. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/suppressed_logging.py +0 -0
  61. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/system_environment_adapter.py +0 -0
  62. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/system_environment_inspector.py +0 -0
  63. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/system_time_adapter.py +0 -0
  64. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/terminal_quit_key_listener.py +0 -0
  65. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/web_scraper_adapter.py +0 -0
  66. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/web_searcher_adapter.py +0 -0
  67. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/adapters/outbound/yaml_config_adapter.py +0 -0
  68. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/__init__.py +0 -0
  69. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/__init__.py +0 -0
  70. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/__init__.py +0 -0
  71. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/action_ports.py +0 -0
  72. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/change_set.py +0 -0
  73. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/exceptions.py +0 -0
  74. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/execution_report.py +0 -0
  75. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/orchestrator_ports.py +0 -0
  76. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/plan.py +0 -0
  77. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/planning_ports.py +0 -0
  78. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/project_context.py +0 -0
  79. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/report_assembly_data.py +0 -0
  80. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/session.py +0 -0
  81. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/shell_output.py +0 -0
  82. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/domain/models/web_search_results.py +0 -0
  83. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/__init__.py +0 -0
  84. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/inbound/__init__.py +0 -0
  85. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/inbound/edit_simulator.py +0 -0
  86. {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
  87. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/inbound/plan_parser.py +0 -0
  88. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/inbound/plan_reviewer.py +0 -0
  89. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/inbound/plan_validator.py +0 -0
  90. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/inbound/planning_use_case.py +0 -0
  91. {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
  92. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/__init__.py +0 -0
  93. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/config_service.py +0 -0
  94. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/environment_inspector.py +0 -0
  95. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/execution_report_assembler.py +0 -0
  96. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/file_system_manager.py +0 -0
  97. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/llm_client.py +0 -0
  98. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/markdown_report_formatter.py +0 -0
  99. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/quit_key_listener.py +0 -0
  100. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/repo_tree_generator.py +0 -0
  101. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/session_loop_guard.py +0 -0
  102. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/session_repository.py +0 -0
  103. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/shell_executor.py +0 -0
  104. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/system_environment.py +0 -0
  105. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/time_service.py +0 -0
  106. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/user_interactor.py +0 -0
  107. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/web_scraper.py +0 -0
  108. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/ports/outbound/web_searcher.py +0 -0
  109. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/__init__.py +0 -0
  110. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/action_changeset_builder.py +0 -0
  111. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/action_diff_manager.py +0 -0
  112. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/action_dispatcher.py +0 -0
  113. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/action_executor.py +0 -0
  114. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/action_factory.py +0 -0
  115. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/action_parser_complex.py +0 -0
  116. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/action_parser_strategies.py +0 -0
  117. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/context_service.py +0 -0
  118. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/edit_simulator.py +0 -0
  119. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/execution_orchestrator.py +0 -0
  120. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/execution_report_assembler.py +0 -0
  121. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/markdown_plan_parser.py +0 -0
  122. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/markdown_report_formatter.py +0 -0
  123. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/parser_infrastructure.py +0 -0
  124. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/parser_metadata.py +0 -0
  125. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/parser_reporting.py +0 -0
  126. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/plan_validator.py +0 -0
  127. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/planning_service.py +0 -0
  128. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/session_lifecycle_manager.py +0 -0
  129. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/session_loop_guard.py +0 -0
  130. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/session_planner.py +0 -0
  131. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/session_pruning_service.py +0 -0
  132. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/session_replanner.py +0 -0
  133. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/session_repository.py +0 -0
  134. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/templates/execution_report.md.j2 +0 -0
  135. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/update_checker.py +0 -0
  136. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/validation_rules/__init__.py +0 -0
  137. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/validation_rules/edit.py +0 -0
  138. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/validation_rules/edit_matcher.py +0 -0
  139. {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
  140. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/validation_rules/execute.py +0 -0
  141. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/validation_rules/filesystem.py +0 -0
  142. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/validation_rules/helpers.py +0 -0
  143. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/services/validation_rules/message.py +0 -0
  144. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/__init__.py +0 -0
  145. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/diff.py +0 -0
  146. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/io.py +0 -0
  147. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/markdown.py +0 -0
  148. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/serialization.py +0 -0
  149. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/stdin_ownership.py +0 -0
  150. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/string.py +0 -0
  151. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/core/utils/terminal.py +0 -0
  152. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/registries/__init__.py +0 -0
  153. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/registries/infrastructure.py +0 -0
  154. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/registries/reviewer.py +0 -0
  155. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/registries/validators.py +0 -0
  156. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/__init__.py +0 -0
  157. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/config/__init__.py +0 -0
  158. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/config/config.yaml +0 -0
  159. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/config/init.context +0 -0
  160. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/ARCHITECTURE.md +0 -0
  161. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/PROJECT.md +0 -0
  162. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/case-file.md +0 -0
  163. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/component-design.md +0 -0
  164. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/makefile.md +0 -0
  165. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/milestone.md +0 -0
  166. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/pre-commit.md +0 -0
  167. {teddy_cli-0.1.17.dev1290 → teddy_cli-0.1.17.dev1292}/src/teddy_executor/resources/templates/specification-document.md +0 -0
  168. {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
  Metadata-Version: 2.5
2
2
  Name: teddy-cli
3
- Version: 0.1.17.dev1290
3
+ Version: 0.1.17.dev1292
4
4
  Summary: A local-first, file-based AI coding workflow that applies the UNIX philosophy to AI collaboration.
5
5
  Author: Raphael Atteritano
6
6
  License: AGPL-3.0-only
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "teddy-cli"
3
- version = "0.1.17.dev1290"
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 XMLs in .teddy/prompts/ with defaults.
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-name header, the agent-specific XML, and the shared MRP base prompt.
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 INTERRUPT_REASON, InterruptGuard
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(INTERRUPT_REASON, fg=typer.colors.YELLOW)
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(INTERRUPT_REASON, fg=typer.colors.YELLOW)
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
- if (
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 provider key below.",
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.xml``) of the affected
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
 
@@ -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 XML files are present in the .teddy/prompts/ directory.
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/*.xml`` and
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-name
45
- header + agent XML + shared MRP base prompt). Returns ``None`` when no
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
- XML with the new agent's prompt from .teddy/prompts/.
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 prompt XMLs copied into a project's ``.teddy/prompts/`` by
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.xml",
62
- "assistant.xml",
63
- "debugger.xml",
64
- "developer.xml",
65
- "pathfinder.xml",
66
- "prototyper.xml",
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
- create_only: frozenset[str] = frozenset(),
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 prompt XMLs to .teddy/prompts/.
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 XML files are present in the .teddy/prompts/ directory.
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/*.xml`` and ``docs/templates/*.md`` files
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.xml"
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.xml``. When ``None``, the bundled package resource is resolved
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 (any extension)."""
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
- for f in self._file_system_manager.list_directory(directory):
94
- if Path(f).stem.casefold() == agent_name.casefold():
95
- return f"{directory}/{f}"
96
- return None
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 XML from the canonical
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(agent_name, content)
168
+ return self._assemble_composed_prompt(content)
159
169
 
160
- def _assemble_composed_prompt(self, agent_name: str, content: str) -> str:
161
- """Assembles a composed system prompt from an agent name and content.
170
+ def _assemble_composed_prompt(self, content: str) -> str:
171
+ """Assembles a composed system prompt from agent content.
162
172
 
163
- Prepends the canonical agent-name header, then appends the shared MRP
164
- base prompt AFTER the agent-specific content. A resolved prompt that
165
- already carries its own ``<response_format>`` (e.g. a customized
166
- ``.teddy/prompts/`` override) keeps the header but skips the MRP block.
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, but keeps the agent-name header.
181
+ # returned unchanged, without the shared MRP block.
173
182
  if "<response_format>" in content:
174
- return assembled
183
+ return content
175
184
 
176
- # Both the agent-specific XML and the MRP block live inside the
177
- # assembled ``<system>`` prompt; MRP is appended AFTER the agent content.
178
- return f"{assembled}\n\n{self._load_mrp_base_prompt()}"
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(agent_name, content)
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
- "\nSession terminated (empty message).",
308
- fg=typer.colors.RED,
309
- err=True,
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
- "To continue the session, use `teddy resume [session_path]`.",
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-name header + agent XML + MRP). Because the
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
- prompt_filename = None
170
- for f in self._file_system_manager.list_directory(prompts_dir):
171
- if Path(f).stem.casefold() == agent_name.casefold():
172
- prompt_filename = f
173
- break
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 XML with the
527
- RECOMPOSED system prompt for the new agent (agent-name header + agent
528
- XML + MRP), so later turns reuse it verbatim (Option A).
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-name header + agent XML + MRP) via the prompt manager and
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