teddy-cli 0.1.5__tar.gz → 0.1.7__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 (144) hide show
  1. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/PKG-INFO +1 -1
  2. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/pyproject.toml +1 -1
  3. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/__main__.py +52 -15
  4. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/inbound/session_cli_handlers.py +91 -4
  5. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_execution.py +3 -0
  6. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/outbound/system_environment_inspector.py +17 -1
  7. teddy_cli-0.1.7/src/teddy_executor/core/ports/inbound/init.py +47 -0
  8. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/outbound/environment_inspector.py +10 -1
  9. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/context_service.py +7 -10
  10. teddy_cli-0.1.7/src/teddy_executor/core/services/init_service.py +138 -0
  11. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/markdown_plan_parser.py +5 -3
  12. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/parser_infrastructure.py +3 -1
  13. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/validation_rules/helpers.py +1 -1
  14. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/resources/config/config.yaml +0 -5
  15. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/resources/config/prompts/architect.xml +2 -5
  16. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/resources/config/prompts/assistant.xml +0 -3
  17. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/resources/config/prompts/debugger.xml +1 -4
  18. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/resources/config/prompts/developer.xml +2 -5
  19. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/resources/config/prompts/pathfinder.xml +2 -5
  20. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/resources/config/prompts/prototyper.xml +22 -20
  21. teddy_cli-0.1.5/src/teddy_executor/core/ports/inbound/init.py +0 -15
  22. teddy_cli-0.1.5/src/teddy_executor/core/services/init_service.py +0 -80
  23. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/LICENSE +0 -0
  24. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/__init__.py +0 -0
  25. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/__init__.py +0 -0
  26. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/inbound/__init__.py +0 -0
  27. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/inbound/cli_formatter.py +0 -0
  28. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/inbound/cli_helpers.py +0 -0
  29. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/inbound/console_plan_reviewer.py +0 -0
  30. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/inbound/textual_plan_reviewer.py +0 -0
  31. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_app.py +0 -0
  32. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_editor.py +0 -0
  33. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_helpers.py +0 -0
  34. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_logic.py +0 -0
  35. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_previews.py +0 -0
  36. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/inbound/textual_plan_reviewer_widgets.py +0 -0
  37. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/outbound/__init__.py +0 -0
  38. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/outbound/console_interactor.py +0 -0
  39. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/outbound/console_interactor_ask_loop.py +0 -0
  40. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/outbound/console_interactor_helpers.py +0 -0
  41. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/outbound/console_tooling.py +0 -0
  42. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/outbound/filesystem_helpers.py +0 -0
  43. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/outbound/litellm_adapter.py +0 -0
  44. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/outbound/local_file_system_adapter.py +0 -0
  45. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/outbound/local_repo_tree_generator.py +0 -0
  46. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/outbound/openrouter_hydrator.py +0 -0
  47. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/outbound/shell_adapter.py +0 -0
  48. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/outbound/shell_command_builder.py +0 -0
  49. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/outbound/system_environment_adapter.py +0 -0
  50. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/outbound/system_time_adapter.py +0 -0
  51. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/outbound/web_scraper_adapter.py +0 -0
  52. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/outbound/web_searcher_adapter.py +0 -0
  53. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/adapters/outbound/yaml_config_adapter.py +0 -0
  54. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/container.py +0 -0
  55. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/__init__.py +0 -0
  56. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/domain/__init__.py +0 -0
  57. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/domain/models/__init__.py +0 -0
  58. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/domain/models/action_ports.py +0 -0
  59. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/domain/models/change_set.py +0 -0
  60. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/domain/models/exceptions.py +0 -0
  61. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/domain/models/execution_report.py +0 -0
  62. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/domain/models/orchestrator_ports.py +0 -0
  63. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/domain/models/plan.py +0 -0
  64. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/domain/models/planning_ports.py +0 -0
  65. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/domain/models/project_context.py +0 -0
  66. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/domain/models/report_assembly_data.py +0 -0
  67. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/domain/models/session.py +0 -0
  68. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/domain/models/shell_output.py +0 -0
  69. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/domain/models/web_search_results.py +0 -0
  70. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/__init__.py +0 -0
  71. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/inbound/__init__.py +0 -0
  72. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/inbound/edit_simulator.py +0 -0
  73. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/inbound/get_context_use_case.py +0 -0
  74. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/inbound/plan_parser.py +0 -0
  75. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/inbound/plan_reviewer.py +0 -0
  76. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/inbound/plan_validator.py +0 -0
  77. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/inbound/planning_use_case.py +0 -0
  78. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/inbound/run_plan_use_case.py +0 -0
  79. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/outbound/__init__.py +0 -0
  80. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/outbound/config_service.py +0 -0
  81. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/outbound/execution_report_assembler.py +0 -0
  82. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/outbound/file_system_manager.py +0 -0
  83. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/outbound/llm_client.py +0 -0
  84. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/outbound/markdown_report_formatter.py +0 -0
  85. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/outbound/prompt_manager.py +0 -0
  86. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/outbound/repo_tree_generator.py +0 -0
  87. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/outbound/session_loop_guard.py +0 -0
  88. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/outbound/session_manager.py +0 -0
  89. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/outbound/session_repository.py +0 -0
  90. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/outbound/shell_executor.py +0 -0
  91. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/outbound/system_environment.py +0 -0
  92. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/outbound/time_service.py +0 -0
  93. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/outbound/user_interactor.py +0 -0
  94. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/outbound/web_scraper.py +0 -0
  95. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/ports/outbound/web_searcher.py +0 -0
  96. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/__init__.py +0 -0
  97. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/action_changeset_builder.py +0 -0
  98. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/action_diff_manager.py +0 -0
  99. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/action_dispatcher.py +0 -0
  100. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/action_executor.py +0 -0
  101. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/action_factory.py +0 -0
  102. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/action_parser_complex.py +0 -0
  103. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/action_parser_strategies.py +0 -0
  104. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/edit_simulator.py +0 -0
  105. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/execution_orchestrator.py +0 -0
  106. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/execution_report_assembler.py +0 -0
  107. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/markdown_report_formatter.py +0 -0
  108. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/parser_metadata.py +0 -0
  109. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/parser_reporting.py +0 -0
  110. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/plan_validator.py +0 -0
  111. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/planning_service.py +0 -0
  112. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/prompt_manager.py +0 -0
  113. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/session_lifecycle_manager.py +0 -0
  114. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/session_loop_guard.py +0 -0
  115. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/session_orchestrator.py +0 -0
  116. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/session_planner.py +0 -0
  117. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/session_pruning_service.py +0 -0
  118. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/session_replanner.py +0 -0
  119. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/session_repository.py +0 -0
  120. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/session_service.py +0 -0
  121. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/templates/execution_report.md.j2 +0 -0
  122. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/update_checker.py +0 -0
  123. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/validation_rules/__init__.py +0 -0
  124. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/validation_rules/edit.py +0 -0
  125. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/validation_rules/edit_matcher.py +0 -0
  126. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/validation_rules/edit_matcher_heuristics.py +0 -0
  127. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/validation_rules/execute.py +0 -0
  128. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/validation_rules/filesystem.py +0 -0
  129. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/services/validation_rules/message.py +0 -0
  130. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/utils/__init__.py +0 -0
  131. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/utils/diff.py +0 -0
  132. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/utils/io.py +0 -0
  133. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/utils/markdown.py +0 -0
  134. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/utils/serialization.py +0 -0
  135. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/core/utils/string.py +0 -0
  136. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/prompts.py +0 -0
  137. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/registries/__init__.py +0 -0
  138. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/registries/infrastructure.py +0 -0
  139. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/registries/reviewer.py +0 -0
  140. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/registries/validators.py +0 -0
  141. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/resources/__init__.py +0 -0
  142. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/resources/config/.gitignore +0 -0
  143. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/src/teddy_executor/resources/config/__init__.py +0 -0
  144. {teddy_cli-0.1.5 → teddy_cli-0.1.7}/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.5
