teddy-cli 0.1.0__py3-none-any.whl

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 (143) hide show
  1. teddy_cli-0.1.0.dist-info/LICENSE +677 -0
  2. teddy_cli-0.1.0.dist-info/METADATA +33 -0
  3. teddy_cli-0.1.0.dist-info/RECORD +143 -0
  4. teddy_cli-0.1.0.dist-info/WHEEL +4 -0
  5. teddy_cli-0.1.0.dist-info/entry_points.txt +3 -0
  6. teddy_executor/__init__.py +1 -0
  7. teddy_executor/__main__.py +335 -0
  8. teddy_executor/adapters/__init__.py +0 -0
  9. teddy_executor/adapters/inbound/__init__.py +0 -0
  10. teddy_executor/adapters/inbound/cli_formatter.py +107 -0
  11. teddy_executor/adapters/inbound/cli_helpers.py +249 -0
  12. teddy_executor/adapters/inbound/console_plan_reviewer.py +69 -0
  13. teddy_executor/adapters/inbound/session_cli_handlers.py +366 -0
  14. teddy_executor/adapters/inbound/textual_plan_reviewer.py +78 -0
  15. teddy_executor/adapters/inbound/textual_plan_reviewer_app.py +367 -0
  16. teddy_executor/adapters/inbound/textual_plan_reviewer_editor.py +281 -0
  17. teddy_executor/adapters/inbound/textual_plan_reviewer_execution.py +213 -0
  18. teddy_executor/adapters/inbound/textual_plan_reviewer_helpers.py +308 -0
  19. teddy_executor/adapters/inbound/textual_plan_reviewer_logic.py +345 -0
  20. teddy_executor/adapters/inbound/textual_plan_reviewer_previews.py +227 -0
  21. teddy_executor/adapters/inbound/textual_plan_reviewer_widgets.py +246 -0
  22. teddy_executor/adapters/outbound/__init__.py +7 -0
  23. teddy_executor/adapters/outbound/console_interactor.py +212 -0
  24. teddy_executor/adapters/outbound/console_interactor_ask_loop.py +121 -0
  25. teddy_executor/adapters/outbound/console_interactor_helpers.py +95 -0
  26. teddy_executor/adapters/outbound/console_tooling.py +62 -0
  27. teddy_executor/adapters/outbound/filesystem_helpers.py +61 -0
  28. teddy_executor/adapters/outbound/litellm_adapter.py +462 -0
  29. teddy_executor/adapters/outbound/local_file_system_adapter.py +300 -0
  30. teddy_executor/adapters/outbound/local_repo_tree_generator.py +96 -0
  31. teddy_executor/adapters/outbound/openrouter_hydrator.py +89 -0
  32. teddy_executor/adapters/outbound/shell_adapter.py +344 -0
  33. teddy_executor/adapters/outbound/shell_command_builder.py +105 -0
  34. teddy_executor/adapters/outbound/system_environment_adapter.py +62 -0
  35. teddy_executor/adapters/outbound/system_environment_inspector.py +54 -0
  36. teddy_executor/adapters/outbound/system_time_adapter.py +22 -0
  37. teddy_executor/adapters/outbound/web_scraper_adapter.py +346 -0
  38. teddy_executor/adapters/outbound/web_searcher_adapter.py +122 -0
  39. teddy_executor/adapters/outbound/yaml_config_adapter.py +105 -0
  40. teddy_executor/container.py +333 -0
  41. teddy_executor/core/__init__.py +0 -0
  42. teddy_executor/core/domain/__init__.py +0 -0
  43. teddy_executor/core/domain/models/__init__.py +44 -0
  44. teddy_executor/core/domain/models/action_ports.py +28 -0
  45. teddy_executor/core/domain/models/change_set.py +10 -0
  46. teddy_executor/core/domain/models/exceptions.py +40 -0
  47. teddy_executor/core/domain/models/execution_report.py +65 -0
  48. teddy_executor/core/domain/models/orchestrator_ports.py +26 -0
  49. teddy_executor/core/domain/models/plan.py +85 -0
  50. teddy_executor/core/domain/models/planning_ports.py +43 -0
  51. teddy_executor/core/domain/models/project_context.py +56 -0
  52. teddy_executor/core/domain/models/report_assembly_data.py +18 -0
  53. teddy_executor/core/domain/models/session.py +17 -0
  54. teddy_executor/core/domain/models/shell_output.py +12 -0
  55. teddy_executor/core/domain/models/web_search_results.py +26 -0
  56. teddy_executor/core/ports/__init__.py +0 -0
  57. teddy_executor/core/ports/inbound/__init__.py +0 -0
  58. teddy_executor/core/ports/inbound/edit_simulator.py +33 -0
  59. teddy_executor/core/ports/inbound/get_context_use_case.py +32 -0
  60. teddy_executor/core/ports/inbound/init.py +15 -0
  61. teddy_executor/core/ports/inbound/plan_parser.py +52 -0
  62. teddy_executor/core/ports/inbound/plan_reviewer.py +44 -0
  63. teddy_executor/core/ports/inbound/plan_validator.py +26 -0
  64. teddy_executor/core/ports/inbound/planning_use_case.py +30 -0
  65. teddy_executor/core/ports/inbound/run_plan_use_case.py +60 -0
  66. teddy_executor/core/ports/outbound/__init__.py +34 -0
  67. teddy_executor/core/ports/outbound/config_service.py +29 -0
  68. teddy_executor/core/ports/outbound/environment_inspector.py +30 -0
  69. teddy_executor/core/ports/outbound/execution_report_assembler.py +19 -0
  70. teddy_executor/core/ports/outbound/file_system_manager.py +131 -0
  71. teddy_executor/core/ports/outbound/llm_client.py +90 -0
  72. teddy_executor/core/ports/outbound/markdown_report_formatter.py +26 -0
  73. teddy_executor/core/ports/outbound/prompt_manager.py +55 -0
  74. teddy_executor/core/ports/outbound/repo_tree_generator.py +17 -0
  75. teddy_executor/core/ports/outbound/session_loop_guard.py +16 -0
  76. teddy_executor/core/ports/outbound/session_manager.py +97 -0
  77. teddy_executor/core/ports/outbound/session_repository.py +65 -0
  78. teddy_executor/core/ports/outbound/shell_executor.py +24 -0
  79. teddy_executor/core/ports/outbound/system_environment.py +25 -0
  80. teddy_executor/core/ports/outbound/time_service.py +28 -0
  81. teddy_executor/core/ports/outbound/user_interactor.py +126 -0
  82. teddy_executor/core/ports/outbound/web_scraper.py +24 -0
  83. teddy_executor/core/ports/outbound/web_searcher.py +25 -0
  84. teddy_executor/core/services/__init__.py +0 -0
  85. teddy_executor/core/services/action_changeset_builder.py +90 -0
  86. teddy_executor/core/services/action_diff_manager.py +110 -0
  87. teddy_executor/core/services/action_dispatcher.py +142 -0
  88. teddy_executor/core/services/action_executor.py +209 -0
  89. teddy_executor/core/services/action_factory.py +197 -0
  90. teddy_executor/core/services/action_parser_complex.py +216 -0
  91. teddy_executor/core/services/action_parser_strategies.py +84 -0
  92. teddy_executor/core/services/context_service.py +437 -0
  93. teddy_executor/core/services/edit_simulator.py +128 -0
  94. teddy_executor/core/services/execution_orchestrator.py +295 -0
  95. teddy_executor/core/services/execution_report_assembler.py +62 -0
  96. teddy_executor/core/services/init_service.py +80 -0
  97. teddy_executor/core/services/markdown_plan_parser.py +309 -0
  98. teddy_executor/core/services/markdown_report_formatter.py +143 -0
  99. teddy_executor/core/services/parser_infrastructure.py +222 -0
  100. teddy_executor/core/services/parser_metadata.py +153 -0
  101. teddy_executor/core/services/parser_reporting.py +267 -0
  102. teddy_executor/core/services/plan_validator.py +82 -0
  103. teddy_executor/core/services/planning_service.py +242 -0
  104. teddy_executor/core/services/prompt_manager.py +146 -0
  105. teddy_executor/core/services/session_lifecycle_manager.py +228 -0
  106. teddy_executor/core/services/session_loop_guard.py +46 -0
  107. teddy_executor/core/services/session_orchestrator.py +538 -0
  108. teddy_executor/core/services/session_planner.py +43 -0
  109. teddy_executor/core/services/session_pruning_service.py +438 -0
  110. teddy_executor/core/services/session_replanner.py +105 -0
  111. teddy_executor/core/services/session_repository.py +194 -0
  112. teddy_executor/core/services/session_service.py +529 -0
  113. teddy_executor/core/services/templates/execution_report.md.j2 +290 -0
  114. teddy_executor/core/services/validation_rules/__init__.py +4 -0
  115. teddy_executor/core/services/validation_rules/edit.py +207 -0
  116. teddy_executor/core/services/validation_rules/edit_matcher.py +247 -0
  117. teddy_executor/core/services/validation_rules/edit_matcher_heuristics.py +84 -0
  118. teddy_executor/core/services/validation_rules/execute.py +37 -0
  119. teddy_executor/core/services/validation_rules/filesystem.py +73 -0
  120. teddy_executor/core/services/validation_rules/helpers.py +178 -0
  121. teddy_executor/core/services/validation_rules/message.py +29 -0
  122. teddy_executor/core/utils/__init__.py +1 -0
  123. teddy_executor/core/utils/diff.py +57 -0
  124. teddy_executor/core/utils/io.py +75 -0
  125. teddy_executor/core/utils/markdown.py +131 -0
  126. teddy_executor/core/utils/serialization.py +39 -0
  127. teddy_executor/core/utils/string.py +351 -0
  128. teddy_executor/prompts.py +45 -0
  129. teddy_executor/registries/__init__.py +1 -0
  130. teddy_executor/registries/infrastructure.py +147 -0
  131. teddy_executor/registries/reviewer.py +57 -0
  132. teddy_executor/registries/validators.py +47 -0
  133. teddy_executor/resources/__init__.py +1 -0
  134. teddy_executor/resources/config/.gitignore +2 -0
  135. teddy_executor/resources/config/__init__.py +1 -0
  136. teddy_executor/resources/config/config.yaml +49 -0
  137. teddy_executor/resources/config/init.context +5 -0
  138. teddy_executor/resources/config/prompts/architect.xml +462 -0
  139. teddy_executor/resources/config/prompts/assistant.xml +336 -0
  140. teddy_executor/resources/config/prompts/debugger.xml +456 -0
  141. teddy_executor/resources/config/prompts/developer.xml +481 -0
  142. teddy_executor/resources/config/prompts/pathfinder.xml +502 -0
  143. teddy_executor/resources/config/prompts/prototyper.xml +425 -0
