teddy-cli 0.1.17.dev1399__tar.gz → 0.1.17.dev1401__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 (165) hide show
  1. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/PKG-INFO +1 -1
  2. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/pyproject.toml +1 -1
  3. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/inbound/session_cli_handlers.py +176 -5
  4. teddy_cli-0.1.17.dev1401/src/teddy_executor/adapters/outbound/local_repo_tree_generator.py +138 -0
  5. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/yaml_config_adapter.py +33 -1
  6. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/action_factory.py +2 -2
  7. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/validation_rules/helpers.py +2 -4
  8. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/registries/infrastructure.py +6 -2
  9. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/config/config.yaml +13 -3
  10. teddy_cli-0.1.17.dev1399/src/teddy_executor/adapters/outbound/local_repo_tree_generator.py +0 -81
  11. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/.gitignore +0 -0
  12. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/LICENSE +0 -0
  13. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/README.md +0 -0
  14. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/__init__.py +0 -0
  15. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/__main__.py +0 -0
  16. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/__init__.py +0 -0
  17. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/inbound/__init__.py +0 -0
  18. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/inbound/cli_formatter.py +0 -0
  19. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/inbound/cli_helpers.py +0 -0
  20. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/inbound/textual_plan_reviewer.py +0 -0
  21. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_app.py +0 -0
  22. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_editor.py +0 -0
  23. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_execution.py +0 -0
  24. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_helpers.py +0 -0
  25. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_logic.py +0 -0
  26. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_previews.py +0 -0
  27. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_widgets.py +0 -0
  28. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/__init__.py +0 -0
  29. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/console_interactor.py +0 -0
  30. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/console_interactor_ask_loop.py +0 -0
  31. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/console_interactor_helpers.py +0 -0
  32. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/console_tooling.py +0 -0
  33. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/filesystem_helpers.py +0 -0
  34. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/litellm_adapter.py +0 -0
  35. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/local_file_system_adapter.py +0 -0
  36. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/openrouter_hydrator.py +0 -0
  37. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/shell_adapter.py +0 -0
  38. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/shell_command_builder.py +0 -0
  39. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/suppressed_logging.py +0 -0
  40. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/system_environment_adapter.py +0 -0
  41. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/system_environment_inspector.py +0 -0
  42. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/system_time_adapter.py +0 -0
  43. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/terminal_quit_key_listener.py +0 -0
  44. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/web_scraper_adapter.py +0 -0
  45. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/adapters/outbound/web_searcher_adapter.py +0 -0
  46. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/container.py +0 -0
  47. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/__init__.py +0 -0
  48. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/domain/__init__.py +0 -0
  49. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/domain/models/__init__.py +0 -0
  50. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/domain/models/action_ports.py +0 -0
  51. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/domain/models/change_set.py +0 -0
  52. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/domain/models/drift_report.py +0 -0
  53. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/domain/models/exceptions.py +0 -0
  54. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/domain/models/execution_report.py +0 -0
  55. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/domain/models/orchestrator_ports.py +0 -0
  56. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/domain/models/plan.py +0 -0
  57. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/domain/models/planning_ports.py +0 -0
  58. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/domain/models/project_context.py +0 -0
  59. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/domain/models/report_assembly_data.py +0 -0
  60. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/domain/models/session.py +0 -0
  61. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/domain/models/shell_output.py +0 -0
  62. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/domain/models/web_search_results.py +0 -0
  63. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/__init__.py +0 -0
  64. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/inbound/__init__.py +0 -0
  65. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/inbound/edit_simulator.py +0 -0
  66. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/inbound/get_context_use_case.py +0 -0
  67. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/inbound/init.py +0 -0
  68. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/inbound/plan_parser.py +0 -0
  69. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/inbound/plan_reviewer.py +0 -0
  70. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/inbound/plan_validator.py +0 -0
  71. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/inbound/planning_use_case.py +0 -0
  72. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/inbound/run_plan_use_case.py +0 -0
  73. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/__init__.py +0 -0
  74. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/config_service.py +0 -0
  75. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/environment_inspector.py +0 -0
  76. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/execution_report_assembler.py +0 -0
  77. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/file_system_manager.py +0 -0
  78. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/llm_client.py +0 -0
  79. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/markdown_report_formatter.py +0 -0
  80. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/prompt_manager.py +0 -0
  81. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/quit_key_listener.py +0 -0
  82. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/repo_tree_generator.py +0 -0
  83. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/session_loop_guard.py +0 -0
  84. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/session_manager.py +0 -0
  85. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/session_repository.py +0 -0
  86. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/shell_executor.py +0 -0
  87. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/system_environment.py +0 -0
  88. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/time_service.py +0 -0
  89. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/user_interactor.py +0 -0
  90. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/web_scraper.py +0 -0
  91. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/ports/outbound/web_searcher.py +0 -0
  92. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/__init__.py +0 -0
  93. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/action_changeset_builder.py +0 -0
  94. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/action_diff_manager.py +0 -0
  95. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/action_dispatcher.py +0 -0
  96. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/action_executor.py +0 -0
  97. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/action_parser_complex.py +0 -0
  98. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/action_parser_strategies.py +0 -0
  99. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/context_service.py +0 -0
  100. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/edit_simulator.py +0 -0
  101. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/execution_orchestrator.py +0 -0
  102. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/execution_report_assembler.py +0 -0
  103. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/init_service.py +0 -0
  104. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/markdown_plan_parser.py +0 -0
  105. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/markdown_report_formatter.py +0 -0
  106. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/parser_infrastructure.py +0 -0
  107. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/parser_metadata.py +0 -0
  108. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/parser_reporting.py +0 -0
  109. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/plan_validator.py +0 -0
  110. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/planning_service.py +0 -0
  111. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/prompt_manager.py +0 -0
  112. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/session_lifecycle_manager.py +0 -0
  113. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/session_loop_guard.py +0 -0
  114. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/session_orchestrator.py +0 -0
  115. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/session_planner.py +0 -0
  116. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/session_pruning_service.py +0 -0
  117. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/session_replanner.py +0 -0
  118. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/session_repository.py +0 -0
  119. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/session_service.py +0 -0
  120. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/templates/execution_report.md.j2 +0 -0
  121. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/update_checker.py +0 -0
  122. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/validation_rules/__init__.py +0 -0
  123. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/validation_rules/edit.py +0 -0
  124. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/validation_rules/edit_matcher.py +0 -0
  125. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/validation_rules/edit_matcher_heuristics.py +0 -0
  126. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/validation_rules/execute.py +0 -0
  127. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/validation_rules/filesystem.py +0 -0
  128. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/services/validation_rules/message.py +0 -0
  129. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/utils/__init__.py +0 -0
  130. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/utils/diff.py +0 -0
  131. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/utils/editor_names.py +0 -0
  132. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/utils/interrupt_guard.py +0 -0
  133. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/utils/interruptible_call.py +0 -0
  134. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/utils/io.py +0 -0
  135. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/utils/markdown.py +0 -0
  136. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/utils/paths.py +0 -0
  137. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/utils/serialization.py +0 -0
  138. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/utils/session_notices.py +0 -0
  139. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/utils/stdin_ownership.py +0 -0
  140. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/utils/string.py +0 -0
  141. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/core/utils/terminal.py +0 -0
  142. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/prompts.py +0 -0
  143. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/registries/__init__.py +0 -0
  144. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/registries/reviewer.py +0 -0
  145. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/registries/validators.py +0 -0
  146. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/MRP.md +0 -0
  147. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/__init__.py +0 -0
  148. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/config/__init__.py +0 -0
  149. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/config/init.context +0 -0
  150. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/config/prompts/architect.md +0 -0
  151. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/config/prompts/assistant.md +0 -0
  152. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/config/prompts/debugger.md +0 -0
  153. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/config/prompts/developer.md +0 -0
  154. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/config/prompts/pathfinder.md +0 -0
  155. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/config/prompts/prototyper.md +0 -0
  156. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/templates/ARCHITECTURE.md +0 -0
  157. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/templates/PROJECT.md +0 -0
  158. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/templates/case-file.md +0 -0
  159. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/templates/ci.md +0 -0
  160. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/templates/component-design.md +0 -0
  161. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/templates/makefile.md +0 -0
  162. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/templates/milestone.md +0 -0
  163. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/templates/pre-commit.md +0 -0
  164. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/src/teddy_executor/resources/templates/specification-document.md +0 -0
  165. {teddy_cli-0.1.17.dev1399 → teddy_cli-0.1.17.dev1401}/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.dev1399