3
+ Version: 0.1.7
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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "teddy-cli"
3
- version = "0.1.5"
3
+ version = "0.1.7"
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"
@@ -172,18 +172,61 @@ def start( # noqa: PLR0913
172
172
  )
173
173
 
174
174
 
175
- @app.command()
176
- def init():
175
+ init_app = typer.Typer()
176
+
177
+
178
+ @init_app.callback(invoke_without_command=True)
179
+ def init_callback(ctx: typer.Context):
177
180
  """
178
181
  Initializes the .teddy directory and pre-warms heavy imports for faster startup.
179
182
  """
180
183
  from teddy_executor.adapters.inbound.cli_helpers import prewarm_imports
184
+ from teddy_executor.core.ports.inbound.init import IInitUseCase
181
185
 
182
186
  container = get_container()
183
187
  _ensure_project_initialized(container, root_dir=str(Path.cwd()))
184
188
  prewarm_imports()
185
189
  # Auto-login would trigger here once `teddy login` is implemented (no-op for now)
186
- typer.echo("TeDDy initialized in .teddy folder.")
190
+ if ctx.invoked_subcommand is not None:
191
+ # A subcommand (e.g., prompts, config) will handle its own initialization
192
+ # and output. Skip the full summary to avoid conflicting messages.
193
+ return
194
+ init_use_case = container.resolve(IInitUseCase)
195
+ summary = init_use_case.ensure_initialized()
196
+ typer.echo(f"TeDDy initialized in .teddy folder. {summary}")
197
+
198
+
199
+ @init_app.command()
200
+ def prompts():
201
+ """
202
+ Overwrites bundled prompt XMLs in .teddy/prompts/ with defaults.
203
+ """
204
+ from teddy_executor.core.ports.inbound.init import IInitUseCase
205
+
206
+ container = get_container()
207
+ init_use_case = container.resolve(IInitUseCase)
208
+ status = init_use_case.ensure_prompts_initialized(overwrite=True)
209
+ typer.echo(status)
210
+
211
+
212
+ @init_app.command()
213
+ def config():
214
+ """
215
+ Overwrites config.yaml, .gitignore, and init.context with defaults.
216
+ """
217
+ from teddy_executor.core.ports.inbound.init import IInitUseCase
218
+
219
+ container = get_container()
220
+ init_use_case = container.resolve(IInitUseCase)
221
+ status = init_use_case.ensure_config_initialized(overwrite=True)
222
+ typer.echo(status)
223
+
224
+
225
+ app.add_typer(
226
+ init_app,
227
+ name="init",
228
+ help="Initialize .teddy directory, prompts, or configuration.",
229
+ )
187
230
 
