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,56 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from typing import Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from dataclasses import field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class ContextItem:
|
|
12
|
+
"""
|
|
13
|
+
Metadata for a single context file.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
path: The relative file path.
|
|
17
|
+
token_count: Estimated token size.
|
|
18
|
+
git_status: 2-char git status code.
|
|
19
|
+
scope: Source scope (System/Session/Turn).
|
|
20
|
+
selected: Whether the file is selected for the next turn.
|
|
21
|
+
auto_prune_reason: Reason for pre-deselection, if any.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
path: str
|
|
25
|
+
token_count: int
|
|
26
|
+
git_status: str
|
|
27
|
+
scope: str
|
|
28
|
+
selected: bool = True
|
|
29
|
+
auto_prune_reason: Optional[str] = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class ProjectContext:
|
|
34
|
+
"""
|
|
35
|
+
A strictly-typed DTO representing the aggregated project context for display.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
header: A string containing metadata about the context (e.g., CWD, OS).
|
|
39
|
+
content: The main body of the context (e.g., file tree and contents).
|
|
40
|
+
scoped_paths: A mapping of scope names (e.g., 'Turn', 'Session') to lists of file paths.
|
|
41
|
+
git_status: An optional string containing the output of 'git status -s'.
|
|
42
|
+
items: Structured list of context files and metadata.
|
|
43
|
+
agent_name: Name of the active agent.
|
|
44
|
+
system_prompt_tokens: Token count of the system prompt.
|
|
45
|
+
total_window: Total context window for the model.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
header: str
|
|
49
|
+
content: str
|
|
50
|
+
scoped_paths: Dict[str, List[str]] = field(default_factory=dict)
|
|
51
|
+
git_status: Optional[str] = None
|
|
52
|
+
items: List[ContextItem] = field(default_factory=list)
|
|
53
|
+
agent_name: str = "Unknown"
|
|
54
|
+
system_prompt_tokens: int = 0
|
|
55
|
+
content_tokens: int = 0
|
|
56
|
+
total_window: int = 0
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Optional, Sequence
|
|
4
|
+
|
|
5
|
+
from teddy_executor.core.domain.models import ActionLog, Plan
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class ReportAssemblyData:
|
|
10
|
+
"""
|
|
11
|
+
DTO grouping parameters for ExecutionReport assembly.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
plan: Plan
|
|
15
|
+
action_logs: Sequence[ActionLog]
|
|
16
|
+
start_time: datetime
|
|
17
|
+
message: Optional[str] = None
|
|
18
|
+
is_session: bool = False
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass(frozen=True)
|
|
6
|
+
class SessionOptions:
|
|
7
|
+
"""
|
|
8
|
+
Options for initializing a new session.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
name: str
|
|
12
|
+
agent_name: str
|
|
13
|
+
initial_request: Optional[str] = None
|
|
14
|
+
additional_context: list[str] = field(default_factory=list)
|
|
15
|
+
model: Optional[str] = None
|
|
16
|
+
provider: Optional[str] = None
|
|
17
|
+
api_key: Optional[str] = None
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from typing import TypedDict, NotRequired
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ShellOutput(TypedDict):
|
|
5
|
+
"""
|
|
6
|
+
A strictly-typed dictionary representing the result of a shell command execution.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
stdout: str
|
|
10
|
+
stderr: str
|
|
11
|
+
return_code: int
|
|
12
|
+
failed_command: NotRequired[str]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the strictly-typed data transfer object for web search results.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List, TypedDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SearchResult(TypedDict):
|
|
9
|
+
"""Represents a single search result item."""
|
|
10
|
+
|
|
11
|
+
title: str
|
|
12
|
+
href: str
|
|
13
|
+
description: str # The SERP snippet
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class QueryResult(TypedDict):
|
|
17
|
+
"""Represents the results for a single search query."""
|
|
18
|
+
|
|
19
|
+
query: str
|
|
20
|
+
results: List[SearchResult]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class WebSearchResults(TypedDict):
|
|
24
|
+
"""Represents the aggregated results from one or more web search queries."""
|
|
25
|
+
|
|
26
|
+
query_results: List[QueryResult]
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import List, Protocol, TypedDict
|
|
2
|
+
from teddy_executor.core.domain.models.plan import DEFAULT_SIMILARITY_THRESHOLD
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class EditPair(TypedDict, total=False):
|
|
6
|
+
find: str
|
|
7
|
+
replace: str
|
|
8
|
+
match_all: bool
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class IEditSimulator(Protocol):
|
|
12
|
+
"""
|
|
13
|
+
Service for applying a sequence of FIND/REPLACE edits to a string.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def simulate_edits(
|
|
17
|
+
self,
|
|
18
|
+
content: str,
|
|
19
|
+
edits: List[EditPair],
|
|
20
|
+
threshold: float = DEFAULT_SIMILARITY_THRESHOLD,
|
|
21
|
+
match_all: bool = False,
|
|
22
|
+
) -> tuple[str, list[float]]:
|
|
23
|
+
"""
|
|
24
|
+
Applies each FIND/REPLACE pair in sequence to the provided content.
|
|
25
|
+
|
|
26
|
+
:param content: The original string content.
|
|
27
|
+
:param edits: A list of EditPair dictionaries.
|
|
28
|
+
:param threshold: The similarity threshold for fuzzy matching.
|
|
29
|
+
:param match_all: Global override for bulk replacement.
|
|
30
|
+
:return: A tuple of (transformed_string, list_of_similarity_scores).
|
|
31
|
+
:raises ValueError: If a FIND block is not found or is ambiguous (matches multiple times).
|
|
32
|
+
"""
|
|
33
|
+
...
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from typing import Dict, Optional, Protocol, Sequence
|
|
2
|
+
from teddy_executor.core.domain.models import ProjectContext
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class IGetContextUseCase(Protocol):
|
|
6
|
+
"""
|
|
7
|
+
Inbound Port for the Get Context use case.
|
|
8
|
+
This defines the contract for the primary entry point for gathering project context.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def get_context(
|
|
12
|
+
self,
|
|
13
|
+
context_files: Optional[Dict[str, Sequence[str]]] = None,
|
|
14
|
+
include_tokens: bool = True,
|
|
15
|
+
agent_name: str = "Unknown",
|
|
16
|
+
total_window: int = 0,
|
|
17
|
+
cache_dir: Optional[str] = None,
|
|
18
|
+
current_turn: Optional[str] = None,
|
|
19
|
+
system_prompt_tokens: int = 0,
|
|
20
|
+
) -> ProjectContext:
|
|
21
|
+
"""
|
|
22
|
+
Gathers all project context information.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
context_files: Optional mapping of scope names to .context files.
|
|
26
|
+
current_turn: Optional 2-digit turn number to include in the header.
|
|
27
|
+
system_prompt_tokens: Token count of the system prompt (pre-computed).
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
ProjectContext: A data object containing the aggregated context.
|
|
31
|
+
"""
|
|
32
|
+
...
|
|
@@ -0,0 +1,15 @@
|
|
|
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) -> None:
|
|
11
|
+
"""
|
|
12
|
+
Ensures the .teddy/ directory and its essential configuration
|
|
13
|
+
files are present in the current project root.
|
|
14
|
+
"""
|
|
15
|
+
pass
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
from teddy_executor.core.domain.models import Plan
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from typing import Any, Optional, List
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InvalidPlanError(Exception):
|
|
10
|
+
"""Raised when the plan is malformed."""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
message: str,
|
|
15
|
+
offending_node: Optional[Any] = None,
|
|
16
|
+
offending_nodes: Optional[List[Any]] = None,
|
|
17
|
+
validation_errors: Optional[List[Any]] = None,
|
|
18
|
+
):
|
|
19
|
+
super().__init__(message)
|
|
20
|
+
self.offending_nodes = offending_nodes or []
|
|
21
|
+
self.validation_errors = validation_errors or []
|
|
22
|
+
if offending_node:
|
|
23
|
+
self.offending_nodes.append(offending_node)
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def offending_node(self) -> Optional[Any]:
|
|
27
|
+
"""Backward compatibility for single offending node."""
|
|
28
|
+
return self.offending_nodes[0] if self.offending_nodes else None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class IPlanParser(ABC):
|
|
32
|
+
"""
|
|
33
|
+
Defines the inbound port for parsing a plan from a raw string into a Plan object.
|
|
34
|
+
This is the contract that all plan parser implementations must adhere to.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def parse(self, plan_content: str, plan_path: Optional[str] = None) -> Plan:
|
|
39
|
+
"""
|
|
40
|
+
Reads and parses the specified plan string into a structured Plan object.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
plan_content: The raw string content of the plan.
|
|
44
|
+
plan_path: The optional path to the plan file for context detection.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
A Plan domain object.
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
InvalidPlanError: If the plan content is malformed or invalid.
|
|
51
|
+
"""
|
|
52
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import Protocol, Optional, runtime_checkable, TYPE_CHECKING
|
|
2
|
+
from teddy_executor.core.domain.models.plan import Plan
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from teddy_executor.core.domain.models.plan import ActionData
|
|
6
|
+
from teddy_executor.core.domain.models.project_context import (
|
|
7
|
+
ProjectContext,
|
|
8
|
+
) # vulture: ignore
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@runtime_checkable
|
|
12
|
+
class IPlanReviewer(Protocol):
|
|
13
|
+
"""
|
|
14
|
+
Inbound port for the interactive review and modification of a Plan.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def review(
|
|
18
|
+
self, plan: Plan, project_context: Optional["ProjectContext"] = None
|
|
19
|
+
) -> Optional[Plan]:
|
|
20
|
+
"""
|
|
21
|
+
Initiates the interactive review process.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
plan: The plan to review.
|
|
25
|
+
project_context: Optional context metadata for display and auto-pruning.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
The modified Plan object, or None if the user cancels.
|
|
29
|
+
"""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
def review_action(
|
|
33
|
+
self,
|
|
34
|
+
action: "ActionData",
|
|
35
|
+
total_actions: int,
|
|
36
|
+
agent_name: Optional[str] = None,
|
|
37
|
+
) -> tuple[bool, str]:
|
|
38
|
+
"""
|
|
39
|
+
Initiates a sequential interactive review for a single action.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
A tuple of (should_execute, captured_message).
|
|
43
|
+
"""
|
|
44
|
+
...
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the inbound port for plan validation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Placeholder content
|
|
6
|
+
# The Developer will implement this based on the design document.
|
|
7
|
+
# See: docs/core/ports/inbound/plan_validator.md
|
|
8
|
+
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from typing import Dict, Optional, Sequence
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class IPlanValidator(ABC):
|
|
14
|
+
"""
|
|
15
|
+
Defines the contract for any service that performs pre-flight validation of a Plan.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def validate(
|
|
20
|
+
self, plan, context_paths: Optional[Dict[str, Sequence[str]]] = None
|
|
21
|
+
) -> list:
|
|
22
|
+
"""
|
|
23
|
+
Validates the plan and returns a list of validation errors.
|
|
24
|
+
An empty list signifies a successful validation.
|
|
25
|
+
"""
|
|
26
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Dict, Optional, Sequence
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class IPlanningUseCase(ABC):
|
|
6
|
+
"""
|
|
7
|
+
Defines the contract for generating an AI plan.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
@abstractmethod
|
|
11
|
+
def generate_plan(
|
|
12
|
+
self,
|
|
13
|
+
user_message: Optional[str],
|
|
14
|
+
turn_dir: str,
|
|
15
|
+
context_files: Optional[Dict[str, Sequence[str]]] = None,
|
|
16
|
+
) -> tuple[str, float]:
|
|
17
|
+
"""
|
|
18
|
+
Generates a new plan.md file based on context and user message.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
user_message: The instructions from the user.
|
|
22
|
+
turn_dir: The directory where artifacts for the turn are stored.
|
|
23
|
+
context_files: Optional scoped context files to include. If None,
|
|
24
|
+
implementations should auto-resolve session/turn manifests
|
|
25
|
+
from turn_dir.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
The path to the generated plan.md.
|
|
29
|
+
"""
|
|
30
|
+
pass
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Optional, TYPE_CHECKING
|
|
3
|
+
from teddy_executor.core.domain.models import ExecutionReport, Plan
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from teddy_executor.core.domain.models.project_context import ProjectContext
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class IRunPlanUseCase(ABC):
|
|
10
|
+
"""
|
|
11
|
+
Defines the contract for running a teddy execution plan.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def execute( # noqa: PLR0913
|
|
16
|
+
self,
|
|
17
|
+
plan: Optional[Plan] = None,
|
|
18
|
+
plan_content: Optional[str] = None,
|
|
19
|
+
plan_path: Optional[str] = None,
|
|
20
|
+
interactive: bool = True,
|
|
21
|
+
message: Optional[str] = None,
|
|
22
|
+
project_context: Optional["ProjectContext"] = None,
|
|
23
|
+
) -> ExecutionReport:
|
|
24
|
+
"""
|
|
25
|
+
Executes a plan and returns a report.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
plan: An already parsed Plan object.
|
|
29
|
+
plan_content: Raw Markdown content of a plan.
|
|
30
|
+
plan_path: Path to a plan file on disk.
|
|
31
|
+
interactive: A flag to enable/disable step-by-step user approval.
|
|
32
|
+
message: Optional user instruction to include in the report.
|
|
33
|
+
project_context: Optional context metadata for display and auto-pruning.
|
|
34
|
+
"""
|
|
35
|
+
_ = (plan, plan_content, plan_path, interactive, message, project_context)
|
|
36
|
+
raise NotImplementedError
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def resume(
|
|
40
|
+
self,
|
|
41
|
+
session_name: str,
|
|
42
|
+
interactive: bool = True,
|
|
43
|
+
project_context: Optional["ProjectContext"] = None,
|
|
44
|
+
) -> tuple[str, Optional[ExecutionReport]]:
|
|
45
|
+
"""
|
|
46
|
+
Intelligently resumes the session based on its state.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
A tuple (actual_session_name, report). The actual_session_name
|
|
50
|
+
may differ from the input session_name after a centennial
|
|
51
|
+
migration (when the session transitions to a continuation name
|
|
52
|
+
like 'my-session-2').
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
session_name: The name of the session to resume.
|
|
56
|
+
interactive: Whether to run in interactive mode.
|
|
57
|
+
project_context: Optional context for the run.
|
|
58
|
+
"""
|
|
59
|
+
_ = (session_name, interactive, project_context)
|
|
60
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from .config_service import IConfigService
|
|
2
|
+
from .environment_inspector import IEnvironmentInspector
|
|
3
|
+
from .file_system_manager import IFileSystemManager
|
|
4
|
+
from .llm_client import ILlmClient, LlmApiError
|
|
5
|
+
from .markdown_report_formatter import IMarkdownReportFormatter
|
|
6
|
+
from .prompt_manager import IPromptManager
|
|
7
|
+
from .repo_tree_generator import IRepoTreeGenerator
|
|
8
|
+
from .session_loop_guard import ISessionLoopGuard
|
|
9
|
+
from .session_manager import ISessionManager
|
|
10
|
+
from .shell_executor import IShellExecutor
|
|
11
|
+
from .system_environment import ISystemEnvironment
|
|
12
|
+
from .time_service import ITimeService
|
|
13
|
+
from .user_interactor import IUserInteractor
|
|
14
|
+
from .web_scraper import WebScraper as IWebScraper
|
|
15
|
+
from .web_searcher import IWebSearcher
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"IConfigService",
|
|
19
|
+
"IEnvironmentInspector",
|
|
20
|
+
"IFileSystemManager",
|
|
21
|
+
"ILlmClient",
|
|
22
|
+
"IMarkdownReportFormatter",
|
|
23
|
+
"IPromptManager",
|
|
24
|
+
"IRepoTreeGenerator",
|
|
25
|
+
"ISessionLoopGuard",
|
|
26
|
+
"ISessionManager",
|
|
27
|
+
"IShellExecutor",
|
|
28
|
+
"ISystemEnvironment",
|
|
29
|
+
"ITimeService",
|
|
30
|
+
"IUserInteractor",
|
|
31
|
+
"IWebScraper",
|
|
32
|
+
"IWebSearcher",
|
|
33
|
+
"LlmApiError",
|
|
34
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class IConfigService(ABC):
|
|
6
|
+
"""
|
|
7
|
+
Interface for retrieving application configuration and secrets.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
@abstractmethod
|
|
11
|
+
def get_config_path(self) -> str:
|
|
12
|
+
"""
|
|
13
|
+
Returns the absolute or relative path to the configuration file.
|
|
14
|
+
"""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def get_setting(self, key: str, default: Optional[Any] = None) -> Optional[Any]:
|
|
19
|
+
"""
|
|
20
|
+
Retrieves a configuration value by its key.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
key: The configuration key to retrieve.
|
|
24
|
+
default: The default value to return if the key is not found.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
The configuration value, or the default value if not found.
|
|
28
|
+
"""
|
|
29
|
+
pass
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from typing import Protocol
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class IEnvironmentInspector(Protocol):
|
|
8
|
+
"""
|
|
9
|
+
Outbound Port for inspecting the user's environment (OS, terminal, etc.).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def get_environment_info(self) -> dict[str, str]:
|
|
13
|
+
"""
|
|
14
|
+
Gathers key information about the system environment.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
dict[str, str]: A dictionary of environment properties.
|
|
18
|
+
Keys include: os_name, os_version, python_version, cwd, shell,
|
|
19
|
+
current_date (YYYY-MM-DD), current_time (HH:MM:SS).
|
|
20
|
+
"""
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
def get_git_status(self) -> Optional[str]:
|
|
24
|
+
"""
|
|
25
|
+
Gathers the current Git status of the working directory.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Optional[str]: The output of 'git status -s' or None if not a git repo.
|
|
29
|
+
"""
|
|
30
|
+
...
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
from teddy_executor.core.domain.models import ExecutionReport, ReportAssemblyData
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IExecutionReportAssembler(ABC):
|
|
7
|
+
"""
|
|
8
|
+
Defines the outbound port for assembling an ExecutionReport and determining run status.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def assemble(
|
|
13
|
+
self,
|
|
14
|
+
data: ReportAssemblyData,
|
|
15
|
+
) -> ExecutionReport:
|
|
16
|
+
"""
|
|
17
|
+
Calculates the final run status and constructs a complete ExecutionReport.
|
|
18
|
+
"""
|
|
19
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from typing import Protocol, Sequence, TextIO
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class IFileSystemManager(Protocol):
|
|
5
|
+
"""
|
|
6
|
+
An outbound port for interacting with a file system.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def path_exists(self, path: str) -> bool:
|
|
10
|
+
"""
|
|
11
|
+
Checks if a path (file or directory) exists.
|
|
12
|
+
"""
|
|
13
|
+
...
|
|
14
|
+
|
|
15
|
+
def is_dir(self, path: str) -> bool:
|
|
16
|
+
"""
|
|
17
|
+
Returns True if the path exists and is a directory.
|
|
18
|
+
"""
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
def list_directory_recursive(self, path: str) -> list[str]:
|
|
22
|
+
"""
|
|
23
|
+
Lists all files in a directory and its subdirectories, respecting ignores.
|
|
24
|
+
"""
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
def create_directory(self, path: str) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Creates a directory, including any necessary parent directories.
|
|
30
|
+
"""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
def write_file(self, path: str, content: str) -> None:
|
|
34
|
+
"""
|
|
35
|
+
Writes content to a file, creating it if it doesn't exist
|
|
36
|
+
and overwriting it if it does.
|
|
37
|
+
"""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
def create_file(self, path: str, content: str, overwrite: bool = False) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Creates a new file with the given content.
|
|
43
|
+
|
|
44
|
+
If overwrite is False (default), raises FileExistsError if the file already exists.
|
|
45
|
+
If overwrite is True, replaces existing file content.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
FileExistsError: If a file already exists and overwrite is False.
|
|
49
|
+
"""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
def read_file(self, path: str) -> str:
|
|
53
|
+
"""
|
|
54
|
+
Reads the content of a file from the specified path.
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
FileNotFoundError: If no file exists at the specified path.
|
|
58
|
+
"""
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
def read_raw_file(self, path: str) -> str:
|
|
62
|
+
"""
|
|
63
|
+
Reads the full, untruncated content of a file from the specified path.
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
FileNotFoundError: If no file exists at the specified path.
|
|
67
|
+
"""
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
def edit_file(
|
|
71
|
+
self,
|
|
72
|
+
path: str,
|
|
73
|
+
edits: list[dict[str, str]],
|
|
74
|
+
similarity_threshold: float = 0.95,
|
|
75
|
+
match_all: bool = False,
|
|
76
|
+
) -> list[float]:
|
|
77
|
+
"""
|
|
78
|
+
Modifies an existing file by applying a list of find-and-replace blocks.
|
|
79
|
+
|
|
80
|
+
Raises:
|
|
81
|
+
FileNotFoundError: If no file exists at the specified path.
|
|
82
|
+
"""
|
|
83
|
+
...
|
|
84
|
+
|
|
85
|
+
def get_context_paths(self) -> list[str]:
|
|
86
|
+
"""
|
|
87
|
+
Reads all .teddy/*.context files and returns a deduplicated list of paths.
|
|
88
|
+
"""
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
def resolve_paths_from_files(self, file_paths: Sequence[str]) -> list[str]:
|
|
92
|
+
"""
|
|
93
|
+
Reads a list of context files and returns a deduplicated list of the paths they contain.
|
|
94
|
+
"""
|
|
95
|
+
...
|
|
96
|
+
|
|
97
|
+
def read_files_in_vault(self, paths: list[str]) -> dict[str, str | None]:
|
|
98
|
+
"""
|
|
99
|
+
Reads the content of multiple files. Returns content for found files
|
|
100
|
+
and None for files that are not found.
|
|
101
|
+
"""
|
|
102
|
+
...
|
|
103
|
+
|
|
104
|
+
def list_directory(self, path: str) -> list[str]:
|
|
105
|
+
"""
|
|
106
|
+
Lists the names of files and directories in the specified path.
|
|
107
|
+
"""
|
|
108
|
+
...
|
|
109
|
+
|
|
110
|
+
def get_mtime(self, path: str) -> float:
|
|
111
|
+
"""
|
|
112
|
+
Returns the modification time of a file or directory as a timestamp.
|
|
113
|
+
"""
|
|
114
|
+
...
|
|
115
|
+
|
|
116
|
+
def move_directory(self, old_path: str, new_path: str) -> None:
|
|
117
|
+
"""
|
|
118
|
+
Moves or renames a directory.
|
|
119
|
+
"""
|
|
120
|
+
_ = old_path
|
|
121
|
+
...
|
|
122
|
+
|
|
123
|
+
def open_file_for_append(self, path: str) -> TextIO:
|
|
124
|
+
"""
|
|
125
|
+
Opens a file for appending, creating parent directories if needed.
|
|
126
|
+
Returns a TextIO file-like object for writing.
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
IOError: If the file cannot be opened.
|
|
130
|
+
"""
|
|
131
|
+
...
|