teddy-cli 0.1.4__tar.gz → 0.1.5__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 (142) hide show
  1. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/PKG-INFO +2 -1
  2. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/pyproject.toml +2 -1
  3. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/__main__.py +79 -13
  4. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/inbound/cli_helpers.py +15 -0
  5. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/inbound/session_cli_handlers.py +59 -0
  6. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/parser_metadata.py +76 -0
  7. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/planning_service.py +1 -0
  8. teddy_cli-0.1.5/src/teddy_executor/core/services/update_checker.py +212 -0
  9. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/resources/config/config.yaml +1 -0
  10. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/resources/config/prompts/architect.xml +24 -6
  11. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/resources/config/prompts/assistant.xml +3 -0
  12. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/resources/config/prompts/debugger.xml +6 -3
  13. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/resources/config/prompts/developer.xml +10 -4
  14. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/resources/config/prompts/pathfinder.xml +6 -3
  15. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/resources/config/prompts/prototyper.xml +4 -1
  16. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/LICENSE +0 -0
  17. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/__init__.py +0 -0
  18. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/__init__.py +0 -0
  19. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/inbound/__init__.py +0 -0
  20. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/inbound/cli_formatter.py +0 -0
  21. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/inbound/console_plan_reviewer.py +0 -0
  22. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/inbound/textual_plan_reviewer.py +0 -0
  23. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_app.py +0 -0
  24. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_editor.py +0 -0
  25. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_execution.py +0 -0
  26. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_helpers.py +0 -0
  27. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_logic.py +0 -0
  28. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_previews.py +0 -0
  29. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_widgets.py +0 -0
  30. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/outbound/__init__.py +0 -0
  31. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/outbound/console_interactor.py +0 -0
  32. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/outbound/console_interactor_ask_loop.py +0 -0
  33. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/outbound/console_interactor_helpers.py +0 -0
  34. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/outbound/console_tooling.py +0 -0
  35. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/outbound/filesystem_helpers.py +0 -0
  36. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/outbound/litellm_adapter.py +0 -0
  37. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/outbound/local_file_system_adapter.py +0 -0
  38. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/outbound/local_repo_tree_generator.py +0 -0
  39. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/outbound/openrouter_hydrator.py +0 -0
  40. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/outbound/shell_adapter.py +0 -0
  41. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/outbound/shell_command_builder.py +0 -0
  42. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/outbound/system_environment_adapter.py +0 -0
  43. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/outbound/system_environment_inspector.py +0 -0
  44. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/outbound/system_time_adapter.py +0 -0
  45. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/outbound/web_scraper_adapter.py +0 -0
  46. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/outbound/web_searcher_adapter.py +0 -0
  47. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/adapters/outbound/yaml_config_adapter.py +0 -0
  48. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/container.py +0 -0
  49. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/__init__.py +0 -0
  50. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/domain/__init__.py +0 -0
  51. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/domain/models/__init__.py +0 -0
  52. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/domain/models/action_ports.py +0 -0
  53. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/domain/models/change_set.py +0 -0
  54. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/domain/models/exceptions.py +0 -0
  55. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/domain/models/execution_report.py +0 -0
  56. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/domain/models/orchestrator_ports.py +0 -0
  57. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/domain/models/plan.py +0 -0
  58. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/domain/models/planning_ports.py +0 -0
  59. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/domain/models/project_context.py +0 -0
  60. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/domain/models/report_assembly_data.py +0 -0
  61. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/domain/models/session.py +0 -0
  62. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/domain/models/shell_output.py +0 -0
  63. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/domain/models/web_search_results.py +0 -0
  64. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/__init__.py +0 -0
  65. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/inbound/__init__.py +0 -0
  66. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/inbound/edit_simulator.py +0 -0
  67. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/inbound/get_context_use_case.py +0 -0
  68. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/inbound/init.py +0 -0
  69. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/inbound/plan_parser.py +0 -0
  70. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/inbound/plan_reviewer.py +0 -0
  71. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/inbound/plan_validator.py +0 -0
  72. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/inbound/planning_use_case.py +0 -0
  73. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/inbound/run_plan_use_case.py +0 -0
  74. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/outbound/__init__.py +0 -0
  75. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/outbound/config_service.py +0 -0
  76. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/outbound/environment_inspector.py +0 -0
  77. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/outbound/execution_report_assembler.py +0 -0
  78. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/outbound/file_system_manager.py +0 -0
  79. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/outbound/llm_client.py +0 -0
  80. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/outbound/markdown_report_formatter.py +0 -0
  81. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/outbound/prompt_manager.py +0 -0
  82. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/outbound/repo_tree_generator.py +0 -0
  83. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/outbound/session_loop_guard.py +0 -0
  84. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/outbound/session_manager.py +0 -0
  85. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/outbound/session_repository.py +0 -0
  86. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/outbound/shell_executor.py +0 -0
  87. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/outbound/system_environment.py +0 -0
  88. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/outbound/time_service.py +0 -0
  89. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/outbound/user_interactor.py +0 -0
  90. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/outbound/web_scraper.py +0 -0
  91. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/ports/outbound/web_searcher.py +0 -0
  92. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/__init__.py +0 -0
  93. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/action_changeset_builder.py +0 -0
  94. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/action_diff_manager.py +0 -0
  95. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/action_dispatcher.py +0 -0
  96. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/action_executor.py +0 -0
  97. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/action_factory.py +0 -0
  98. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/action_parser_complex.py +0 -0
  99. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/action_parser_strategies.py +0 -0
  100. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/context_service.py +0 -0
  101. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/edit_simulator.py +0 -0
  102. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/execution_orchestrator.py +0 -0
  103. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/execution_report_assembler.py +0 -0
  104. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/init_service.py +0 -0
  105. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/markdown_plan_parser.py +0 -0
  106. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/markdown_report_formatter.py +0 -0
  107. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/parser_infrastructure.py +0 -0
  108. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/parser_reporting.py +0 -0
  109. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/plan_validator.py +0 -0
  110. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/prompt_manager.py +0 -0
  111. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/session_lifecycle_manager.py +0 -0
  112. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/session_loop_guard.py +0 -0
  113. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/session_orchestrator.py +0 -0
  114. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/session_planner.py +0 -0
  115. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/session_pruning_service.py +0 -0
  116. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/session_replanner.py +0 -0
  117. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/session_repository.py +0 -0
  118. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/session_service.py +0 -0
  119. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/templates/execution_report.md.j2 +0 -0
  120. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/validation_rules/__init__.py +0 -0
  121. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/validation_rules/edit.py +0 -0
  122. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/validation_rules/edit_matcher.py +0 -0
  123. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/validation_rules/edit_matcher_heuristics.py +0 -0
  124. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/validation_rules/execute.py +0 -0
  125. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/validation_rules/filesystem.py +0 -0
  126. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/validation_rules/helpers.py +0 -0
  127. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/services/validation_rules/message.py +0 -0
  128. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/utils/__init__.py +0 -0
  129. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/utils/diff.py +0 -0
  130. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/utils/io.py +0 -0
  131. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/utils/markdown.py +0 -0
  132. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/utils/serialization.py +0 -0
  133. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/core/utils/string.py +0 -0
  134. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/prompts.py +0 -0
  135. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/registries/__init__.py +0 -0
  136. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/registries/infrastructure.py +0 -0
  137. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/registries/reviewer.py +0 -0
  138. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/registries/validators.py +0 -0
  139. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/resources/__init__.py +0 -0
  140. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/resources/config/.gitignore +0 -0
  141. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/resources/config/__init__.py +0 -0
  142. {teddy_cli-0.1.4 → teddy_cli-0.1.5}/src/teddy_executor/resources/config/init.context +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: teddy-cli
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: A local-first, file-based AI coding workflow that applies the UNIX philosophy to AI collaboration.
5
5
  License: AGPL-3.0-only
