Coverage for little_loops/cli_args.py: 30%

1"""Shared CLI argument definitions for little-loops tools.

2

3 Provides reusable functions for adding common command-line arguments

4 to argparse parsers, ensuring consistency across ll-auto, ll-parallel,

5 and ll-sprint commands.

6"""

7

8 from __future__ import annotations

9

10 import argparse

11 from pathlib import Path

12

13

14 def add_dry_run_arg(parser: argparse.ArgumentParser) -> None:

15 """Add --dry-run/-n argument to parser."""

16 parser.add_argument(

17 "--dry-run",

18 "-n",

19 action="store_true",

20 help="Show what would be done without making changes",

21 )

22

23

24 def add_resume_arg(parser: argparse.ArgumentParser) -> None:

25 """Add --resume/-r argument to parser."""

26 parser.add_argument(

27 "--resume",

28 "-r",

29 action="store_true",

30 help="Resume from previous checkpoint",

31 )

32

33

34 def add_config_arg(parser: argparse.ArgumentParser) -> None:

35 """Add --config argument to parser."""

36 parser.add_argument(

37 "--config",

38 type=Path,

39 default=None,

40 help="Path to project root (default: current directory)",

41 )

42

43

44 def add_only_arg(parser: argparse.ArgumentParser) -> None:

45 """Add --only argument for filtering specific issues."""

46 parser.add_argument(

47 "--only",

48 type=str,

49 default=None,

50 help="Comma-separated list of issue IDs to process (e.g., BUG-001,FEAT-002)",

51 )

52

53

54 def add_skip_arg(parser: argparse.ArgumentParser, help_text: str | None = None) -> None:

55 """Add --skip argument for excluding specific issues.

56

57 Args:

58 parser: The argument parser to add the argument to

59 help_text: Optional custom help text. If not provided, uses default.

60 """

61 if help_text is None:

62 help_text = "Comma-separated list of issue IDs to skip (e.g., BUG-003,FEAT-004)"

63 parser.add_argument(

64 "--skip",

65 type=str,

66 default=None,

67 help=help_text,

68 )

69

70

71 def add_max_workers_arg(parser: argparse.ArgumentParser, default: int | None = None) -> None:

72 """Add --max-workers/-w argument for parallel execution.

73

74 Args:

75 parser: The argument parser to add the argument to

76 default: Default value. If None, no default is specified.

77 """

78 if default is not None:

79 parser.add_argument(

80 "--max-workers",

81 "-w",

82 type=int,

83 default=default,

84 help=f"Maximum parallel workers (default: {default})",

85 )

86 else:

87 parser.add_argument(

88 "--max-workers",

89 "-w",

90 type=int,

91 default=None,

92 help="Maximum parallel workers",

93 )

94

95

96 def add_timeout_arg(parser: argparse.ArgumentParser, default: int | None = None) -> None:

97 """Add --timeout/-t argument for per-issue timeout.

98

99 Args:

100 parser: The argument parser to add the argument to

101 default: Default value in seconds. If None, no default is specified.

102 """

103 if default is not None:

104 parser.add_argument(

105 "--timeout",

106 "-t",

107 type=int,

108 default=default,

109 help=f"Timeout in seconds (default: {default})",

110 )

111 else:

112 parser.add_argument(

113 "--timeout",

114 "-t",

115 type=int,

116 default=None,

117 help="Timeout in seconds",

118 )

119

120

121 def add_idle_timeout_arg(parser: argparse.ArgumentParser) -> None:

122 """Add --idle-timeout argument for idle process termination.

123

124 Args:

125 parser: The argument parser to add the argument to

126 """

127 parser.add_argument(

128 "--idle-timeout",

129 type=int,

130 default=None,

131 help="Kill worker if no output for N seconds (0 to disable, default: from config)",

132 )

133

134

135 def add_handoff_threshold_arg(parser: argparse.ArgumentParser) -> None:

136 """Add --handoff-threshold argument for per-run context handoff override.

137

138 Args:

139 parser: The argument parser to add the argument to

140 """

141 parser.add_argument(

142 "--handoff-threshold",

143 type=int,

144 default=None,

145 help="Override auto-handoff context threshold (1-100, default: from config)",

146 )

147

148

149 def add_quiet_arg(parser: argparse.ArgumentParser) -> None:

150 """Add --quiet/-q argument to suppress output."""

151 parser.add_argument(

152 "--quiet",

153 "-q",

154 action="store_true",

155 help="Suppress non-essential output",

156 )

157

158

159 def add_skip_analysis_arg(parser: argparse.ArgumentParser) -> None:

160 """Add --skip-analysis argument to skip dependency discovery."""

161 parser.add_argument(

162 "--skip-analysis",

163 action="store_true",

164 help="Skip dependency analysis (use when dependencies are known to be current)",

165 )

166

167

168 def add_max_issues_arg(parser: argparse.ArgumentParser) -> None:

169 """Add --max-issues/-m argument for limiting issues processed."""