@@ -0,0 +1,194 @@
1
+ import re
2
+ import yaml
3
+ from pathlib import Path
4
+ from typing import Set, Dict, Any
5
+
6
+ from teddy_executor.core.ports.outbound.file_system_manager import IFileSystemManager
7
+ from teddy_executor.core.ports.outbound.session_repository import ISessionRepository
8
+
9
+
10
+ class SessionRepository(ISessionRepository):
11
+ """
12
+ Handles low-level filesystem lookups and path resolution for TeDDy sessions.
13
+ """
14
+
15
+ def __init__(self, file_system_manager: IFileSystemManager):
16
+ self._file_system_manager = file_system_manager
17
+
18
+ def path_exists(self, path: str) -> bool:
19
+ """Checks if a path exists on the filesystem."""
20
+ return self._file_system_manager.path_exists(path)
21
+
22
+ def list_directory(self, path: str) -> list[str]:
23
+ """Lists the contents of a directory."""
24
+ return self._file_system_manager.list_directory(path)
25
+
26
+ def create_turn_directory(self, turn_dir: str) -> None:
27
+ """Ensures a turn directory exists."""
28
+ self._file_system_manager.create_directory(turn_dir)
29
+
30
+ def _strip_prefix(self, name: str) -> str:
31
+ """Strips the YYYYMMDD_HHMMSS- prefix from a session folder name."""
32
+ return re.sub(r"^\d{8}_\d{6}-", "", name)
33
+
34
+ def get_latest_session_name(self) -> str:
35
+ """Identifies the most recently modified session (returns folder name)."""
36
+ sessions_root = ".teddy/sessions"
37
+ if not self._file_system_manager.path_exists(sessions_root):
38
+ raise ValueError("No sessions found.")
39
+
40
+ sessions = self._file_system_manager.list_directory(sessions_root)
41
+ if not sessions:
42
+ raise ValueError("No sessions found.")
43
+
44
+ session_stats = []
45
+ for name in sessions:
46
+ path = f"{sessions_root}/{name}"
47
+ try:
48
+ mtime = self._file_system_manager.get_mtime(path)
49
+ session_stats.append((name, mtime))
50
+ except (FileNotFoundError, OSError):
51
+ continue
52
+
53
+ if not session_stats:
54
+ raise ValueError("No valid sessions found.")
55
+
56
+ session_stats.sort(key=lambda x: x[1], reverse=True)
57
+ return session_stats[0][0]
58
+
59
+ def resolve_session_from_path(self, path: str) -> str:
60
+ """Climbs the directory tree to find the session (returns folder name)."""
61
+ # Convert to relative path if absolute, relative to CWD
62
+ try:
63
+ p = Path(path).resolve().relative_to(Path.cwd())
64
+ except ValueError:
65
+ p = Path(path)
66
+
67
+ for parent in [p] + list(p.parents):
68
+ if parent.parent.name == "sessions" and ".teddy" in parent.parts:
69
+ return parent.name
70
+
71
+ if self._file_system_manager.path_exists(f".teddy/sessions/{path}"):
72
+ return path
73
+
74
+ raise ValueError(f"Could not resolve session from path: {path}")
75
+
76
+ def is_valid_path(self, path_str: str) -> bool:
77
+ """Heuristic to check if a string is a plausible file path."""
78
+ if (
79
+ not path_str
80
+ or path_str.startswith("#")
81
+ or "**" in path_str
82
+ or ":" in path_str
83
+ and not path_str.startswith(("http:", "https:"))
84
+ ):
85
+ return False
86
+ # Markdown structural elements often start with '-' or '*' followed by bold markers
87
+ if (
88
+ path_str.startswith("- **")
89
+ or path_str.startswith("* **")
90
+ or "Command:" in path_str
91
+ ):
92
+ return False
93
+ return True
94
+
95
+ def read_context_file(self, path: str) -> Set[str]:
96
+ """Reads a context file robustly."""
97
+ try:
98
+ if not self._file_system_manager.path_exists(path):
99
+ return set()
100
+ content = self._file_system_manager.read_file(path)
101
+ lines = {line.strip() for line in content.splitlines() if line.strip()}
102
+ return {line for line in lines if self.is_valid_path(line)}
103
+ except (FileNotFoundError, OSError):
104
+ return set()
105
+
106
+ def to_root_relative(self, turn_dir: Path, filename: str) -> str:
107
+ """Calculates a root-relative path for a file within a turn directory."""
108
+ file_path = turn_dir.joinpath(filename)
109
+ path_parts = list(file_path.parts)
110
+
111
+ if ".teddy" in path_parts:
112
+ idx = path_parts.index(".teddy")
113
+ return "/".join(path_parts[idx:])
114
+
115
+ if "sessions" in path_parts:
116
+ idx = path_parts.index("sessions")
117
+ return ".teddy/" + "/".join(path_parts[idx:])
118
+
119
+ return f"{turn_dir.name}/{filename}"
120
+
121
+ def load_meta(self, turn_dir: str) -> Dict[str, Any]:
122
+ """Loads and parses meta.yaml for a turn."""
123
+ content = self._file_system_manager.read_file(f"{turn_dir}/meta.yaml")
124
+ meta = yaml.safe_load(str(content))
125
+ return meta if isinstance(meta, dict) else {}
126
+
127
+ def save_meta(self, path: str, data: Dict[str, Any]) -> None:
128
+ """Serializes and persists metadata to the filesystem."""
129
+ from teddy_executor.core.utils.serialization import scrub_dict_for_serialization
130
+
131
+ serializable = scrub_dict_for_serialization(data)
132
+ self._file_system_manager.write_file(path, yaml.dump(serializable))
133
+
134
+ def copy_prompt(self, src_dir: str, dest_dir: str, agent: str) -> None:
135
+ """Copies an agent prompt file between turn directories."""
136
+ if not self._file_system_manager.path_exists(src_dir):
137
+ return
138
+ for f in self._file_system_manager.list_directory(src_dir):
139
+ if Path(f).stem == agent:
140
+ src_path = f"{src_dir}/{f}"
141
+ if self._file_system_manager.path_exists(src_path):
142
+ content = self._file_system_manager.read_file(src_path)
143
+ self._file_system_manager.write_file(f"{dest_dir}/{f}", content)
144
+ return
145
+
146
+ def get_latest_turn(self, session_name: str) -> str:
147
+ """Identifies and returns the path to the latest turn in a session."""
148
+ session_root = f".teddy/sessions/{session_name}"
149
+ try:
150
+ items = self._file_system_manager.list_directory(session_root)
151
+ except FileNotFoundError:
152
+ raise ValueError(f"Session '{session_name}' not found.")
153
+
154
+ # Filter for zero-padded numeric directories (e.g., '01', '02')
155
+ turns = [item for item in items if item.isdigit()]
156
+ if not turns:
157
+ raise ValueError(f"No turns found in session '{session_name}'.")
158
+
159
+ latest_turn_id = sorted(turns)[-1]
160
+ return f"{session_root}/{latest_turn_id}"
161
+
162
+ def rename_session(self, old_name: str, new_name: str) -> str:
163
+ """Renames a session directory on the filesystem."""
164
+ # Preserve date prefix if present
165
+ prefix_match = re.match(r"^(\d{8}_\d{6}-)", old_name)
166
+ prefix = prefix_match.group(1) if prefix_match else ""
167
+
168
+ # Ensure new name doesn't double-prefix
169
+ clean_new_name = re.sub(r"^\d{8}_\d{6}-", "", new_name)
170
+
171
+ old_path = f".teddy/sessions/{old_name}"
172
+ new_path = f".teddy/sessions/{prefix}{clean_new_name}"
173
+
174
+ if not self._file_system_manager.path_exists(old_path):
175
+ raise ValueError(f"Session '{old_name}' not found.")
176
+ if self._file_system_manager.path_exists(new_path):
177
+ raise ValueError(f"Session '{new_name}' already exists.")
178
+
179
+ self._file_system_manager.move_directory(old_path, new_path)
180
+ return new_path
181
+
182
+ def resolve_context_paths(self, plan_path: str) -> Dict[str, list[str]]:
183
+ """Locates and returns the contents of session and turn context files."""
184
+ plan_p = Path(plan_path)
185
+ turn_dir = plan_p.parent
186
+ session_dir = turn_dir.parent
187
+
188
+ session_context_path = (session_dir / "session.context").as_posix()
189
+ turn_context_path = (turn_dir / "turn.context").as_posix()
190
+
191
+ return {
192
+ "Session": sorted(list(self.read_context_file(session_context_path))),
193
+ "Turn": sorted(list(self.read_context_file(turn_context_path))),
194
+ }