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,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
+ ...