188
231
 
189
232
  @app.command()
@@ -239,25 +282,19 @@ def update(
239
282
  if is_channel_switch:
240
283
  typer.echo(f"You are running the latest experimental version ({current}).")
241
284
  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'"
285
+ "To switch to the stable release, run: pipx uninstall teddy-cli && pipx install teddy-cli"
246
286
  )
287
+ typer.echo("To apply prompt updates, run: teddy init prompts")
247
288
  elif experimental:
248
289
  typer.echo(f"A new experimental version {latest} is available.")
249
290
  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'"
291
+ "To upgrade, run: pipx upgrade teddy-cli --pip-args '--index-url https://test.pypi.org/simple/'"
254
292
  )
293
+ typer.echo("To apply prompt updates, run: teddy init prompts")
255
294
  else:
256
295
  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
- )
296
+ typer.echo("To upgrade, run: pipx upgrade teddy-cli")
297
+ typer.echo("To apply prompt updates, run: teddy init prompts")
261
298
 
262
299
 
263
300
  @app.command()
@@ -1,5 +1,9 @@
1
+ import logging
2
+ import shutil
3
+ import subprocess # nosec B404
1
4
  from pathlib import Path
2
5
  from typing import Dict, Optional, Sequence
6
+
3
7
  import typer
4
8
  from punq import Container
5
9
 
@@ -27,6 +31,84 @@ from teddy_executor.core.services.update_checker import (
27
31
  )
28
32
 
29
33
 
