Coverage for little_loops / config / automation.py: 100%
71 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-04-11 23:20 -0500
« prev ^ index » next coverage.py v7.12.0, created at 2026-04-11 23: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
57 use_feature_branches: bool = False
58 remote_name: str = "origin"
60 @classmethod
61 def from_dict(cls, data: dict[str, Any]) -> ParallelAutomationConfig:
62 """Create ParallelAutomationConfig from dictionary.
64 Shared fields use parallel-specific defaults:
65 - state_file: ".parallel-manage-state.json"
66 - stream_output: False
67 """
68 base = AutomationConfig(
69 timeout_seconds=data.get("timeout_per_issue", data.get("timeout_seconds", 3600)),
70 state_file=data.get("state_file", ".parallel-manage-state.json"),
71 worktree_base=data.get("worktree_base", ".worktrees"),
72 max_workers=data.get("max_workers", 2),
73 stream_output=data.get("stream_subprocess_output", data.get("stream_output", False)),
74 )
75 return cls(
76 base=base,
77 p0_sequential=data.get("p0_sequential", True),
78 max_merge_retries=data.get("max_merge_retries", 2),
79 command_prefix=data.get("command_prefix", "/ll:"),
80 ready_command=data.get("ready_command", "ready-issue {{issue_id}}"),
81 manage_command=data.get(
82 "manage_command", "manage-issue {{issue_type}} {{action}} {{issue_id}}"
83 ),
84 worktree_copy_files=data.get(
85 "worktree_copy_files", [".claude/settings.local.json", ".env"]
86 ),
87 require_code_changes=data.get("require_code_changes", True),
88 use_feature_branches=data.get("use_feature_branches", False),
89 remote_name=data.get("remote_name", "origin"),
90 )
93@dataclass
94class ConfidenceGateConfig:
95 """Confidence score gate configuration for manage-issue."""
97 enabled: bool = False
98 readiness_threshold: int = 85
99 outcome_threshold: int = 70
101 @classmethod
102 def from_dict(cls, data: dict[str, Any]) -> ConfidenceGateConfig:
103 """Create ConfidenceGateConfig from dictionary."""
104 legacy = data.get("threshold", 85)
105 return cls(
106 enabled=data.get("enabled", False),
107 readiness_threshold=data.get("readiness_threshold", legacy),
108 outcome_threshold=data.get("outcome_threshold", 70),
109 )
112@dataclass
113class CommandsConfig:
114 """Command customization configuration."""
116 pre_implement: str | None = None
117 post_implement: str | None = None
118 custom_verification: list[str] = field(default_factory=list)
119 confidence_gate: ConfidenceGateConfig = field(default_factory=ConfidenceGateConfig)
120 tdd_mode: bool = False
122 @classmethod
123 def from_dict(cls, data: dict[str, Any]) -> CommandsConfig:
124 """Create CommandsConfig from dictionary."""
125 return cls(
126 pre_implement=data.get("pre_implement"),
127 post_implement=data.get("post_implement"),
128 custom_verification=data.get("custom_verification", []),
129 confidence_gate=ConfidenceGateConfig.from_dict(data.get("confidence_gate", {})),
130 tdd_mode=data.get("tdd_mode", False),
131 )
134@dataclass
135class ScoringWeightsConfig:
136 """Scoring weights for semantic conflict analysis.
138 Weights for the three signals used in compute_conflict_score().
139 Should sum to 1.0 for normalized scoring.
141 Attributes:
142 semantic: Weight for semantic target overlap (component/function names)
143 section: Weight for section mention overlap (UI regions)
144 type: Weight for modification type match
145 """
147 semantic: float = 0.5
148 section: float = 0.3
149 type: float = 0.2
151 @classmethod
152 def from_dict(cls, data: dict[str, Any]) -> ScoringWeightsConfig:
153 """Create ScoringWeightsConfig from dictionary."""
154 return cls(
155 semantic=data.get("semantic", 0.5),
156 section=data.get("section", 0.3),
157 type=data.get("type", 0.2),
158 )
161@dataclass
162class DependencyMappingConfig:
163 """Dependency mapping threshold configuration.
165 Controls overlap detection sensitivity and conflict scoring thresholds.
166 Default values match the previously hardcoded constants for backwards
167 compatibility.
169 Attributes:
170 overlap_min_files: Minimum overlapping files to trigger overlap
171 overlap_min_ratio: Minimum ratio of overlapping files to smaller set
172 min_directory_depth: Minimum path segments for directory overlap
173 conflict_threshold: Below = parallel-safe, above = dependency proposed
174 high_conflict_threshold: Above = HIGH conflict label
175 confidence_modifier: Applied when dependency direction is ambiguous
176 scoring_weights: Weights for semantic/section/type signals
177 exclude_common_files: Infrastructure files excluded from overlap detection
178 """
180 overlap_min_files: int = 2
181 overlap_min_ratio: float = 0.25
182 min_directory_depth: int = 2
183 conflict_threshold: float = 0.4
184 high_conflict_threshold: float = 0.7
185 confidence_modifier: float = 0.5
186 scoring_weights: ScoringWeightsConfig = field(default_factory=ScoringWeightsConfig)
187 exclude_common_files: list[str] = field(
188 default_factory=lambda: [
189 "__init__.py",
190 "pyproject.toml",
191 "setup.py",
192 "setup.cfg",
193 "CHANGELOG.md",
194 "README.md",
195 "conftest.py",
196 ]
197 )
199 @classmethod
200 def from_dict(cls, data: dict[str, Any]) -> DependencyMappingConfig:
201 """Create DependencyMappingConfig from dictionary."""
202 return cls(
203 overlap_min_files=data.get("overlap_min_files", 2),
204 overlap_min_ratio=data.get("overlap_min_ratio", 0.25),
205 min_directory_depth=data.get("min_directory_depth", 2),
206 conflict_threshold=data.get("conflict_threshold", 0.4),
207 high_conflict_threshold=data.get("high_conflict_threshold", 0.7),
208 confidence_modifier=data.get("confidence_modifier", 0.5),
209 scoring_weights=ScoringWeightsConfig.from_dict(data.get("scoring_weights", {})),
210 exclude_common_files=data.get(
211 "exclude_common_files",
212 [
213 "__init__.py",
214 "pyproject.toml",
215 "setup.py",
216 "setup.cfg",
217 "CHANGELOG.md",
218 "README.md",
219 "conftest.py",
220 ],
221 ),
222 )