Coverage for little_loops / config / automation.py: 90%
67 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-03-18 16:20 -0500
« prev ^ index » next coverage.py v7.12.0, created at 2026-03-18 16:20 -0500
1"""Automation and execution configuration dataclasses.
3Covers automation scripts, parallel execution, confidence gates,
4command behavior, and dependency analysis configuration.
5"""
7from __future__ import annotations
9from dataclasses import dataclass, field
10from typing import Any
13@dataclass
14class AutomationConfig:
15 """Automation script configuration."""
17 timeout_seconds: int = 3600
18 idle_timeout_seconds: int = 0 # Kill if no output for N seconds (0 to disable)
19 state_file: str = ".auto-manage-state.json"
20 worktree_base: str = ".worktrees"
21 max_workers: int = 2
22 stream_output: bool = True
23 max_continuations: int = 3 # Max session restarts on context handoff
25 @classmethod
26 def from_dict(cls, data: dict[str, Any]) -> AutomationConfig:
27 """Create AutomationConfig from dictionary."""
28 return cls(
29 timeout_seconds=data.get("timeout_seconds", 3600),
30 idle_timeout_seconds=data.get("idle_timeout_seconds", 0),
31 state_file=data.get("state_file", ".auto-manage-state.json"),
32 worktree_base=data.get("worktree_base", ".worktrees"),
33 max_workers=data.get("max_workers", 2),
34 stream_output=data.get("stream_output", True),
35 max_continuations=data.get("max_continuations", 3),
36 )
39@dataclass
40class ParallelAutomationConfig:
41 """Parallel automation configuration using composition.
43 Uses AutomationConfig for shared settings (max_workers, worktree_base,
44 state_file, timeout_seconds, stream_output) plus parallel-specific fields.
45 """
47 base: AutomationConfig
48 p0_sequential: bool = True
49 max_merge_retries: int = 2
50 command_prefix: str = "/ll:"
51 ready_command: str = "ready-issue {{issue_id}}"
52 manage_command: str = "manage-issue {{issue_type}} {{action}} {{issue_id}}"
53 worktree_copy_files: list[str] = field(
54 default_factory=lambda: [".claude/settings.local.json", ".env"]
55 )
56 require_code_changes: bool = True
58 @classmethod
59 def from_dict(cls, data: dict[str, Any]) -> ParallelAutomationConfig:
60 """Create ParallelAutomationConfig from dictionary.
62 Shared fields use parallel-specific defaults:
63 - state_file: ".parallel-manage-state.json"
64 - stream_output: False
65 """
66 base = AutomationConfig(
67 timeout_seconds=data.get("timeout_seconds", 3600),
68 state_file=data.get("state_file", ".parallel-manage-state.json"),
69 worktree_base=data.get("worktree_base", ".worktrees"),
70 max_workers=data.get("max_workers", 2),
71 stream_output=data.get("stream_output", False),
72 )
73 return cls(
74 base=base,
75 p0_sequential=data.get("p0_sequential", True),
76 max_merge_retries=data.get("max_merge_retries", 2),
77 command_prefix=data.get("command_prefix", "/ll:"),
78 ready_command=data.get("ready_command", "ready-issue {{issue_id}}"),
79 manage_command=data.get(
80 "manage_command", "manage-issue {{issue_type}} {{action}} {{issue_id}}"
81 ),
82 worktree_copy_files=data.get(
83 "worktree_copy_files", [".claude/settings.local.json", ".env"]
84 ),
85 require_code_changes=data.get("require_code_changes", True),
86 )
89@dataclass
90class ConfidenceGateConfig:
91 """Confidence score gate configuration for manage-issue."""
93 enabled: bool = False
94 threshold: int = 85
96 @classmethod
97 def from_dict(cls, data: dict[str, Any]) -> ConfidenceGateConfig:
98 """Create ConfidenceGateConfig from dictionary."""
99 return cls(
100 enabled=data.get("enabled", False),
101 threshold=data.get("threshold", 85),
102 )
105@dataclass
106class CommandsConfig:
107 """Command customization configuration."""
109 pre_implement: str | None = None
110 post_implement: str | None = None
111 custom_verification: list[str] = field(default_factory=list)
112 confidence_gate: ConfidenceGateConfig = field(default_factory=ConfidenceGateConfig)
113 tdd_mode: bool = False
115 @classmethod
116 def from_dict(cls, data: dict[str, Any]) -> CommandsConfig:
117 """Create CommandsConfig from dictionary."""
118 return cls(
119 pre_implement=data.get("pre_implement"),
120 post_implement=data.get("post_implement"),
121 custom_verification=data.get("custom_verification", []),
122 confidence_gate=ConfidenceGateConfig.from_dict(data.get("confidence_gate", {})),
123 tdd_mode=data.get("tdd_mode", False),
124 )
127@dataclass
128class ScoringWeightsConfig:
129 """Scoring weights for semantic conflict analysis.
131 Weights for the three signals used in compute_conflict_score().
132 Should sum to 1.0 for normalized scoring.
134 Attributes:
135 semantic: Weight for semantic target overlap (component/function names)
136 section: Weight for section mention overlap (UI regions)
137 type: Weight for modification type match
138 """
140 semantic: float = 0.5
141 section: float = 0.3
142 type: float = 0.2
144 @classmethod
145 def from_dict(cls, data: dict[str, Any]) -> ScoringWeightsConfig:
146 """Create ScoringWeightsConfig from dictionary."""
147 return cls(
148 semantic=data.get("semantic", 0.5),
149 section=data.get("section", 0.3),
150 type=data.get("type", 0.2),
151 )
154@dataclass
155class DependencyMappingConfig:
156 """Dependency mapping threshold configuration.
158 Controls overlap detection sensitivity and conflict scoring thresholds.
159 Default values match the previously hardcoded constants for backwards
160 compatibility.
162 Attributes:
163 overlap_min_files: Minimum overlapping files to trigger overlap
164 overlap_min_ratio: Minimum ratio of overlapping files to smaller set
165 min_directory_depth: Minimum path segments for directory overlap
166 conflict_threshold: Below = parallel-safe, above = dependency proposed
167 high_conflict_threshold: Above = HIGH conflict label
168 confidence_modifier: Applied when dependency direction is ambiguous
169 scoring_weights: Weights for semantic/section/type signals
170 exclude_common_files: Infrastructure files excluded from overlap detection
171 """
173 overlap_min_files: int = 2
174 overlap_min_ratio: float = 0.25
175 min_directory_depth: int = 2
176 conflict_threshold: float = 0.4
177 high_conflict_threshold: float = 0.7
178 confidence_modifier: float = 0.5
179 scoring_weights: ScoringWeightsConfig = field(default_factory=ScoringWeightsConfig)
180 exclude_common_files: list[str] = field(
181 default_factory=lambda: [
182 "__init__.py",
183 "pyproject.toml",
184 "setup.py",
185 "setup.cfg",
186 "CHANGELOG.md",
187 "README.md",
188 "conftest.py",
189 ]
190 )
192 @classmethod
193 def from_dict(cls, data: dict[str, Any]) -> DependencyMappingConfig:
194 """Create DependencyMappingConfig from dictionary."""
195 return cls(
196 overlap_min_files=data.get("overlap_min_files", 2),
197 overlap_min_ratio=data.get("overlap_min_ratio", 0.25),
198 min_directory_depth=data.get("min_directory_depth", 2),
199 conflict_threshold=data.get("conflict_threshold", 0.4),
200 high_conflict_threshold=data.get("high_conflict_threshold", 0.7),
201 confidence_modifier=data.get("confidence_modifier", 0.5),
202 scoring_weights=ScoringWeightsConfig.from_dict(data.get("scoring_weights", {})),
203 exclude_common_files=data.get(
204 "exclude_common_files",
205 [
206 "__init__.py",
207 "pyproject.toml",
208 "setup.py",
209 "setup.cfg",
210 "CHANGELOG.md",
211 "README.md",
212 "conftest.py",
213 ],
214 ),
215 )