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.
- teddy_cli-0.1.0.dist-info/LICENSE +677 -0
- teddy_cli-0.1.0.dist-info/METADATA +33 -0
- teddy_cli-0.1.0.dist-info/RECORD +143 -0
- teddy_cli-0.1.0.dist-info/WHEEL +4 -0
- teddy_cli-0.1.0.dist-info/entry_points.txt +3 -0
- teddy_executor/__init__.py +1 -0
- teddy_executor/__main__.py +335 -0
- teddy_executor/adapters/__init__.py +0 -0
- teddy_executor/adapters/inbound/__init__.py +0 -0
- teddy_executor/adapters/inbound/cli_formatter.py +107 -0
- teddy_executor/adapters/inbound/cli_helpers.py +249 -0
- teddy_executor/adapters/inbound/console_plan_reviewer.py +69 -0
- teddy_executor/adapters/inbound/session_cli_handlers.py +366 -0
- teddy_executor/adapters/inbound/textual_plan_reviewer.py +78 -0
- teddy_executor/adapters/inbound/textual_plan_reviewer_app.py +367 -0
- teddy_executor/adapters/inbound/textual_plan_reviewer_editor.py +281 -0
- teddy_executor/adapters/inbound/textual_plan_reviewer_execution.py +213 -0
- teddy_executor/adapters/inbound/textual_plan_reviewer_helpers.py +308 -0
- teddy_executor/adapters/inbound/textual_plan_reviewer_logic.py +345 -0
- teddy_executor/adapters/inbound/textual_plan_reviewer_previews.py +227 -0
- teddy_executor/adapters/inbound/textual_plan_reviewer_widgets.py +246 -0
- teddy_executor/adapters/outbound/__init__.py +7 -0
- teddy_executor/adapters/outbound/console_interactor.py +212 -0
- teddy_executor/adapters/outbound/console_interactor_ask_loop.py +121 -0
- teddy_executor/adapters/outbound/console_interactor_helpers.py +95 -0
- teddy_executor/adapters/outbound/console_tooling.py +62 -0
- teddy_executor/adapters/outbound/filesystem_helpers.py +61 -0
- teddy_executor/adapters/outbound/litellm_adapter.py +462 -0
- teddy_executor/adapters/outbound/local_file_system_adapter.py +300 -0
- teddy_executor/adapters/outbound/local_repo_tree_generator.py +96 -0
- teddy_executor/adapters/outbound/openrouter_hydrator.py +89 -0
- teddy_executor/adapters/outbound/shell_adapter.py +344 -0
- teddy_executor/adapters/outbound/shell_command_builder.py +105 -0
- teddy_executor/adapters/outbound/system_environment_adapter.py +62 -0
- teddy_executor/adapters/outbound/system_environment_inspector.py +54 -0
- teddy_executor/adapters/outbound/system_time_adapter.py +22 -0
- teddy_executor/adapters/outbound/web_scraper_adapter.py +346 -0
- teddy_executor/adapters/outbound/web_searcher_adapter.py +122 -0
- teddy_executor/adapters/outbound/yaml_config_adapter.py +105 -0
- teddy_executor/container.py +333 -0
- teddy_executor/core/__init__.py +0 -0
- teddy_executor/core/domain/__init__.py +0 -0
- teddy_executor/core/domain/models/__init__.py +44 -0
- teddy_executor/core/domain/models/action_ports.py +28 -0
- teddy_executor/core/domain/models/change_set.py +10 -0
- teddy_executor/core/domain/models/exceptions.py +40 -0
- teddy_executor/core/domain/models/execution_report.py +65 -0
- teddy_executor/core/domain/models/orchestrator_ports.py +26 -0
- teddy_executor/core/domain/models/plan.py +85 -0
- teddy_executor/core/domain/models/planning_ports.py +43 -0
- teddy_executor/core/domain/models/project_context.py +56 -0
- teddy_executor/core/domain/models/report_assembly_data.py +18 -0
- teddy_executor/core/domain/models/session.py +17 -0
- teddy_executor/core/domain/models/shell_output.py +12 -0
- teddy_executor/core/domain/models/web_search_results.py +26 -0
- teddy_executor/core/ports/__init__.py +0 -0
- teddy_executor/core/ports/inbound/__init__.py +0 -0
- teddy_executor/core/ports/inbound/edit_simulator.py +33 -0
- teddy_executor/core/ports/inbound/get_context_use_case.py +32 -0
- teddy_executor/core/ports/inbound/init.py +15 -0
- teddy_executor/core/ports/inbound/plan_parser.py +52 -0
- teddy_executor/core/ports/inbound/plan_reviewer.py +44 -0
- teddy_executor/core/ports/inbound/plan_validator.py +26 -0
- teddy_executor/core/ports/inbound/planning_use_case.py +30 -0
- teddy_executor/core/ports/inbound/run_plan_use_case.py +60 -0
- teddy_executor/core/ports/outbound/__init__.py +34 -0
- teddy_executor/core/ports/outbound/config_service.py +29 -0
- teddy_executor/core/ports/outbound/environment_inspector.py +30 -0
- teddy_executor/core/ports/outbound/execution_report_assembler.py +19 -0
- teddy_executor/core/ports/outbound/file_system_manager.py +131 -0
- teddy_executor/core/ports/outbound/llm_client.py +90 -0
- teddy_executor/core/ports/outbound/markdown_report_formatter.py +26 -0
- teddy_executor/core/ports/outbound/prompt_manager.py +55 -0
- teddy_executor/core/ports/outbound/repo_tree_generator.py +17 -0
- teddy_executor/core/ports/outbound/session_loop_guard.py +16 -0
- teddy_executor/core/ports/outbound/session_manager.py +97 -0
- teddy_executor/core/ports/outbound/session_repository.py +65 -0
- teddy_executor/core/ports/outbound/shell_executor.py +24 -0
- teddy_executor/core/ports/outbound/system_environment.py +25 -0
- teddy_executor/core/ports/outbound/time_service.py +28 -0
- teddy_executor/core/ports/outbound/user_interactor.py +126 -0
- teddy_executor/core/ports/outbound/web_scraper.py +24 -0
- teddy_executor/core/ports/outbound/web_searcher.py +25 -0
- teddy_executor/core/services/__init__.py +0 -0
- teddy_executor/core/services/action_changeset_builder.py +90 -0
- teddy_executor/core/services/action_diff_manager.py +110 -0
- teddy_executor/core/services/action_dispatcher.py +142 -0
- teddy_executor/core/services/action_executor.py +209 -0
- teddy_executor/core/services/action_factory.py +197 -0
- teddy_executor/core/services/action_parser_complex.py +216 -0
- teddy_executor/core/services/action_parser_strategies.py +84 -0
- teddy_executor/core/services/context_service.py +437 -0
- teddy_executor/core/services/edit_simulator.py +128 -0
- teddy_executor/core/services/execution_orchestrator.py +295 -0
- teddy_executor/core/services/execution_report_assembler.py +62 -0
- teddy_executor/core/services/init_service.py +80 -0
- teddy_executor/core/services/markdown_plan_parser.py +309 -0
- teddy_executor/core/services/markdown_report_formatter.py +143 -0
- teddy_executor/core/services/parser_infrastructure.py +222 -0
- teddy_executor/core/services/parser_metadata.py +153 -0
- teddy_executor/core/services/parser_reporting.py +267 -0
- teddy_executor/core/services/plan_validator.py +82 -0
- teddy_executor/core/services/planning_service.py +242 -0
- teddy_executor/core/services/prompt_manager.py +146 -0
- teddy_executor/core/services/session_lifecycle_manager.py +228 -0
- teddy_executor/core/services/session_loop_guard.py +46 -0
- teddy_executor/core/services/session_orchestrator.py +538 -0
- teddy_executor/core/services/session_planner.py +43 -0
- teddy_executor/core/services/session_pruning_service.py +438 -0
- teddy_executor/core/services/session_replanner.py +105 -0
- teddy_executor/core/services/session_repository.py +194 -0
- teddy_executor/core/services/session_service.py +529 -0
- teddy_executor/core/services/templates/execution_report.md.j2 +290 -0
- teddy_executor/core/services/validation_rules/__init__.py +4 -0
- teddy_executor/core/services/validation_rules/edit.py +207 -0
- teddy_executor/core/services/validation_rules/edit_matcher.py +247 -0
- teddy_executor/core/services/validation_rules/edit_matcher_heuristics.py +84 -0
- teddy_executor/core/services/validation_rules/execute.py +37 -0
- teddy_executor/core/services/validation_rules/filesystem.py +73 -0
- teddy_executor/core/services/validation_rules/helpers.py +178 -0
- teddy_executor/core/services/validation_rules/message.py +29 -0
- teddy_executor/core/utils/__init__.py +1 -0
- teddy_executor/core/utils/diff.py +57 -0
- teddy_executor/core/utils/io.py +75 -0
- teddy_executor/core/utils/markdown.py +131 -0
- teddy_executor/core/utils/serialization.py +39 -0
- teddy_executor/core/utils/string.py +351 -0
- teddy_executor/prompts.py +45 -0
- teddy_executor/registries/__init__.py +1 -0
- teddy_executor/registries/infrastructure.py +147 -0
- teddy_executor/registries/reviewer.py +57 -0
- teddy_executor/registries/validators.py +47 -0
- teddy_executor/resources/__init__.py +1 -0
- teddy_executor/resources/config/.gitignore +2 -0
- teddy_executor/resources/config/__init__.py +1 -0
- teddy_executor/resources/config/config.yaml +49 -0
- teddy_executor/resources/config/init.context +5 -0
- teddy_executor/resources/config/prompts/architect.xml +462 -0
- teddy_executor/resources/config/prompts/assistant.xml +336 -0
- teddy_executor/resources/config/prompts/debugger.xml +456 -0
- teddy_executor/resources/config/prompts/developer.xml +481 -0
- teddy_executor/resources/config/prompts/pathfinder.xml +502 -0
- 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
|
+
}
|