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

1"""Automation and execution configuration dataclasses. 

2 

3Covers automation scripts, parallel execution, confidence gates, 

4command behavior, and dependency analysis configuration. 

5""" 

6 

7from __future__ import annotations 

8 

9from dataclasses import dataclass, field 

10from typing import Any 

11 

12 

13@dataclass 

14class AutomationConfig: 

15 """Automation script configuration.""" 

16 

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 

24 

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 ) 

37 

38 

39@dataclass 

40class ParallelAutomationConfig: 

41 """Parallel automation configuration using composition. 

42 

43 Uses AutomationConfig for shared settings (max_workers, worktree_base, 

44 state_file, timeout_seconds, stream_output) plus parallel-specific fields. 

45 """ 

46 

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 

58 @classmethod 

59 def from_dict(cls, data: dict[str, Any]) -> ParallelAutomationConfig: 

60 """Create ParallelAutomationConfig from dictionary. 

61 

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 ) 

87 

88 

89@dataclass 

90class ConfidenceGateConfig: 

91 """Confidence score gate configuration for manage-issue.""" 

92 

93 enabled: bool = False 

94 threshold: int = 85 

95 

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 ) 

103 

104 

105@dataclass 

106class CommandsConfig: 

107 """Command customization configuration.""" 

108 

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 

114 

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 ) 

125 

126 

127@dataclass 

128class ScoringWeightsConfig: 

129 """Scoring weights for semantic conflict analysis. 

130 

131 Weights for the three signals used in compute_conflict_score(). 

132 Should sum to 1.0 for normalized scoring. 

133 

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 """ 

139 

140 semantic: float = 0.5 

141 section: float = 0.3 

142 type: float = 0.2 

143 

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 ) 

152 

153 

154@dataclass 

155class DependencyMappingConfig: 

156 """Dependency mapping threshold configuration. 

157 

158 Controls overlap detection sensitivity and conflict scoring thresholds. 

159 Default values match the previously hardcoded constants for backwards 

160 compatibility. 

161 

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 """ 

172 

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 ) 

191 

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 )