3
+ Version: 0.1.17.dev1401
4
4
  Summary: The local-first, test-driven agentic engineering harness. Because you should own the means of development.
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.dev1399"
3
+ version = "0.1.17.dev1401"
4
4
  description = "The local-first, test-driven agentic engineering harness. Because you should own the means of development."
5
5
  readme = "README.md"
6
6
  keywords = [
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import os
2
3
  import shutil
3
4
  import signal
4
5
  import subprocess # nosec B404
@@ -244,10 +245,179 @@ def _check_git_initialized() -> None:
244
245
  )
245
246
 
246
247
 
247
- def _run_health_checks() -> None:
248
- """Run all startup health checks and display notifications if needed."""
249
- _ensure_commit_hooks()
250
- _check_git_initialized()
248
+ def _warn_disabled_workspace_steps(config_service: IConfigService) -> None:
249
+ """Print one warning per disabled workspace step with a real consequence.
250
+
251
+ A disabled hook installer warns because the unskippable post-commit test
252
+ gate then depends on a hook that never landed. A disabled git init warns
253
+ only when the workspace is not already a repository, because an existing
254
+ repository needs no initialization.
255
+ """
256
+ if not config_service.get_setting("workspace.pre-commit", True):
257
+ typer.secho(
258
+ "⚠ Pre-commit hook installation is disabled. "
259
+ "The post-commit test gate will not run.",
260
+ fg=typer.colors.YELLOW,
261
+ err=True,
262
+ )
263
+ init_disabled = not config_service.get_setting("workspace.init", True)
264
+ if init_disabled and not (Path.cwd() / ".git").exists():
265
+ typer.secho(
266
+ "⚠ Git repository not initialized. Version control actions will fail.",
267
+ fg=typer.colors.YELLOW,
268
+ err=True,
269
+ )
270
+
271
+
272
+ def _run_health_checks(config_service: IConfigService) -> None:
273
+ """Run each startup health check the workspace toggles allow.
274
+
275
+ A disabled step is skipped, and ``_warn_disabled_workspace_steps`` reports
276
+ the ones that carry a real consequence.
277
+ """
278
+ if config_service.get_setting("workspace.pre-commit", True):
279
+ _ensure_commit_hooks()
280
+ if config_service.get_setting("workspace.init", True):
281
+ _check_git_initialized()
282
+ _warn_disabled_workspace_steps(config_service)
283
+
284
+
285
+ _GIT_PULL_TIMEOUT_SECONDS = 15
286
+ _PULL_SKIPPED_WARNING = "⚠ Workspace pull skipped; continuing with the current branch."
287
+ _PULL_TIMEOUT_WARNING = "⚠ Workspace pull timed out; continuing without it."
288
+ _IN_PROGRESS_GIT_MARKERS = (
289
+ "rebase-merge",
290
+ "rebase-apply",
291
+ "MERGE_HEAD",
292
+ "CHERRY_PICK_HEAD",
293
+ )
294
+
295
+
296
+ def _pull_workspace_on_start(
297
+ config_service: IConfigService,
298
+ run_func: Optional[Callable[..., Any]] = None,
299
+ ) -> None:
300
+ """Fast-forward the workspace before starting a new session.
301
+
302
+ The pull runs only when ``workspace.pull`` is enabled and the workspace is
303
+ a Git repository, so a disabled toggle or a non-repository directory stays
304
+ silent. A detached HEAD, a missing upstream, or an in-progress operation
305
+ also stays silent, because there is nothing safe to fast-forward. A dirty
306
+ worktree or a failed pull reports one warning and leaves the workspace
307
+ unchanged. A successful fast-forward names the number of commits it moved.
308
+
309
+ The Git process is injected through ``run_func`` so every decision is
310
+ testable without spawning a real repository.
311
+ """
312
+ if not config_service.get_setting("workspace.pull", True):
313
+ return
314
+ if not (Path.cwd() / ".git").exists():
315
+ return
316
+ git_run = run_func or subprocess.run
317
+ try:
318
+ _fast_forward_workspace(git_run)
319
+ except subprocess.TimeoutExpired:
320
+ typer.secho(_PULL_TIMEOUT_WARNING, fg=typer.colors.YELLOW, err=True)
321
+
322
+
323
+ def _fast_forward_workspace(git_run: Callable[..., Any]) -> None:
324
+ """Run the pull decision table through the injected Git runner.
325
+
326
+ Each precondition is probed before the pull so an unsafe state never
327
+ reaches ``git pull``. A dirty worktree reports one warning, while every
328
+ other unsafe state stays silent because it has nothing to report.
329
+ """
330
+ skip_reason = _pull_skip_reason(git_run)
331
+ if skip_reason is not None:
332
+ if skip_reason:
333
+ typer.secho(skip_reason, fg=typer.colors.YELLOW, err=True)
334
+ return
335
+ before = _read_head(git_run)
336
+ result = git_run(
337
+ ["git", "pull", "--ff-only"],
338
+ env={**os.environ, "GIT_TERMINAL_PROMPT": "0"},
339
+ capture_output=True,
340
+ text=True,
341
+ timeout=_GIT_PULL_TIMEOUT_SECONDS,
342
+ )
343
+ if result.returncode != 0:
344
+ typer.secho(_PULL_SKIPPED_WARNING, fg=typer.colors.YELLOW, err=True)
345
+ return
346
+ _report_fast_forward(git_run, before, _read_head(git_run))
347
+
348
+
349
+ def _pull_skip_reason(git_run: Callable[..., Any]) -> Optional[str]:
350
+ """Return the outcome text to print, or None when the pull may proceed.
351
+
352
+ An empty string marks a silent skip, because the workspace holds no safe
353
+ fast-forward and reporting it would only add noise. A dirty worktree
354
+ returns the skipped warning, because tracked changes can conflict.
355
+ """
356
+ git_dir = Path.cwd() / ".git"
357
+ if any((git_dir / marker).exists() for marker in _IN_PROGRESS_GIT_MARKERS):
358
+ return ""
359
+ detached = git_run(
360
+ ["git", "symbolic-ref", "-q", "HEAD"],
361
+ capture_output=True,
362
+ timeout=_GIT_PULL_TIMEOUT_SECONDS,
363
+ )
364
+ if detached.returncode != 0:
365
+ return ""
366
+ upstream = git_run(
367
+ ["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
368
+ capture_output=True,
369
+ timeout=_GIT_PULL_TIMEOUT_SECONDS,
370
+ )
371
+ if upstream.returncode != 0:
372
+ return ""
373
+ status = git_run(
374
+ ["git", "status", "--porcelain"],
375
+ capture_output=True,
376
+ text=True,
377
+ timeout=_GIT_PULL_TIMEOUT_SECONDS,
378
+ )
379
+ tracked_changes = [
380
+ line
381
+ for line in status.stdout.splitlines()
382
+ if line and not line.startswith("??")
383
+ ]
384
+ if tracked_changes:
385
+ return _PULL_SKIPPED_WARNING
386
+ return None
387
+
388
+
389
+ def _read_head(git_run: Callable[..., Any]) -> str:
390
+ """Read the current commit id through the injected Git runner."""
391
+ return git_run(
392
+ ["git", "rev-parse", "HEAD"],
393
+ capture_output=True,
394
+ text=True,
395
+ timeout=_GIT_PULL_TIMEOUT_SECONDS,
396
+ ).stdout.strip()
397
+
398
+
399
+ def _report_fast_forward(git_run: Callable[..., Any], before: str, after: str) -> None:
400
+ """Report how many commits the fast-forward moved, or stay silent.
401
+
402
+ A pull that moves no commit has nothing to report, so it prints nothing.
403
+ """
404
+ if after == before:
405
+ return
406
+ count_output = git_run(
407
+ ["git", "rev-list", "--count", f"{before}..{after}"],
408
+ capture_output=True,
409
+ text=True,
410
+ timeout=_GIT_PULL_TIMEOUT_SECONDS,
411
+ ).stdout.strip()
412
+ commits = int(count_output) if count_output else 0
413
+ if commits <= 0:
414
+ return
415
+ noun = "commit" if commits == 1 else "commits"
416
+ typer.secho(
417
+ f"Workspace fast-forwarded ({commits} {noun})",
418
+ fg=typer.colors.GREEN,
419
+ err=True,
420
+ )
251
421
 
252
422
 
253
423
  def _start_background_update_check(container: Container) -> list[str]:
@@ -285,7 +455,7 @@ def _run_startup_preamble(
285
455
  its ``agent`` so the preflight validates the agent prompt; ``resume``
286
456
  leaves it ``None`` and validates no agent.
287
457
  """
288
- _run_health_checks()
458
+ _run_health_checks(container.resolve(IConfigService))
289
459
  typer.echo("Checking configurations...", err=True)
290
460
  fired.extend(
291
461
  _run_cli_preflight_check(
@@ -692,6 +862,7 @@ def handle_new_session( # noqa: PLR0913
692
862
  setup_editor=setup_editor,
693
863
  setup_api_key=setup_api_key,
694
864
  )
865
+ _pull_workspace_on_start(container.resolve(IConfigService))
695
866
  _echo_config_success(container, agent, model=model)
696
867
 
697
868
  session_manager: ISessionManager = container.resolve(ISessionManager)
@@ -0,0 +1,138 @@
1
+ import logging
2
+ import os
3
+ from pathlib import Path
4
+ from teddy_executor.core.ports.outbound.repo_tree_generator import IRepoTreeGenerator
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ UNREADABLE_MARKER = "[... directory not expanded (unreadable): 0 folders, 0 files]"
9
+
10
+
11
+ class LocalRepoTreeGenerator(IRepoTreeGenerator):
12
+ """
13
+ An adapter that generates a file tree for the local repository,
14
+ respecting .gitignore and .teddyignore rules.
15
+ """
16
+
17
+ def __init__(self, root_dir: str = ".", max_depth: int = 0):
18
+ from teddy_executor.adapters.outbound.filesystem_helpers import (
19
+ load_ignore_spec,
20
+ )
21
+
22
+ assert max_depth >= 0, f"max_depth must be non-negative, got {max_depth}"
23
+ self.root_dir = Path(root_dir).resolve()
24
+ self.ignore_spec = load_ignore_spec(self.root_dir)
25
+ self._max_depth = max_depth
26
+
27
+ def generate_tree(self) -> str:
28
+ """
29
+ Generates a string representation of the file tree in a single pass.
30
+ """
31
+ sections: list[tuple[str, str, list[str]]] = []
32
+ root_names = self._collect_directory(self.root_dir, "", sections)
33
+ # Sections sort by their relative path, and the root resolves to ".",
34
+ # so a top-level name below "." (for example "!important") renders
35
+ # before the root block. The "." key preserves that exact order.
36
+ sections.append((".", "", root_names))
37
+ sections.sort(key=lambda item: item[0])
38
+
39
+ rendered: list[str] = []
40
+ for _sort_key, relative_path, names in sections:
41
+ if not names:
42
+ continue
43
+ if relative_path:
44
+ rendered.append("\n".join([f"./{relative_path}:"] + names))
45
+ else:
46
+ rendered.append("\n".join(names))
47
+ return "\n\n".join(rendered)
48
+
49
+ def _collect_directory(
50
+ self,
51
+ directory: str | Path,
52
+ relative_path: str,
53
+ sections: list[tuple[str, str, list[str]]],
54
+ depth: int = 0,
55
+ ) -> list[str]:
56
+ """
57
+ Collects one directory's visible names and recurses into its children.
58
+
59
+ A directory at or beyond the configured depth limit renders one
60
+ omission marker instead of expanding, so truncation is never silent.
61
+ An unreadable directory renders an explicit marker rather than an
62
+ empty section, so it never looks like a readable empty directory.
63
+ Directory and symlink flags come from the scan cache, so each entry
64
+ costs no additional stat call. Symlinks are listed as files and are
65
+ never followed, which keeps circular links from recursing.
66
+ """
67
+ if self._max_depth > 0 and depth >= self._max_depth:
68
+ marker = self._depth_limit_marker(directory, relative_path)
69
+ return [marker] if marker else []
70
+
71
+ entries = self._scan_visible_entries(directory, relative_path)
72
+ if entries is None:
73
+ return [UNREADABLE_MARKER]
74
+
75
+ names: list[str] = []
76
+ for entry, is_directory, child_path in entries:
77
+ names.append(entry.name)
78
+ if is_directory:
79
+ child_names = self._collect_directory(
80
+ entry.path, child_path, sections, depth + 1
81
+ )
82
+ sort_key = child_path.replace("/", os.sep).lower()
83
+ sections.append((sort_key, child_path, child_names))
84
+
85
+ names.sort(key=str.lower)
86
+ return names
87
+
88
+ def _scan_visible_entries(
89
+ self, directory: str | Path, relative_path: str
90
+ ) -> list[tuple[os.DirEntry, bool, str]] | None:
91
+ """
92
+ Returns one visible immediate child per entry after ignore rules.
93
+
94
+ The scan is materialized before its context closes, so each DirEntry
95
+ still answers its cached directory flag. An unreadable directory
96
+ returns ``None`` so callers can tell it apart from a readable empty
97
+ one, which returns an empty list.
98
+ """
99
+ try:
100
+ with os.scandir(directory) as iterator:
101
+ entries = list(iterator)
102
+ except PermissionError:
103
+ logger.debug("Skipped unreadable directory %s", directory, exc_info=True)
104
+ return None
105
+ visible: list[tuple[os.DirEntry, bool, str]] = []
106
+ for entry in entries:
107
+ is_directory = entry.is_dir(follow_symlinks=False)
108
+ child_path = (
109
+ f"{relative_path}/{entry.name}" if relative_path else entry.name
110
+ )
111
+ match_path = (child_path + "/") if is_directory else child_path
112
+ if self.ignore_spec.match_file(match_path):
113
+ continue
114
+ visible.append((entry, is_directory, child_path))
115
+ return visible
116
+
117
+ def _depth_limit_marker(self, directory: str | Path, relative_path: str) -> str:
118
+ """
119
+ Renders the omission marker for a directory at the depth limit.
120
+
121
+ Counts only the visible immediate children after ignore rules. An
122
+ unreadable directory renders the unreadable marker instead. Returns an
123
+ empty string when no child is visible, so a genuinely empty directory
124
+ renders no section rather than a misleading marker.
125
+ """
126
+ entries = self._scan_visible_entries(directory, relative_path)
127
+ if entries is None:
128
+ return UNREADABLE_MARKER
129
+ folders = sum(1 for _entry, is_directory, _path in entries if is_directory)
130
+ files = len(entries) - folders
131
+ if not folders and not files:
132
+ return ""
133
+ folder_noun = "folder" if folders == 1 else "folders"
134
+ file_noun = "file" if files == 1 else "files"
135
+ return (
136
+ f"[... directory not expanded (depth limit {self._max_depth}): "
137
+ f"{folders} {folder_noun}, {files} {file_noun}]"
138
+ )
@@ -14,6 +14,13 @@ _INTERPOLATION_PATTERN = re.compile(
14
14
 
15
15
  _MERGE_TAG = "tag:yaml.org,2002:merge"
16
16
 
17
+ # Legacy ``execution.<key>`` names and the canonical group each one now lives in.
18
+ _LEGACY_EXECUTION_KEY_GROUPS = {
19
+ "default_timeout_seconds": "execute",
20
+ "max_output_lines": "execute",
21
+ "similarity_threshold": "edit",
22
+ }
23
+
17
24
 
18
25
  def _strict_load(text: str, source_path: str) -> Any:
19
26
  """Parse ``text`` and reject a repeated mapping key at any depth."""
@@ -114,10 +121,35 @@ class YamlConfigAdapter(IConfigService):
114
121
  try:
115
122
  with open(self._config_path, "r", encoding="utf-8") as f:
116
123
  data = _strict_load(f.read(), self._config_path)
117
- return data if isinstance(data, dict) else {}
124
+ user_config = data if isinstance(data, dict) else {}
118
125
  except (yaml.YAMLError, OSError):
119
126
  return {}
120
127
 
128
+ self._migrate_legacy_execution_keys(user_config)
129
+ return user_config
130
+
131
+ def _migrate_legacy_execution_keys(self, user_config: Dict[str, Any]) -> None:
132
+ """Copies tuned legacy ``execution.*`` values to their canonical groups.
133
+
134
+ A user file written before the execute/edit split still holds these
135
+ keys. The copy runs on the in-memory user dict before the baseline
136
+ merge, so the canonical key resolves while the user file stays
137
+ untouched. An absent or non-mapping legacy parent is left alone, a
138
+ ``None`` value is skipped, and an existing canonical key wins.
139
+ """
140
+ legacy = user_config.get("execution")
141
+ if not isinstance(legacy, dict):
142
+ return
143
+ for legacy_key, group in _LEGACY_EXECUTION_KEY_GROUPS.items():
144
+ value = legacy.get(legacy_key)
145
+ if value is None:
146
+ continue
147
+ target = user_config.get(group)
148
+ if not isinstance(target, dict):
149
+ target = {}
150
+ user_config[group] = target
151
+ target.setdefault(legacy_key, value)
152
+
121
153
  def _merge_dicts(self, base: Dict[str, Any], overrides: Dict[str, Any]) -> None:
122
154
  """Recursively merges overrides into base. Prunes keys set to None."""
123
155
  for key, value in overrides.items():
@@ -135,7 +135,7 @@ class ActionFactory(IActionFactory):
135
135
  if "timeout" not in execute_params and self._config_service:
136
136
  # Safe-by-Default: Provide hardcoded 60.0 fallback if config is missing
137
137
  default_timeout = self._config_service.get_setting(
138
- "execution.default_timeout_seconds", 60.0
138
+ "execute.default_timeout_seconds", 60.0
139
139
  )
140
140
  if default_timeout is not None:
141
141
  from teddy_executor.core.services.parser_infrastructure import (
@@ -154,7 +154,7 @@ class ActionFactory(IActionFactory):
154
154
  if "similarity_threshold" not in kwargs and self._config_service:
155
155
  # Safe-by-Default: Provide domain default if config is missing
156
156
  global_threshold = self._config_service.get_setting(
157
- "execution.similarity_threshold", DEFAULT_SIMILARITY_THRESHOLD
157
+ "edit.similarity_threshold", DEFAULT_SIMILARITY_THRESHOLD
158
158
  )
159
159
  if global_threshold is not None:
160
160
  from teddy_executor.core.services.parser_infrastructure import (
@@ -131,7 +131,7 @@ def resolve_similarity_threshold(
131
131
  Resolves in order: Action Params -> Global Config -> Domain Default.
132
132
  """
133
133
  global_threshold_raw = config_service.get_setting(
134
- "execution.similarity_threshold", DEFAULT_SIMILARITY_THRESHOLD
134
+ "edit.similarity_threshold", DEFAULT_SIMILARITY_THRESHOLD
135
135
  )
136
136
  global_threshold = (
137
137
  float(global_threshold_raw)
@@ -139,9 +139,7 @@ def resolve_similarity_threshold(
139
139
  else DEFAULT_SIMILARITY_THRESHOLD
140
140
  )
141
141
 
142
- threshold_raw = action_params.get(
143
- "execution.similarity_threshold", global_threshold
144
- )
142
+ threshold_raw = action_params.get("edit.similarity_threshold", global_threshold)
145
143
  return float(threshold_raw) if threshold_raw is not None else global_threshold
146
144
 
147
145
 
@@ -76,7 +76,7 @@ def register_infrastructure(container: punq.Container) -> None:
76
76
  factory=lambda: ShellAdapter(
77
77
  command_builder=container.resolve(ShellCommandBuilder),
78
78
  max_execute_lines=container.resolve(IConfigService).get_setting(
79
- "execution.max_output_lines"
79
+ "execute.max_output_lines", 100
80
80
  ),
81
81
  # Shared process-global guard: the SAME instance the session-loop
82
82
  # boundary installs and the orchestrator drains, so an in-flight
@@ -143,7 +143,11 @@ def register_infrastructure(container: punq.Container) -> None:
143
143
  )
144
144
  container.register(
145
145
  IRepoTreeGenerator,
146
- factory=lambda: LocalRepoTreeGenerator(),
146
+ factory=lambda: LocalRepoTreeGenerator(
147
+ max_depth=container.resolve(IConfigService).get_setting(
148
+ "context.max_tree_depth", 0
149
+ )
150
+ ),
147
151
  scope=punq.Scope.transient,
148
152
  )
149
153
  container.register(
@@ -2,14 +2,24 @@
2
2
 
3
3
  editor: ""
4
4
 
5
- execution:
6
- default_timeout_seconds: 60 # Default timeout for EXECUTE actions (in seconds).
7
- similarity_threshold: 0.95 # EDIT match threshold (1.0 = exact match; ignores relative indentation).
5
+ execute:
6
+ default_timeout_seconds: 60 # Default timeout for EXECUTE actions.
8
7
  max_output_lines: 100 # Caps EXECUTE stdout/stderr output lines.
9
8
 
9
+ edit:
10
+ similarity_threshold: 0.95 # EDIT match threshold (1.0 = exact match; ignores relative indentation).
11
+
12
+ workspace:
13
+ init: true # Initialize a missing Git repository (start and resume).
14
+ pre-commit: true # Install pre-commit and post-commit git hooks (start and resume).
15
+ pull: true # Fast-forward pull before starting a new session (start only).
16
+
10
17
  read:
11
18
  max_lines: 1000 # Caps READ action output lines.
12
19
 
20
+ context:
21
+ max_tree_depth: 4 # Max project tree depth (0 = unlimited).
22
+
13
23
  research:
14
24
  max_results: 5 # Number of search results to fetch and scrape.
15
25
 
@@ -1,81 +0,0 @@
1
- import logging
2
- import os
3
- from pathlib import Path
4
- from teddy_executor.core.ports.outbound.repo_tree_generator import IRepoTreeGenerator
5
-
6
- logger = logging.getLogger(__name__)
7
-
8
-
9
- class LocalRepoTreeGenerator(IRepoTreeGenerator):
10
- """
11
- An adapter that generates a file tree for the local repository,
12
- respecting .gitignore and .teddyignore rules.
13
- """
14
-
15
- def __init__(self, root_dir: str = "."):
16
- from teddy_executor.adapters.outbound.filesystem_helpers import (
17
- load_ignore_spec,
18
- )
19
-
20
- self.root_dir = Path(root_dir).resolve()
21
- self.ignore_spec = load_ignore_spec(self.root_dir)
22
-
23
- def generate_tree(self) -> str:
24
- """
25
- Generates a string representation of the file tree in a single pass.
26
- """
27
- sections: list[tuple[str, str, list[str]]] = []
28
- root_names = self._collect_directory(self.root_dir, "", sections)
29
- # Sections sort by their relative path, and the root resolves to ".",
30
- # so a top-level name below "." (for example "!important") renders
31
- # before the root block. The "." key preserves that exact order.
32
- sections.append((".", "", root_names))
33
- sections.sort(key=lambda item: item[0])
34
-
35
- rendered: list[str] = []
36
- for _sort_key, relative_path, names in sections:
37
- if not names:
38
- continue
39
- if relative_path:
40
- rendered.append("\n".join([f"./{relative_path}:"] + names))
41
- else:
42
- rendered.append("\n".join(names))
43
- return "\n\n".join(rendered)
44
-
45
- def _collect_directory(
46
- self,
47
- directory: str | Path,
48
- relative_path: str,
49
- sections: list[tuple[str, str, list[str]]],
50
- ) -> list[str]:
51
- """
52
- Collects one directory's visible names and recurses into its children.
53
-
54
- Directory and symlink flags come from the scan cache, so each entry
55
- costs no additional stat call. Symlinks are listed as files and are
56
- never followed, which keeps circular links from recursing.
57
- """
58
- names: list[str] = []
59
- try:
60
- with os.scandir(directory) as iterator:
61
- entries = list(iterator)
62
- except PermissionError:
63
- logger.debug("Skipping unreadable directory %s", directory, exc_info=True)
64
- return names
65
-
66
- for entry in entries:
67
- is_directory = entry.is_dir(follow_symlinks=False)
68
- child_path = (
69
- f"{relative_path}/{entry.name}" if relative_path else entry.name
70
- )
71
- match_path = (child_path + "/") if is_directory else child_path
72
- if self.ignore_spec.match_file(match_path):
73
- continue
74
- names.append(entry.name)
75
- if is_directory:
76
- child_names = self._collect_directory(entry.path, child_path, sections)
77
- sort_key = child_path.replace("/", os.sep).lower()
78
- sections.append((sort_key, child_path, child_names))
79
-
80
- names.sort(key=str.lower)
81
- return names