34
+ logger = logging.getLogger(__name__)
35
+
36
+
37
+ def _ensure_commit_hooks() -> None:
38
+ """Install pre-commit hooks if config exists and CLI is available.
39
+ Shows green notification on success, yellow warning if config missing
40
+ or CLI not found."""
41
+ config_path = Path.cwd() / ".pre-commit-config.yaml"
42
+ if not config_path.exists():
43
+ typer.secho(
44
+ "⚠ No pre-commit hooks configured",
45
+ fg=typer.colors.YELLOW,
46
+ err=True,
47
+ )
48
+ return
49
+ if not shutil.which("pre-commit"):
50
+ typer.secho(
51
+ "⚠ pre-commit CLI not found",
52
+ fg=typer.colors.YELLOW,
53
+ err=True,
54
+ )
55
+ return
56
+ try:
57
+ subprocess.run( # nosec B603 B607
58
+ ["pre-commit", "install", "-t", "pre-commit", "-t", "post-commit"],
59
+ check=True,
60
+ capture_output=True,
61
+ )
62
+ except subprocess.CalledProcessError:
63
+ logger.debug("pre-commit install failed", exc_info=True)
64
+ return
65
+ typer.secho(
66
+ "✓ pre-commit hooks installed",
67
+ fg=typer.colors.GREEN,
68
+ err=True,
69
+ )
70
+
71
+
72
+ def _check_git_initialized() -> None:
73
+ """Verify git repository status. Auto-initializes if not a repo.
74
+ Shows green notification if already a repo or freshly initialized,
75
+ yellow warning if git CLI not found.
76
+ Only checks the current working directory for a `.git` marker.
77
+ Does NOT walk up parent directories."""
78
+ if not shutil.which("git"):
79
+ typer.secho(
80
+ "⚠ Git CLI not found",
81
+ fg=typer.colors.YELLOW,
82
+ err=True,
83
+ )
84
+ return
85
+ # Check if CWD itself contains a .git marker (directory or file)
86
+ if (Path.cwd() / ".git").exists():
87
+ typer.secho(
88
+ "✓ Git repository detected",
89
+ fg=typer.colors.GREEN,
90
+ err=True,
91
+ )
92
+ return
93
+ # No local .git found – auto-initialize
94
+ try:
95
+ subprocess.run(["git", "init"], check=True, capture_output=True) # nosec B603 B607
96
+ except subprocess.CalledProcessError:
97
+ logger.debug("git init failed", exc_info=True)
98
+ return
99
+ typer.secho(
100
+ "✓ Git repository initialized",
101
+ fg=typer.colors.GREEN,
102
+ err=True,
103
+ )
104
+
105
+
106
+ def _run_health_checks() -> None:
107
+ """Run all startup health checks and display notifications if needed."""
108
+ _ensure_commit_hooks()
109
+ _check_git_initialized()
110
+
111
+
30
112
  def _determine_session_name(name: Optional[str], message: Optional[str]) -> str:
31
113
  """Determine session name (slugify message if name is missing)."""
32
114
  if name:
@@ -101,8 +183,7 @@ def _display_update_notification(cache_path: Path) -> None:
101
183
  typer.echo(
102
184
  typer.style(
103
185
  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'",
186
+ "To upgrade, run: pipx upgrade teddy-cli\n",
106
187
  fg=typer.colors.YELLOW,
107
188
  )
108
189
  )