6
6
  License-File: LICENSE
@@ -30,6 +30,7 @@ Requires-Dist: python-dotenv (>=1.0.1)
30
30
  Requires-Dist: pyyaml (>=6.0,<7.0)
31
31
  Requires-Dist: requests (>=2.33.0,<3.0.0)
32
32
  Requires-Dist: textual (>=8.1.0,<9.0.0)
33
+ Requires-Dist: tld (>=0.10,<1.0)
33
34
  Requires-Dist: trafilatura (>=2.0.0,<3.0.0)
34
35
  Requires-Dist: typer[all]
35
36
  Requires-Dist: urllib3 (>=2.7.0,<3.0.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "teddy-cli"
3
- version = "0.1.4"
3
+ version = "0.1.5"
4
4
  description = "A local-first, file-based AI coding workflow that applies the UNIX philosophy to AI collaboration."
5
5
  authors = ["Raphael Atteritano"]
6
6
  license = "AGPL-3.0-only"
@@ -29,6 +29,7 @@ bandit = ">=1.7.9,<1.8.0"
29
29
  pip-audit = ">=2.7.3,<2.8.0"
30
30
  textual = "^8.1.0"
31
31
  urllib3 = "^2.7.0"
32
+ tld = ">=0.10,<1.0"
32
33
 
33
34
  [tool.poetry.group.dev.dependencies]
34
35
  pytest = "^9.0.3"
@@ -57,8 +57,12 @@ logging.basicConfig(
57
57
  )
58
58
 
59
59
 
60
- def _ensure_project_initialized(container) -> None:
61
- """Lazily performs project anchoring and initialization."""
60
+ def _ensure_project_initialized(container, root_dir: str | None = None) -> None:
61
+ """Lazily performs project anchoring and initialization.
62
+
63
+ If root_dir is provided, uses that path as the root. Otherwise, finds
64
+ the nearest parent containing .teddy/ via find_project_root().
65
+ """
62
66
  from teddy_executor.adapters.inbound.cli_helpers import find_project_root
63
67
  from teddy_executor.core.ports.outbound.file_system_manager import (
64
68
  IFileSystemManager,
@@ -77,7 +81,10 @@ def _ensure_project_initialized(container) -> None:
77
81
  from teddy_executor.adapters.outbound.yaml_config_adapter import YamlConfigAdapter
78
82
  from punq import Scope
79
83
 
80
- root = str(find_project_root())
84
+ if root_dir is None:
85
+ root = str(find_project_root())
86
+ else:
87
+ root = root_dir
81
88
  c = container
82
89
 
83
90
  regs = getattr(c.registrations, "_Registry__registrations", {})
@@ -170,17 +177,11 @@ def init():
170
177
  """
171
178
  Initializes the .teddy directory and pre-warms heavy imports for faster startup.
172
179
  """
180
+ from teddy_executor.adapters.inbound.cli_helpers import prewarm_imports
181
+
173
182
  container = get_container()
174
- _ensure_project_initialized(container)
175
- # Pre-warm heavy imports to reduce first-run latency
176
- try:
177
- import litellm # noqa: F401
178
- import trafilatura # noqa: F401
179
- import pyperclip # noqa: F401
180
- from bs4 import BeautifulSoup # noqa: F401
181
- from ddgs import DDGS # noqa: F401
182
- except ImportError:
183
- pass # Some optional dependencies may not be installed
183
+ _ensure_project_initialized(container, root_dir=str(Path.cwd()))
184
+ prewarm_imports()
184
185
  # Auto-login would trigger here once `teddy login` is implemented (no-op for now)
185
186
  typer.echo("TeDDy initialized in .teddy folder.")
186
187
 
@@ -194,6 +195,71 @@ def version() -> None:
194
195
  typer.echo(f"TeDDy v{installed}")
195
196
 
196
197
 
198
+ @app.command()
199
+ def update(
200
+ experimental: bool = typer.Option(
201
+ False,
202
+ "--experimental",
203
+ help="Check and upgrade from TestPyPI instead of PyPI.",
204
+ ),
205
+ ):
206
+ """Checks PyPI for the latest version of TeDDy and displays upgrade
207
+ instructions. Does not upgrade automatically."""
208
+ from teddy_executor.core.services.update_checker import (
209
+ get_current_version,
210
+ fetch_latest_version,
211
+ compare_versions,
212
+ is_prerelease,
213
+ PYPI_URL,
214
+ TEST_PYPI_URL,
215
+ )
216
+
217
+ index_url = TEST_PYPI_URL if experimental else PYPI_URL
218
+ # When experimental, include prerelease versions (TestPyPI only has dev releases)
219
+ latest = fetch_latest_version(index_url, stable_only=not experimental)
220
+
221
+ if latest is None:
222
+ typer.echo("Could not check for updates: network error.")
223
+ return
224
+
225
+ current = get_current_version()
226
+
227
+ needs_update = compare_versions(current, latest)
228
+ is_channel_switch = False
229
+ if not needs_update:
230
+ # If current is a pre-release and the latest is stable, allow downgrade
231
+ # to the stable channel
232
+ if is_prerelease(current) and not is_prerelease(latest):
233
+ is_channel_switch = True
234
+
235
+ if not needs_update and not is_channel_switch:
236
+ typer.echo(f"You are already running the latest version ({current}).")
237
+ return
238
+
239
+ if is_channel_switch:
240
+ typer.echo(f"You are running the latest experimental version ({current}).")
241
+ typer.echo(
242
+ "To switch to the stable release, run: pip install --upgrade teddy-cli"
243
+ )
244
+ typer.echo(
245
+ "To apply prompt updates: delete .teddy/prompts/ and run 'teddy init'"
246
+ )
247
+ elif experimental:
248
+ typer.echo(f"A new experimental version {latest} is available.")
249
+ typer.echo(
250
+ "To upgrade, run: pip install --upgrade teddy-cli --index-url https://test.pypi.org/simple/"
251
+ )
252
+ typer.echo(
253
+ "To apply prompt updates: delete .teddy/prompts/ and run 'teddy init'"
254
+ )
255
+ else:
256
+ typer.echo(f"A new version {latest} is available.")
257
+ typer.echo("To upgrade, run: pip install --upgrade teddy-cli")
258
+ typer.echo(
259
+ "To apply prompt updates: delete .teddy/prompts/ and run 'teddy init'"
260
+ )
261
+
262
+
197
263
  @app.command()
198
264
  def context(
199
265
  no_copy: bool = typer.Option(
@@ -247,3 +247,18 @@ def apply_ui_mode_override(container: Container, ui_mode_bool: bool) -> None:
247
247
 
248
248
  mode = "tui" if ui_mode_bool else "console"
249
249
  register_reviewer(container, ui_mode=mode)
250
+
251
+
252
+ def prewarm_imports() -> None:
253
+ """
254
+ Pre-warm heavy imports to reduce first-run latency.
255
+ Extracted from __main__.py init command for reuse by update command.
256
+ """
257
+ try:
258
+ import litellm # noqa: F401
259
+ import trafilatura # noqa: F401
260
+ import pyperclip # noqa: F401
261
+ from bs4 import BeautifulSoup # noqa: F401
262
+ from ddgs import DDGS # noqa: F401
263
+ except ImportError:
264
+ pass
@@ -17,6 +17,13 @@ from teddy_executor.core.utils.string import slugify
17
17
  from teddy_executor.adapters.inbound.cli_formatter import format_project_context
18
18
  from teddy_executor.adapters.inbound.cli_helpers import (
19
19
  echo_and_copy,
20
+ find_project_root,
21
+ )
22
+ from teddy_executor.core.services.update_checker import (
23
+ background_check,
24
+ compare_versions,
25
+ get_current_version,
26
+ read_update_cache,
20
27
  )
21
28
 
22
29
 
@@ -78,6 +85,34 @@ def _orchestrate_session_loop(
78
85
  break
79
86
 
80
87
 
88
+ def _display_update_notification(cache_path: Path) -> None:
89
+ """Check the update cache and display a non-blocking notification
90
+ if a newer version is available. Called on session startup, after
91
+ the background check thread has been started."""
92
+ try:
93
+ cache = read_update_cache(cache_path)
94
+ if cache is None:
95
+ return
96
+ latest = cache.get("latest_version", "")
97
+ if not latest:
98
+ return
99
+ current = get_current_version()
100
+ if compare_versions(current, latest):
101
+ typer.echo(
102
+ typer.style(
103
+ f"ℹ A new version {latest} is available. "
104
+ "To upgrade, run: pip install --upgrade teddy-cli\n"
105
+ " To apply prompt updates: delete .teddy/prompts/ and run 'teddy init'",
106
+ fg=typer.colors.YELLOW,
107
+ )
108
+ )
109
+ except Exception:
110
+ import logging
111
+
112
+ logger = logging.getLogger(__name__)
113
+ logger.debug("Failed to display update notification", exc_info=True)
114
+
115
+
81
116
  def handle_new_session( # noqa: PLR0913
82
117
  container: Container,
83
118
  name: Optional[str],
@@ -91,6 +126,18 @@ def handle_new_session( # noqa: PLR0913
91
126
  api_key: Optional[str] = None,
92
127
  ):
93
128
  """Logic for the 'start' command."""
129
+ import threading
130
+
131
+ # Start non-blocking background version check
132
+ cache_path = find_project_root() / ".teddy" / ".update_cache.json"
133
+ thread = threading.Thread(
134
+ target=background_check,
135
+ args=(cache_path,),
136
+ daemon=True,
137
+ )
138
+ thread.start()
139
+ _display_update_notification(cache_path)
140
+
94
141
  try:
95
142
  # 0. Ensure project is initialized
96
143
  container.resolve(IInitUseCase).ensure_initialized()
@@ -326,6 +373,18 @@ def handle_resume_session( # noqa: PLR0913
326
373
  api_key: Optional[str] = None,
327
374
  ):
328
375
  """Logic for the 'resume' command."""
376
+ import threading
377
+
378
+ # Start non-blocking background version check
379
+ cache_path = find_project_root() / ".teddy" / ".update_cache.json"
380
+ thread = threading.Thread(
381
+ target=background_check,
382
+ args=(cache_path,),
383
+ daemon=True,
384
+ )
385
+ thread.start()
386
+ _display_update_notification(cache_path)
387
+
329
388
  try:
330
389
  # 1. Pre-flight checks
331
390
  typer.echo("Checking configurations...", err=True)
@@ -17,6 +17,76 @@ from teddy_executor.core.services.parser_infrastructure import (
17
17
  )
18
18
 
19
19
 
20
+ def _extract_text_with_emphasis(node: Any) -> str:
21
+ """
22
+ Extract text from an AST node, emitting delimiter markers for Strong/Emphasis tokens.
23
+
24
+ Used only for extracting the value portion of metadata key-value pairs.
25
+ Unlike `get_child_text`, it emits `__text__` for Strong tokens and `_text_`
26
+ for Emphasis tokens, preserving literal double underscores like those in
27
+ `__main__.py` that mistletoe would otherwise parse as formatting.
28
+ """
29
+ from mistletoe.span_token import Strong, Emphasis, RawText
30
+
31
+ if isinstance(node, RawText):
32
+ return getattr(node, "content", "")
33
+
34
+ if isinstance(node, Strong):
35
+ inner = ""
36
+ if hasattr(node, "children") and node.children:
37
+ inner = "".join(_extract_text_with_emphasis(c) for c in node.children)
38
+ return f"__{inner}__"
39
+
40
+ if isinstance(node, Emphasis):
41
+ inner = ""
42
+ if hasattr(node, "children") and node.children:
43
+ inner = "".join(_extract_text_with_emphasis(c) for c in node.children)
44
+ return f"_{inner}_"
45
+
46
+ if hasattr(node, "children") and node.children is not None:
47
+ return "".join(_extract_text_with_emphasis(c) for c in node.children)
48
+ return getattr(node, "content", "")
49
+
50
+
51
+ def _extract_value_after_key(item: "MdListItem", key_text: str) -> Optional[str]:
52
+ """
53
+ Extract the value portion of a key-value pair from an AST ListItem node,
54
+ preserving emphasis delimiters in the value (e.g., for paths like __main__.py).
55
+
56
+ Navigates the item's children to find the node containing the key (a Strong
57
+ token like **Resource:**), then extracts emphasis-aware text from all subsequent
58
+ sibling nodes. When a Strong/Emphasis token's text matches the key, we STOP
59
+ recursion and return the remaining siblings at that level.
60
+ """
61
+ from mistletoe.span_token import Strong, Emphasis
62
+
63
+ key_with_colon = f"{key_text}:"
64
+
65
+ def find_remaining(parent) -> Optional[list[Any]]:
66
+ if not hasattr(parent, "children") or not parent.children:
67
+ return None
68
+ children = list(parent.children)
69
+ for i, child in enumerate(children):
70
+ child_text = get_child_text(child)
71
+ if child_text and key_with_colon in child_text:
72
+ if isinstance(child, (Strong, Emphasis)):
73
+ return children[i + 1 :]
74
+ remaining = find_remaining(child)
75
+ if remaining is not None:
76
+ return remaining
77
+ return None
78
+
79
+ remaining_children = find_remaining(item)
80
+ if not remaining_children:
81
+ return None
82
+
83
+ value_parts = []
84
+ for child in remaining_children:
85
+ value_parts.append(_extract_text_with_emphasis(child))
86
+ result = "".join(value_parts).strip()
87
+ return result if result else None
88
+
89
+
20
90
  def _process_link_key(
21
91
  item: "MdListItem", text: str, key_map: dict[str, str]
22
92
  ) -> Optional[tuple[str, str]]:
@@ -29,6 +99,12 @@ def _process_link_key(
29
99
  if link_node:
30
100
  target = normalize_link_target(link_node.target)
31
101
  return param_key, normalize_path(target)
102
+ # No link found: extract value from plain text using AST-based
103
+ # emphasis-aware extraction to preserve double underscores.
104
+ value = _extract_value_after_key(item, key_text)
105
+ if value is not None:
106
+ return param_key, normalize_path(value)
107
+ # Last resort fallback: use original (possibly corrupted) text
32
108
  parts = text.split(f"{key_text}:", 1)
33
109
  if len(parts) == EXPECTED_KV_PARTS and parts[1].strip():
34
110
  return param_key, normalize_path(parts[1].strip())
@@ -75,6 +75,7 @@ class PlanningService(IPlanningUseCase):
75
75
  agent_name=agent_name,
76
76
  current_turn=Path(turn_dir).name,
77
77
  system_prompt_tokens=system_token_count,
78
+ cache_dir=str(Path(turn_dir).parent),
78
79
  )
79
80
 
80
81
  # Context is purely project state (including initial_request.md via session.context).
@@ -0,0 +1,212 @@
1
+ """Update Checker: Lightweight version check and upgrade mechanism.
2
+
3
+ Provides functions for detecting the current installed version, fetching the
4
+ latest version from PyPI/TestPyPI, comparing versions, caching results, and
5
+ performing upgrades. All public functions use stdlib only (plus the
6
+ `packaging` library which is a transitive dependency via pip-audit).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pathlib import Path
12
+ from typing import TYPE_CHECKING, Optional
13
+
14
+ from packaging.version import Version
15
+
16
+ if TYPE_CHECKING:
17
+ import ssl
18
+
19
+ # --- Constants ---
20
+
21
+ PYPI_URL = "https://pypi.org/pypi/teddy-cli/json"
22
+ TEST_PYPI_URL = "https://test.pypi.org/pypi/teddy-cli/json"
23
+ CACHE_FILENAME = ".update_cache.json"
24
+ CACHE_TTL_HOURS = 24
25
+
26
+
27
+ # --- SSL Context Setup ---
28
+
29
+
30
+ def _create_ssl_context() -> ssl.SSLContext:
31
+ """
32
+ Create an SSL context with proper CA bundle.
33
+
34
+ Priority order:
35
+ 1. certifi if available (provides latest Mozilla CA bundle)
36
+ 2. Default SSL context (system CA bundle)
37
+
38
+ Returns an ssl.SSLContext object suitable for urllib.
39
+ """
40
+ import ssl # noqa: F811 (imported at module level under TYPE_CHECKING)
41
+
42
+ try:
43
+ import certifi
44
+
45
+ cafile = certifi.where()
46
+ if Path(cafile).is_file():
47
+ return ssl.create_default_context(cafile=cafile)
48
+ except ImportError:
49
+ pass
50
+
51
+ # Fallback: use system default (may fail on some Python 3.14 macOS builds)
52
+ return ssl.create_default_context()
53
+
54
+
55
+ # --- Public API ---
56
+
57
+
58
+ def get_current_version() -> str:
59
+ """
60
+ Read installed version from importlib.metadata.
61
+ Falls back to '0.0.0' for dev installations or missing package.
62
+ """
63
+ try:
64
+ from importlib.metadata import version as _get_version
65
+
66
+ return _get_version("teddy-cli")
67
+ except Exception:
68
+ return "0.0.0"
69
+
70
+
71
+ def fetch_latest_version(
72
+ index_url: str = PYPI_URL,
73
+ stable_only: bool = True,
74
+ ) -> Optional[str]:
75
+ """
76
+ Fetch the highest version from PyPI/TestPyPI JSON API.
77
+
78
+ Scans all releases in data['releases'] (instead of only data['info']['version']),
79
+ and optionally filters to stable versions only.
80
+
81
+ Args:
82
+ index_url: The PyPI JSON API URL.
83
+ stable_only: If True (default), only consider stable (non-prerelease) versions.
84
+ If False, consider all versions including dev/pre-releases.
85
+
86
+ Returns:
87
+ The highest matching version string, or None on failure.
88
+ """
89
+ import json
90
+ import urllib.error
91
+ import urllib.request
92
+
93
+ try:
94
+ req = urllib.request.Request(
95
+ index_url,
96
+ headers={
97
+ "User-Agent": "TeDDy-Update-Checker/1.0",
98
+ "Accept": "application/json",
99
+ },
100
+ )
101
+ context = _create_ssl_context()
102
+ with urllib.request.urlopen(req, timeout=10, context=context) as resp: # nosec
103
+ data = json.loads(resp.read().decode("utf-8"))
104
+ releases = data.get("releases", {})
105
+ if not releases:
106
+ # Fallback to info.version if releases dict is empty
107
+ return data.get("info", {}).get("version")
108
+ valid_versions = []
109
+ for v_str in releases.keys():
110
+ try:
111
+ ver = Version(v_str)
112
+ if stable_only and ver.is_prerelease:
113
+ continue
114
+ valid_versions.append(ver)
115
+ except Exception:
116
+ pass
117
+ if not valid_versions:
118
+ return None
119
+ highest = max(valid_versions)
120
+ return str(highest)
121
+ except (urllib.error.URLError, OSError, json.JSONDecodeError, KeyError) as e:
122
+ import logging
123
+
124
+ logger = logging.getLogger(__name__)
125
+ logger.debug("fetch_latest_version failed: %s", e)
126
+ return None
127
+
128
+
129
+ def is_prerelease(version_str: str) -> bool:
130
+ """
131
+ Returns True if the given version string is a pre-release (dev, alpha, beta, rc, etc.)
132
+ according to PEP 440. Returns False on any parse failure.
133
+ """
134
+ try:
135
+ return Version(version_str).is_prerelease
136
+ except Exception:
137
+ return False
138
+
139
+
140
+ def compare_versions(current: str, latest: str) -> bool:
141
+ """
142
+ Returns True if latest > current using PEP 440 version comparison.
143
+ Returns False on any parse failure.
144
+ """
145
+ try:
146
+ return Version(latest) > Version(current)
147
+ except Exception:
148
+ return False
149
+
150
+
151
+ def read_update_cache(cache_path: Path) -> Optional[dict]:
152
+ """
153
+ Read the cache file. Returns None if:
154
+ - File is missing
155
+ - File is corrupt (invalid JSON)
156
+ - File has invalid structure (missing keys)
157
+ - TTL exceeded (24h from checked_at)
158
+ """
159
+ import json
160
+ from datetime import datetime, timezone, timedelta
161
+
162
+ try:
163
+ if not cache_path.is_file():
164
+ return None
165
+ data = json.loads(cache_path.read_text(encoding="utf-8"))
166
+ if not isinstance(data, dict):
167
+ return None
168
+ if "latest_version" not in data or "checked_at" not in data:
169
+ return None
170
+ checked_at = datetime.fromisoformat(data["checked_at"])
171
+ if datetime.now(timezone.utc) - checked_at > timedelta(hours=CACHE_TTL_HOURS):
172
+ return None
173
+ return data
174
+ except (OSError, json.JSONDecodeError, ValueError, TypeError):
175
+ return None
176
+
177
+
178
+ def write_update_cache(cache_path: Path, latest_version: str) -> None:
179
+ """
180
+ Atomically write the cache file (write to temp file, rename).
181
+ Ensures the main thread never reads a partially written file.
182
+ """
183
+ import json
184
+ from datetime import datetime, timezone
185
+
186
+ cache_data = {
187
+ "latest_version": latest_version,
188
+ "checked_at": datetime.now(timezone.utc).isoformat(),
189
+ }
190
+ cache_path.parent.mkdir(parents=True, exist_ok=True)
191
+ tmp_path = cache_path.with_suffix(".tmp")
192
+ try:
193
+ tmp_path.write_text(json.dumps(cache_data, indent=2), encoding="utf-8")
194
+ tmp_path.rename(cache_path)
195
+ except OSError as e:
196
+ import logging
197
+
198
+ logger = logging.getLogger(__name__)
199
+ logger.debug("Failed to write update cache: %s", e)
200
+ if tmp_path.exists():
201
+ tmp_path.unlink()
202
+
203
+
204
+ def background_check(cache_path: Path, index_url: str = PYPI_URL) -> None:
205
+ """
206
+ Non-blocking background version check.
207
+ Intended to run in a daemon thread. Fetches latest version from PyPI
208
+ and writes it to cache. All errors are silently caught.
209
+ """
210
+ latest = fetch_latest_version(index_url)
211
+ if latest is not None:
212
+ write_update_cache(cache_path, latest)
@@ -9,6 +9,7 @@ ui_mode: "tui"
9
9
  # Fallback chain: Config -> VISUAL/EDITOR env vars -> code -> nano.
10
10
  editor: "code"
11
11
 
12
+
12
13
  # Execution Settings
13
14
  execution:
14
15
  default_timeout_seconds: 60
@@ -29,7 +29,7 @@
29
29
  <step n="2" name="CI Quality Gate">Verify CI pipeline status for the targeted components. Ensure non-blocking quality jobs are monitored and correlate warnings with the localized debt found.</step>
30
30
  <step n="3" name="Specs Alignment">Identify divergences where implementation reality differs from the `Specification Document`. Resolve these via As-Built Alignment by `EDIT`-ing the Spec and Design Docs to match reality.</step>
31
31
  <step n="4" name="Milestone Management">If no active milestone exists, `READ` the Roadmap in `docs/project/PROJECT.md` to `CREATE` the next `Milestone`. You MUST translate ALL its Roadmap requirements into the Milestone document. Change status to `In Progress` and transition to `Completed` once fully implemented.</step>
32
- <step n="5" name="Draft Component Designs">`CREATE` or `EDIT` draft design docs for affected components. Focus strictly on strategic intent, purpose, and key contracts; defer detailed logic. Transition any updated existing docs to `Refactoring` status.</step>
32
+ <step n="5" name="Draft Component Designs">`CREATE` or `EDIT` draft design docs for affected components. Focus strictly on strategic intent, purpose, and key contracts; defer detailed logic. Transition any updated existing docs to `Refactoring` status. If any existing component design docs do not adhere to the Component Design blueprint, update them to align.</step>
33
33
  <step n="6" name="Architecture Maintenance">Update `docs/architecture/ARCHITECTURE.md`'s `Component & Boundary Map` and `Key Architectural Decisions` if needed to align.</step>
34
34
  <step n="7" name="Delta Analysis">Use `EXECUTE git grep` and `READ` on all targeted components, contracts, interfaces, and DTOs referenced in the design. Verify implementation status of every piece the design calls for. Catalog the delta: what already exists (skip — do NOT include as a deliverable), what is partially implemented (note remaining work), and what is entirely missing (add as pending). Only truly unimplemented or incomplete work SHALL appear in the slice deliverables. Do NOT create entries for work that demonstrably exists.</step>
35
35
  <step n="8" name="Create Slice">`CREATE` exactly ONE Vertical Slice following these rules:
@@ -91,17 +91,32 @@
91
91
  </section>
92
92
  <section n="3" name="Failure Modes">
93
93
  <item name="Heading">## Failure Modes</item>
94
- <item name="Content">Explicitly list known failure modes for this component. Ports (Interfaces) MUST NOT return generic "error-swallowing" values (like `None` or `False`) to hide internal crashes; instead, they should allow exceptions to propagate to the boundary where they can be handled transparently.</item>
94
+ <item name="Content">Explicitly list known failure modes for this component. Each entry MUST reference the specific precondition or postcondition it violates. Ports (Interfaces) MUST NOT return generic "error-swallowing" values (like `None` or `False`) to hide internal crashes; instead, they should allow exceptions to propagate to the boundary where they can be handled transparently.</item>
95
+ <item name="Content">Format: "- **Failure Title**: Violates the precondition/postcondition 'XYZ'. The component MUST raise a specific exception rather than returning a partial/invalid result."</item>
95
96
  </section>
96
- <section n="4" name="Ports">
97
+ <section n="4" name="Class Invariants">
98
+ <item name="Heading">## Class Invariants</item>
99
+ <item name="Content">Document conditions that must always hold true for instances of this component (e.g., "turn_counter >= 0", "plan is never None after initialization"). Invariants are checked at public method entry and exit. If no invariants apply, state: "None — stateless component."</item>
100
+ </section>
101
+ <section n="5" name="Ports">
97
102
  <item name="Heading">## Ports</item>
98
103
  <item name="Content">Explicit inbound/outbound relationships.</item>
104
+ <item name="Content">If this component depends on other Ports' contracts, list them under **Contract Dependencies:** with links to the relevant doc sections. Example: "- Relies on `IFileSystemManager.read()` postcondition 'Returns file content as string'."</item>
99
105
  </section>
100
- <section n="4" name="Logic">
106
+ <section n="6" name="Logic">
101
107
  <item name="Heading">## Implementation Details / Logic</item>
102
108
  </section>
103
- <section n="5" name="Contracts">
109
+ <section n="7" name="Contracts">
104
110
  <item name="Heading">## Data Contracts / Methods</item>
111
+ <item name="Content">Document every public method with the following standard subsections:</item>
112
+ <item name="Content">```text
113
+ ### `method_name`
114
+ - **Preconditions:** (required) What must be true before calling. Use "must" language: "key must be a non-empty string."
115
+ - **Postconditions:** (required) What is guaranteed after successful return. Use "returns" or "ensures" language: "Returns a valid ConfigValue. Ensures the cache is updated."
116
+ - **Exceptions:** (required) What errors can be raised and under what conditions.
117
+ - **Invariants:** (optional) Class-level invariants that must hold before and after this method executes.
118
+ ```</item>
119
+ <item name="Content">**Contract Enforcement:** Implementations MUST validate preconditions with explicit guard clauses (assertions or early raises). Postcondition failures MUST be surfaced as specific exceptions, never swallowed. Invariants should be checked at public method entry and exit where practical. Core domain code should enforce contracts at boundary crossings (ports), not deep inside implementation details.</item>
105
120
  </section>
106
121
  <directory_conventions>
107
122
  <rule>Doc path: docs/architecture/BOUNDARY/LAYER/TYPE/name.md -> Source path: src/{pkg}/BOUNDARY/LAYER/TYPE/name.py -> Test path: tests/suites/TEST_TYPE/BOUNDARY/LAYER/TYPE/test_name.py</rule>
@@ -140,7 +155,7 @@
140
155
  </section>
141
156
  <section n="6" name="Deliverables">
142
157
  <item name="Heading">## Deliverables</item>
143
- <item name="Content">Checklist of atomic units of work ordered following the Deliverable Dependency Sequence: 1. Contract (Interfaces; DTOs; Signatures; Expansion), 2. Harness (Infrastructure; Env), 3. Seam (Abstraction; Toggles; DI), 4. Logic (Rules; Algorithms), 5. Wiring (Connecting components to the entrypoints/Composition Root), 6. Migration (Updating consumers), 7. Refactor (Internal restructuring; non-breaking), 8. Cleanup (Pruning; Contraction). Every deliverable MUST be an atomic "Green-to-Green" transition; behavioral tests MUST be bundled with the Logic or Wiring deliverable that satisfies them, never committed as standalone failing states. Format: `- [ ] **Type** - Description`.</item>
158
+ <item name="Content">Checklist of atomic units of work ordered following the Tracer Bullet Dependency Sequence: 1. Contract (Interfaces; DTOs; Signatures; Expansion), 2. Harness (Infrastructure; Env), 3. Seam (Abstraction; Toggles; DI), 4. Wiring (The Tracer Bullet: Connecting components to the Composition Root/entrypoints returning trivial/hardcoded data to prove the end-to-end path), 5. Logic (Replacing the Tracer Bullet with complex Rules/Algorithms via TDD), 6. Migration (Updating consumers), 7. Refactor (Internal restructuring; non-breaking), 8. Cleanup (Pruning; Contraction). Every deliverable MUST be an atomic "Green-to-Green" transition; behavioral tests MUST be bundled with Wiring; Unit tests with Logic. Format: `- [ ] **Type** - Description`.</item>
144
159
  </section>
145
160
  <section n="7" name="Implementation Notes">
146
161
  <item name="Heading">## Implementation Notes</item>
@@ -243,6 +258,9 @@ WIP & REMINDERS:
243
258
  2. Commit Message: Use Conventional Commits '<type>(<scope>): <description>' wrapped in single quotes. Use imperative mood. Append ! for breaking changes.
244
259
  3. Execution: You MUST test, stage, commit locally, pull safely with rebase, and push by executing exactly this codeblock:
245
260
  ```shell
261
+ # 0. Ensure commit hooks are installed
262
+ pre-commit install -t pre-commit -t post-commit
263
+
246
264
  # 1. Stage changes and run pre-commit
247
265
  git add .
248
266
  pre-commit run || true
@@ -90,6 +90,9 @@ WIP & REMINDERS:
90
90
  2. Commit Message: Use Conventional Commits '<type>(<scope>): <description>' wrapped in single quotes. Use imperative mood. Append ! for breaking changes.
91
91
  3. Execution: You MUST test, stage, commit locally, pull safely with rebase, and push by executing exactly this codeblock:
92
92
  ```shell
93
+ # 0. Ensure commit hooks are installed
94
+ pre-commit install -t pre-commit -t post-commit
95
+
93
96
  # 1. Stage changes and run pre-commit
94
97
  git add .
95
98
  pre-commit run || true