170 parser.add_argument(

171 "--max-issues",

172 "-m",

173 type=int,

174 default=0,

175 help="Limit number of issues to process (0 = unlimited)",

176 )

177

178

179 def parse_issue_ids(value: str | None) -> set[str] | None:

180 """Parse comma-separated issue IDs into a set.

181

182 Args:

183 value: Comma-separated string like "BUG-001,FEAT-002" or None

184

185 Returns:

186 Set of uppercase issue IDs, or None if value is None

187

188 Example:

189 >>> parse_issue_ids("BUG-001,feat-002")

190 {'BUG-001', 'FEAT-002'}

191 >>> parse_issue_ids(None)

192 None

193 """

194 if value is None:

195 return None

196 return {i.strip().upper() for i in value.split(",")}

197

198

199 def parse_issue_ids_ordered(value: str | None) -> list[str] | None:

200 """Parse comma-separated issue IDs into an ordered list.

201

202 Like parse_issue_ids but preserves input order, enabling callers to

203 honor the sequence in which IDs were specified.

204

205 Args:

206 value: Comma-separated string like "BUG-001,FEAT-002" or None

207

208 Returns:

209 List of uppercase issue IDs in input order, or None if value is None

210

211 Example:

212 >>> parse_issue_ids_ordered("BUG-010,FEAT-005,ENH-020")

213 ['BUG-010', 'FEAT-005', 'ENH-020']

214 >>> parse_issue_ids_ordered(None)

215 None

216 """

217 if value is None:

218 return None

219 return [i.strip().upper() for i in value.split(",")]

220

221

222 VALID_ISSUE_TYPES = {"BUG", "FEAT", "ENH"}

223

224

225 def add_type_arg(parser: argparse.ArgumentParser) -> None:

226 """Add --type argument for filtering issues by type prefix."""

227 parser.add_argument(

228 "--type",

229 type=str,

230 default=None,

231 help="Comma-separated issue types to process (e.g., BUG, FEAT, ENH)",

232 )

233

234

235 def parse_issue_types(value: str | None) -> set[str] | None:

236 """Parse comma-separated issue types into a validated set.

237

238 Args:

239 value: Comma-separated string like "BUG,ENH" or None

240

241 Returns:

242 Set of uppercase type prefixes, or None if value is None

243

244 Raises:

245 SystemExit: If invalid issue types are provided (via argparse error)

246

247 Example:

248 >>> parse_issue_types("bug,enh")

249 {'BUG', 'ENH'}

250 >>> parse_issue_types(None)

251 None

252 """

253 if value is None:

254 return None

255 types = {t.strip().upper() for t in value.split(",")}

256 invalid = types - VALID_ISSUE_TYPES

257 if invalid:

258 import sys

259

260 print(

261 f"error: invalid issue type(s): {', '.join(sorted(invalid))}. "

262 f"Valid types: {', '.join(sorted(VALID_ISSUE_TYPES))}",

263 file=sys.stderr,

264 )

265 sys.exit(2)

266 return types

267

268

269 def add_common_auto_args(parser: argparse.ArgumentParser) -> None:

270 """Add arguments common to ll-auto command.

271

272 Adds: --resume, --dry-run, --max-issues, --quiet, --only, --skip, --type, --config,

273 --idle-timeout, --handoff-threshold

274 """

275 add_resume_arg(parser)

276 add_dry_run_arg(parser)

277 add_max_issues_arg(parser)

278 add_quiet_arg(parser)

279 add_only_arg(parser)

280 add_skip_arg(parser)

281 add_type_arg(parser)

282 add_config_arg(parser)

283 add_idle_timeout_arg(parser)

284 add_handoff_threshold_arg(parser)

285

286

287 def add_common_parallel_args(parser: argparse.ArgumentParser) -> None:

288 """Add arguments common to parallel execution tools.

289

290 Adds: --dry-run, --resume, --max-workers, --timeout, --idle-timeout, --quiet, --only, --skip, --type, --config

291 """

292 add_dry_run_arg(parser)

293 add_resume_arg(parser)

294 add_max_workers_arg(parser)

295 add_timeout_arg(parser)

296 add_idle_timeout_arg(parser)

297 add_quiet_arg(parser)

298 add_only_arg(parser)

299 add_skip_arg(parser)

300 add_type_arg(parser)

301 add_config_arg(parser)

302

303

304 __all__ = [

305 "add_dry_run_arg",

306 "add_resume_arg",

307 "add_config_arg",

308 "add_only_arg",

309 "add_skip_arg",

310 "add_type_arg",

311 "add_max_workers_arg",

312 "add_timeout_arg",

313 "add_idle_timeout_arg",

314 "add_handoff_threshold_arg",

315 "add_quiet_arg",

316 "add_skip_analysis_arg",

317 "add_max_issues_arg",

318 "parse_issue_ids",

319 "parse_issue_types",

320 "VALID_ISSUE_TYPES",

321 "add_common_auto_args",

322 "add_common_parallel_args",

323]