@@ -142,7 +223,10 @@ def handle_new_session( # noqa: PLR0913
142
223
  # 0. Ensure project is initialized
143
224
  container.resolve(IInitUseCase).ensure_initialized()
144
225
 
145
- # 1. Pre-flight checks (Fail-fast before user interaction)
226
+ # 1. Health checks (advisory, non-blocking)
227
+ _run_health_checks()
228
+
229
+ # 2. Pre-flight checks (Fail-fast before user interaction)
146
230
  typer.echo("Checking configurations...", err=True)
147
231
  _run_cli_preflight_check(container, agent=agent)
148
232
  _echo_config_success(container, agent, model=model)
@@ -386,7 +470,10 @@ def handle_resume_session( # noqa: PLR0913
386
470
  _display_update_notification(cache_path)
387
471
 
388
472
  try:
389
- # 1. Pre-flight checks
473
+ # 1. Health checks (advisory, non-blocking)
474
+ _run_health_checks()
475
+
476
+ # 2. Pre-flight checks
390
477
  typer.echo("Checking configurations...", err=True)
391
478
  _run_cli_preflight_check(container)
392
479
 
@@ -154,6 +154,9 @@ async def orchestrate_execution(app: ReviewerApp, node: Any, update_fn: Any) ->
154
154
  action.state = ExecutionStatus.RUNNING
155
155
  app._refresh_node(node)
156
156
 
157
+ # Harvest modified content from pending_temp_file before execution
158
+ harvest_action_content(action, app.INSTRUCTION_MARKER)
159
+
157
160
  try:
158
161
  import anyio
159
162
 
@@ -39,7 +39,7 @@ class SystemEnvironmentInspector(IEnvironmentInspector):
39
39
 
40
40
  def get_git_status(self) -> Optional[str]:
41
41
  """
42
- Gathers the current Git status of the working directory.
42
+ Gathers the current Git status of the working directory (short format).
43
43
  """
44
44
  try:
45
45
  result = self._run_func( # nosec B603 B607
@@ -52,3 +52,19 @@ class SystemEnvironmentInspector(IEnvironmentInspector):
52
52
  return result.stdout.rstrip() if result.stdout else ""
53
53
  except (subprocess.CalledProcessError, FileNotFoundError):
54
54
  return None
55
+
56
+ def get_full_git_status(self) -> Optional[str]:
57
+ """
58
+ Gathers the full Git status of the working directory (including branch info).
59
+ """
60
+ try:
61
+ result = self._run_func( # nosec B603 B607
62
+ ["git", "status"],
63
+ capture_output=True,
64
+ text=True,
65
+ check=True,
66
+ stdin=subprocess.DEVNULL,
67
+ )
68
+ return result.stdout.rstrip() if result.stdout else ""
69
+ except (subprocess.CalledProcessError, FileNotFoundError):
70
+ return None
@@ -0,0 +1,47 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class IInitUseCase(ABC):
5
+ """
6
+ Inbound port for project initialization.
7
+ """
8
+
9
+ @abstractmethod
10
+ def ensure_initialized(self) -> str:
11
+ """
12
+ Ensures the .teddy/ directory and its essential configuration
13
+ files are present in the current project root.
14
+
15
+ Returns:
16
+ A human-readable summary string (e.g., "Config: unchanged. Prompts: updated (3 files).").
17
+ """
18
+ pass
19
+
20
+ @abstractmethod
21
+ def ensure_prompts_initialized(self, overwrite: bool = False) -> str:
22
+ """
23
+ Ensures prompt XML files are present in the .teddy/prompts/ directory.
24
+
25
+ Args:
26
+ overwrite: If True, always overwrite existing prompt files with defaults.
27
+ If False (default), only write missing files.
28
+
29
+ Returns:
30
+ A human-readable status string (e.g., "Prompts overwritten (6 files).").
31
+ """
32
+ pass
33
+
34
+ @abstractmethod
35
+ def ensure_config_initialized(self, overwrite: bool = False) -> str:
36
+ """
37
+ Ensures configuration files (config.yaml, .gitignore, init.context) are present
38
+ in the .teddy/ directory.
39
+
40
+ Args:
41
+ overwrite: If True, always overwrite existing config files with defaults.
42
+ If False (default), only write missing files.
43
+
44
+ Returns:
45
+ A human-readable status string (e.g., "Configuration files overwritten (3 files).").
46
+ """
47
+ pass
@@ -22,9 +22,18 @@ class IEnvironmentInspector(Protocol):
22
22
 
23
23
  def get_git_status(self) -> Optional[str]:
24
24
  """
25
- Gathers the current Git status of the working directory.
25
+ Gathers the current Git status of the working directory (short format).
26
26
 
27
27
  Returns:
28
28
  Optional[str]: The output of 'git status -s' or None if not a git repo.
29
29
  """
30
30
  ...
31
+
32
+ def get_full_git_status(self) -> Optional[str]:
33
+ """
34
+ Gathers the full Git status of the working directory (including branch info).
35
+
36
+ Returns:
37
+ Optional[str]: The output of 'git status' or None if not a git repo.
38
+ """
39
+ ...
@@ -39,7 +39,7 @@ class ContextService(IGetContextUseCase):
39
39
  self._llm_client = llm_client
40
40
  self._web_scraper = web_scraper
41
41
 
42
- def get_context(
42
+ def get_context( # noqa: PLR0913
43
43
  self,
44
44
  context_files: Optional[Dict[str, Sequence[str]]] = None,
45
45
  include_tokens: bool = True,
@@ -53,7 +53,8 @@ class ContextService(IGetContextUseCase):
53
53
  Gathers all project context information by orchestrating its dependencies.
54
54
  """
55
55
  system_info = self._environment_inspector.get_environment_info()
56
- git_status = self._environment_inspector.get_git_status()
56
+ short_git_status = self._environment_inspector.get_git_status()
57
+ full_git_status = self._environment_inspector.get_full_git_status()
57
58
  repo_tree = self._repo_tree_generator.generate_tree()
58
59
 
59
60
  scoped_paths, all_resolved_paths = self._resolve_scoped_paths(context_files)
@@ -79,7 +80,7 @@ class ContextService(IGetContextUseCase):
79
80
  file_contents[url] = None
80
81
 
81
82
  content = self._format_content(
82
- repo_tree, scoped_paths, file_contents, git_status
83
+ repo_tree, scoped_paths, file_contents, full_git_status
83
84
  )
84
85
  content_tokens = (
85
86
  self._llm_client.get_text_token_count(content) if include_tokens else 0
@@ -89,9 +90,9 @@ class ContextService(IGetContextUseCase):
89
90
  header=self._format_header(system_info, current_turn),
90
91
  content=content,
91
92
  scoped_paths=scoped_paths,
92
- git_status=git_status,
93
+ git_status=full_git_status,
93
94
  items=self._collect_items(
94
- scoped_paths, file_contents, git_status, include_tokens
95
+ scoped_paths, file_contents, short_git_status, include_tokens
95
96
  ),
96
97
  agent_name=agent_name,
97
98
  total_window=total_window,
@@ -303,10 +304,6 @@ class ContextService(IGetContextUseCase):
303
304
  git_status: Optional[str] = None,
304
305
  ) -> str:
305
306
  """Formats the main content section of the context report."""
306
- display_status = git_status
307
- if git_status == "":
308
- display_status = "nothing to commit, working tree clean"
309
-
310
307
  # Gather all unique paths
311
308
  all_paths: List[str] = []
312
309
  for paths in scoped_paths.values():
@@ -320,7 +317,7 @@ class ContextService(IGetContextUseCase):
320
317
 
321
318
  content_parts = [
322
319
  "\n## Git Status",
323
- display_status if display_status is not None else "",
320
+ git_status if isinstance(git_status, str) else "",
324
321
  "\n## Project Structure",
325
322
  f"```\n{repo_tree}\n```",
326
323
  ]
@@ -0,0 +1,138 @@
1
+ import logging
2
+ import os
3
+ from importlib import resources
4
+
5
+ import yaml
6
+
7
+ from teddy_executor.core.ports.inbound.init import IInitUseCase
8
+ from teddy_executor.core.ports.outbound.file_system_manager import IFileSystemManager
9
+
10
+
11
+ class InitService(IInitUseCase):
12
+ """
13
+ Service for initializing projects.
14
+ """
15
+
16
+ def __init__(self, file_system: IFileSystemManager, config_dir: str | None = None):
17
+ self._file_system = file_system
18
+ # Find the config directory relative to the package root if not provided
19
+ if config_dir:
20
+ self._config_dir = config_dir
21
+ else:
22
+ # Use importlib.resources to find the bundled config templates
23
+ resource_path = resources.files("teddy_executor.resources.config")
24
+ # Ensure we resolve to an absolute string for compatibility with the FileSystem port
25
+ self._config_dir = os.path.abspath(str(resource_path))
26
+
27
+ def _get_default_content(self, filename: str) -> str | None:
28
+ """Loads default content from the config directory using the file system port."""
29
+ try:
30
+ target_path = os.path.join(self._config_dir, filename)
31
+ if self._file_system.path_exists(target_path):
32
+ return self._file_system.read_file(target_path)
33
+ except (OSError, yaml.YAMLError, ImportError, AttributeError):
34
+ logging.getLogger(__name__).debug(
35
+ "Failed to load default content for %s", filename
36
+ )
37
+ return None
38
+
39
+ def _init_config_dir(self, overwrite: bool = False) -> str:
40
+ """Copies bundled config files (config.yaml, .gitignore, init.context) to .teddy/.
41
+
42
+ Args:
43
+ overwrite: If True, always overwrite existing files. If False, only write missing ones.
44
+
45
+ Returns:
46
+ A status string: "unchanged", "updated (N files)", or "overwritten (N files)".
47
+ """
48
+ config_files = ["config.yaml", ".gitignore", "init.context"]
49
+ count = 0
50
+ for fname in config_files:
51
+ target_path = f".teddy/{fname}"
52
+ if overwrite or not self._file_system.path_exists(target_path):
53
+ content = self._get_default_content(fname)
54
+ if content is not None:
55
+ self._file_system.write_file(target_path, content)
56
+ count += 1
57
+ if count == 0:
58
+ return "unchanged"
59
+ if overwrite:
60
+ return f"overwritten ({count} files)"
61
+ return f"updated ({count} files)"
62
+
63
+ def _init_prompts(self, overwrite: bool = False) -> str:
64
+ """Copies bundled prompt XMLs to .teddy/prompts/.
65
+
66
+ Args:
67
+ overwrite: If True, always overwrite existing files. If False, only write missing ones.
68
+
69
+ Returns:
70
+ A status string: "unchanged", "updated (N files)", or "overwritten (N files)".
71
+ """
72
+ prompts_dir = ".teddy/prompts"
73
+ if not self._file_system.path_exists(prompts_dir):
74
+ self._file_system.create_directory(prompts_dir)
75
+
76
+ prompt_files = [
77
+ "architect.xml",
78
+ "assistant.xml",
79
+ "debugger.xml",
80
+ "developer.xml",
81
+ "pathfinder.xml",
82
+ "prototyper.xml",
83
+ ]
84
+ count = 0
85
+ for fname in prompt_files:
86
+ target_path = f"{prompts_dir}/{fname}"
87
+ if overwrite or not self._file_system.path_exists(target_path):
88
+ content = self._get_default_content(f"prompts/{fname}")
89
+ if content is not None:
90
+ self._file_system.write_file(target_path, content)
91
+ count += 1
92
+ if count == 0:
93
+ return "unchanged"
94
+ if overwrite:
95
+ return f"overwritten ({count} files)"
96
+ return f"updated ({count} files)"
97
+
98
+ def ensure_initialized(self) -> str:
99
+ """
100
+ Ensures the .teddy directory and default files are present.
101
+
102
+ Returns:
103
+ A human-readable summary string (e.g., "Config: unchanged. Prompts: updated (3 files).").
104
+ """
105
+ if not self._file_system.path_exists(".teddy"):
106
+ self._file_system.create_directory(".teddy")
107
+
108
+ config_status = self._init_config_dir(overwrite=False)
109
+ prompts_status = self._init_prompts(overwrite=False)
110
+ return f"Config: {config_status}. Prompts: {prompts_status}."
111
+
112
+ def ensure_prompts_initialized(self, overwrite: bool = True) -> str:
113
+ """
114
+ Ensures prompt XML files are present in the .teddy/prompts/ directory.
115
+
116
+ Args:
117
+ overwrite: If True, always overwrite existing prompt files with defaults.
118
+ If False (default), only write missing files.
119
+
120
+ Returns:
121
+ A human-readable status string (e.g., "Prompts overwritten (6 files).").
122
+ """
123
+ status = self._init_prompts(overwrite=overwrite)
124
+ return f"Prompts {status}."
125
+
126
+ def ensure_config_initialized(self, overwrite: bool = True) -> str:
127
+ """
128
+ Ensures configuration files (config.yaml, .gitignore, init.context) are present in the .teddy/ directory.
129
+
130
+ Args:
131
+ overwrite: If True, always overwrite existing config files with defaults.
132
+ If False (default), only write missing files.
133
+
134
+ Returns:
135
+ A human-readable status string (e.g., "Configuration files overwritten (3 files).").
136
+ """
137
+ status = self._init_config_dir(overwrite=overwrite)
138
+ return f"Configuration files {status}."
@@ -286,22 +286,24 @@ class MarkdownPlanParser(IPlanParser):
286
286
 
287
287
  stream.next() # Consume action heading
288
288
  action_type_str = get_child_text(action_heading).strip().replace("`", "")
289
+ # Normalize: strip non-alphabetic characters so "READ**" matches "READ"
290
+ cleaned_action_type = re.sub(r"[^A-Za-z]", "", action_type_str)
289
291
 
290
292
  # Guard: MESSAGE under ## Action Plan must produce a clear mutual exclusivity error
291
- if action_type_str == "MESSAGE":
293
+ if cleaned_action_type == "MESSAGE":
292
294
  raise InvalidPlanError(
293
295
  "MESSAGE action is not allowed under '## Action Plan'. "
294
296
  "Use '## Message' section instead. Mutual exclusivity is required.",
295
297
  offending_nodes=[action_heading],
296
298
  )
297
299
 
298
- if action_type_str not in self._dispatch_map:
300
+ if cleaned_action_type not in self._dispatch_map:
299
301
  raise InvalidPlanError(
300
302
  f"Unknown action type: {action_type_str}",
301
303
  offending_nodes=[action_heading],
302
304
  )
303
305
 
304
- parse_method = self._dispatch_map[action_type_str]
306
+ parse_method = self._dispatch_map[cleaned_action_type]
305
307
  actions.append(parse_method(stream, node=action_heading))
306
308
 
307
309
  return actions
@@ -142,7 +142,9 @@ def get_action_heading(node: Any, valid_actions: set[str]) -> "Optional[Heading]
142
142
  if isinstance(node, Heading) and node.level == H3_LEVEL:
143
143
  text = get_child_text(node).strip()
144
144
  potential_type = text.split(":")[0].strip().replace("`", "")
145
- if potential_type in valid_actions:
145
+ # Normalize: strip non-alphabetic characters so "READ**" matches "READ"
146
+ cleaned_type = re.sub(r"[^A-Za-z]", "", potential_type)
147
+ if cleaned_type in valid_actions:
146
148
  return node
147
149
  # Allow unknown actions if they are formatted like `ACTION` to fail later
148
150
  children = list(node.children) if node.children else []
@@ -112,7 +112,7 @@ def validate_path_is_safe(path_str: str, action_type: str):
112
112
  Ensures a file path is safe by checking for absolute paths and
113
113
  directory traversal attempts.
114
114
  """
115
- if os.path.isabs(path_str):
115
+ if path_str.startswith("/") or os.path.isabs(path_str):
116
116
  raise PlanValidationError(
117
117
  f"Action `{action_type}` contains an absolute path, which is not allowed: {path_str}"
118
118
  )
@@ -1,15 +1,10 @@
1
1
  # TeDDy Configuration
2
2
 
3
- # UI Settings
4
- # Supported modes: "tui" (Interactive Terminal UI), "console" (Sequential CLI prompts)
5
- ui_mode: "tui"
6
-
7
3
  # The preferred external editor for reviewing and modifying plans/messages.
8
4
  # Supports arbitrary command strings (e.g., "code --wait", "nvim -R", "zed").
9
5
  # Fallback chain: Config -> VISUAL/EDITOR env vars -> code -> nano.
10
6
  editor: "code"
11
7
 
12
-
13
8
  # Execution Settings
14
9
  execution:
15
10
  default_timeout_seconds: 60
@@ -33,7 +33,7 @@
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:
36
- 1. Type & Status: Must be single-purpose (Do NOT mix Feature/Tweak/Refactor). Bugfixes are only created by the Debugger. If Type is Feature, initial Status MUST be `To De-risk`. For Tweak and Refactor, initial Status SHOULD be `Planned`.
36
+ 1. Type & Status: Must be single-purpose (Do NOT mix Feature/Tweak/Refactor). Bugfixes are only created by the Debugger. If Type is Feature, initial Status MUST be `Pending Prototype`. For Tweak and Refactor, initial Status SHOULD be `Planned`.
37
37
  2. Content: MUST include linked Component Docs. If Type is Feature, it MUST include Gherkin scenarios and a final `Wiring` behavioral gate. For Tweak and Refactor, these are omitted or optional.
38
38
  3. Debt: Inject `Refactor`/`Cleanup` deliverables for audit findings, and `Harness` deliverables to patch preventable static analysis gaps.</step>
39
39
  <step n="9" name="Impact Audit & Consistency Check">Use `EXECUTE git grep` on target components. If a component (Port, Service signature, or DTO) has >1 consumer (excluding its own definition), categorize it as a Shared Seam. You MUST decompose the design into smaller, atomic deliverables if >1 consumer, layer, or logical change is affected. For Shared Seams, you MUST partition the plan into a non-breaking sequence (Contract/Expansion -> Migration -> Cleanup/Contraction). Ensure the design follows the Deliverable Dependency Sequence. Resolve hits for deleted/renamed components in documentation and ensure all Contract/Seam changes have matching Harness deliverables.</step>
@@ -130,7 +130,7 @@
130
130
  <description>A cohesive feature that delivers a set of correlated and testable behaviors.</description>
131
131
  <section n="1" name="Metadata">
132
132
  <item name="Heading"># Slice: {{feature_name}}</item>
133
- <item name="Status">- **Status:** [Draft | To De-risk | Planned | In Progress | Completed]</item>
133
+ <item name="Status">- **Status:** [Draft | Pending Prototype | Planned | In Progress | Completed]</item>
134
134
  <item name="Type">- **Type:** [Feature | Tweak | Refactor | Bugfix]</item>
135
135
  <item name="Milestone">- **Milestone:** [link]</item>
136
136
  <item name="Specs">- **Specs:** [links]</item>
@@ -258,9 +258,6 @@ WIP & REMINDERS:
258
258
  2. Commit Message: Use Conventional Commits '<type>(<scope>): <description>' wrapped in single quotes. Use imperative mood. Append ! for breaking changes.
259
259
  3. Execution: You MUST test, stage, commit locally, pull safely with rebase, and push by executing exactly this codeblock:
260
260
  ```shell
261
- # 0. Ensure commit hooks are installed
262
- pre-commit install -t pre-commit -t post-commit
263
-
264
261
  # 1. Stage changes and run pre-commit
265
262
  git add .
266
263
  pre-commit run || true
@@ -90,9 +90,6 @@ 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
-
96
93
  # 1. Stage changes and run pre-commit
97
94
  git add .
98
95
  pre-commit run || true