doit-toolkit-cli 0.1.9__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 (134) hide show
  1. doit_cli/__init__.py +1356 -0
  2. doit_cli/cli/__init__.py +26 -0
  3. doit_cli/cli/analytics_command.py +616 -0
  4. doit_cli/cli/context_command.py +213 -0
  5. doit_cli/cli/diagram_command.py +304 -0
  6. doit_cli/cli/fixit_command.py +641 -0
  7. doit_cli/cli/hooks_command.py +211 -0
  8. doit_cli/cli/init_command.py +613 -0
  9. doit_cli/cli/memory_command.py +293 -0
  10. doit_cli/cli/status_command.py +117 -0
  11. doit_cli/cli/sync_prompts_command.py +248 -0
  12. doit_cli/cli/validate_command.py +196 -0
  13. doit_cli/cli/verify_command.py +204 -0
  14. doit_cli/cli/workflow_mixin.py +224 -0
  15. doit_cli/cli/xref_command.py +555 -0
  16. doit_cli/formatters/__init__.py +8 -0
  17. doit_cli/formatters/base.py +38 -0
  18. doit_cli/formatters/json_formatter.py +126 -0
  19. doit_cli/formatters/markdown_formatter.py +97 -0
  20. doit_cli/formatters/rich_formatter.py +257 -0
  21. doit_cli/main.py +49 -0
  22. doit_cli/models/__init__.py +139 -0
  23. doit_cli/models/agent.py +74 -0
  24. doit_cli/models/analytics_models.py +384 -0
  25. doit_cli/models/context_config.py +464 -0
  26. doit_cli/models/crossref_models.py +182 -0
  27. doit_cli/models/diagram_models.py +363 -0
  28. doit_cli/models/fixit_models.py +355 -0
  29. doit_cli/models/hook_config.py +125 -0
  30. doit_cli/models/project.py +91 -0
  31. doit_cli/models/results.py +121 -0
  32. doit_cli/models/search_models.py +228 -0
  33. doit_cli/models/status_models.py +195 -0
  34. doit_cli/models/sync_models.py +146 -0
  35. doit_cli/models/template.py +77 -0
  36. doit_cli/models/validation_models.py +175 -0
  37. doit_cli/models/workflow_models.py +319 -0
  38. doit_cli/prompts/__init__.py +5 -0
  39. doit_cli/prompts/fixit_prompts.py +344 -0
  40. doit_cli/prompts/interactive.py +390 -0
  41. doit_cli/rules/__init__.py +5 -0
  42. doit_cli/rules/builtin_rules.py +160 -0
  43. doit_cli/services/__init__.py +79 -0
  44. doit_cli/services/agent_detector.py +168 -0
  45. doit_cli/services/analytics_service.py +218 -0
  46. doit_cli/services/architecture_generator.py +290 -0
  47. doit_cli/services/backup_service.py +204 -0
  48. doit_cli/services/config_loader.py +113 -0
  49. doit_cli/services/context_loader.py +1121 -0
  50. doit_cli/services/coverage_calculator.py +142 -0
  51. doit_cli/services/crossref_service.py +237 -0
  52. doit_cli/services/cycle_time_calculator.py +134 -0
  53. doit_cli/services/date_inferrer.py +349 -0
  54. doit_cli/services/diagram_service.py +337 -0
  55. doit_cli/services/drift_detector.py +109 -0
  56. doit_cli/services/entity_parser.py +301 -0
  57. doit_cli/services/er_diagram_generator.py +197 -0
  58. doit_cli/services/fixit_service.py +699 -0
  59. doit_cli/services/github_service.py +192 -0
  60. doit_cli/services/hook_manager.py +258 -0
  61. doit_cli/services/hook_validator.py +528 -0
  62. doit_cli/services/input_validator.py +322 -0
  63. doit_cli/services/memory_search.py +527 -0
  64. doit_cli/services/mermaid_validator.py +334 -0
  65. doit_cli/services/prompt_transformer.py +91 -0
  66. doit_cli/services/prompt_writer.py +133 -0
  67. doit_cli/services/query_interpreter.py +428 -0
  68. doit_cli/services/report_exporter.py +219 -0
  69. doit_cli/services/report_generator.py +256 -0
  70. doit_cli/services/requirement_parser.py +112 -0
  71. doit_cli/services/roadmap_summarizer.py +209 -0
  72. doit_cli/services/rule_engine.py +443 -0
  73. doit_cli/services/scaffolder.py +215 -0
  74. doit_cli/services/score_calculator.py +172 -0
  75. doit_cli/services/section_parser.py +204 -0
  76. doit_cli/services/spec_scanner.py +327 -0
  77. doit_cli/services/state_manager.py +355 -0
  78. doit_cli/services/status_reporter.py +143 -0
  79. doit_cli/services/task_parser.py +347 -0
  80. doit_cli/services/template_manager.py +710 -0
  81. doit_cli/services/template_reader.py +158 -0
  82. doit_cli/services/user_journey_generator.py +214 -0
  83. doit_cli/services/user_story_parser.py +232 -0
  84. doit_cli/services/validation_service.py +188 -0
  85. doit_cli/services/validator.py +232 -0
  86. doit_cli/services/velocity_tracker.py +173 -0
  87. doit_cli/services/workflow_engine.py +405 -0
  88. doit_cli/templates/agent-file-template.md +28 -0
  89. doit_cli/templates/checklist-template.md +39 -0
  90. doit_cli/templates/commands/doit.checkin.md +363 -0
  91. doit_cli/templates/commands/doit.constitution.md +187 -0
  92. doit_cli/templates/commands/doit.documentit.md +485 -0
  93. doit_cli/templates/commands/doit.fixit.md +181 -0
  94. doit_cli/templates/commands/doit.implementit.md +265 -0
  95. doit_cli/templates/commands/doit.planit.md +262 -0
  96. doit_cli/templates/commands/doit.reviewit.md +355 -0
  97. doit_cli/templates/commands/doit.roadmapit.md +368 -0
  98. doit_cli/templates/commands/doit.scaffoldit.md +458 -0
  99. doit_cli/templates/commands/doit.specit.md +521 -0
  100. doit_cli/templates/commands/doit.taskit.md +304 -0
  101. doit_cli/templates/commands/doit.testit.md +277 -0
  102. doit_cli/templates/config/context.yaml +134 -0
  103. doit_cli/templates/config/hooks.yaml +93 -0
  104. doit_cli/templates/config/validation-rules.yaml +64 -0
  105. doit_cli/templates/github-issue-templates/epic.yml +78 -0
  106. doit_cli/templates/github-issue-templates/feature.yml +116 -0
  107. doit_cli/templates/github-issue-templates/task.yml +129 -0
  108. doit_cli/templates/hooks/.gitkeep +0 -0
  109. doit_cli/templates/hooks/post-commit.sh +25 -0
  110. doit_cli/templates/hooks/post-merge.sh +75 -0
  111. doit_cli/templates/hooks/pre-commit.sh +17 -0
  112. doit_cli/templates/hooks/pre-push.sh +18 -0
  113. doit_cli/templates/memory/completed_roadmap.md +50 -0
  114. doit_cli/templates/memory/constitution.md +125 -0
  115. doit_cli/templates/memory/roadmap.md +61 -0
  116. doit_cli/templates/plan-template.md +146 -0
  117. doit_cli/templates/scripts/bash/check-prerequisites.sh +166 -0
  118. doit_cli/templates/scripts/bash/common.sh +156 -0
  119. doit_cli/templates/scripts/bash/create-new-feature.sh +297 -0
  120. doit_cli/templates/scripts/bash/setup-plan.sh +61 -0
  121. doit_cli/templates/scripts/bash/update-agent-context.sh +675 -0
  122. doit_cli/templates/scripts/powershell/check-prerequisites.ps1 +148 -0
  123. doit_cli/templates/scripts/powershell/common.ps1 +137 -0
  124. doit_cli/templates/scripts/powershell/create-new-feature.ps1 +283 -0
  125. doit_cli/templates/scripts/powershell/setup-plan.ps1 +61 -0
  126. doit_cli/templates/scripts/powershell/update-agent-context.ps1 +406 -0
  127. doit_cli/templates/spec-template.md +159 -0
  128. doit_cli/templates/tasks-template.md +313 -0
  129. doit_cli/templates/vscode-settings.json +14 -0
  130. doit_toolkit_cli-0.1.9.dist-info/METADATA +324 -0
  131. doit_toolkit_cli-0.1.9.dist-info/RECORD +134 -0
  132. doit_toolkit_cli-0.1.9.dist-info/WHEEL +4 -0
  133. doit_toolkit_cli-0.1.9.dist-info/entry_points.txt +2 -0
  134. doit_toolkit_cli-0.1.9.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,390 @@
1
+ """Interactive prompts for guided workflows.
2
+
3
+ This module provides the InteractivePrompt and ProgressDisplay classes
4
+ for collecting user input and showing workflow progress.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ from typing import Callable
10
+
11
+ from rich.console import Console
12
+ from rich.panel import Panel
13
+ from rich.prompt import Prompt, Confirm
14
+ from rich.text import Text
15
+ from rich.theme import Theme
16
+
17
+ from ..models.workflow_models import (
18
+ WorkflowStep,
19
+ ValidationResult,
20
+ NavigationCommand,
21
+ )
22
+
23
+
24
+ # =============================================================================
25
+ # Theme Configuration
26
+ # =============================================================================
27
+
28
+
29
+ WORKFLOW_THEME = Theme({
30
+ "prompt": "bold cyan",
31
+ "prompt.default": "dim",
32
+ "error": "bold red",
33
+ "success": "bold green",
34
+ "warning": "bold yellow",
35
+ "info": "dim",
36
+ "step.current": "bold white",
37
+ "step.completed": "green",
38
+ "step.skipped": "dim",
39
+ "step.pending": "dim white",
40
+ })
41
+
42
+
43
+ # =============================================================================
44
+ # InteractivePrompt Class
45
+ # =============================================================================
46
+
47
+
48
+ class InteractivePrompt:
49
+ """Collects user input for workflow steps.
50
+
51
+ This class handles interactive prompting for workflow steps,
52
+ including text input, choice selection, and confirmation dialogs.
53
+ """
54
+
55
+ # Environment variable to force non-interactive mode
56
+ NON_INTERACTIVE_ENV_VAR = "DOIT_NON_INTERACTIVE"
57
+
58
+ def __init__(
59
+ self,
60
+ console: Console | None = None,
61
+ force_non_interactive: bool = False,
62
+ ):
63
+ """Initialize the prompt handler.
64
+
65
+ Args:
66
+ console: Rich console for output. Creates a new one if not provided.
67
+ force_non_interactive: If True, always use non-interactive mode.
68
+ """
69
+ self.console = console or Console(theme=WORKFLOW_THEME)
70
+ self._force_non_interactive = force_non_interactive
71
+
72
+ def prompt(
73
+ self,
74
+ step: WorkflowStep,
75
+ validator: "InputValidator | None" = None,
76
+ ) -> str:
77
+ """Prompt user for step input.
78
+
79
+ Args:
80
+ step: Step to prompt for
81
+ validator: Optional validator for real-time feedback
82
+
83
+ Returns:
84
+ User-provided value
85
+
86
+ Raises:
87
+ NavigationCommand: If user types 'back' or 'skip'
88
+ KeyboardInterrupt: If user presses Ctrl+C
89
+ """
90
+ # Non-TTY mode - return default or raise error
91
+ if not self._is_interactive():
92
+ if step.default_value is not None:
93
+ return step.default_value
94
+ if not step.required:
95
+ return ""
96
+ raise ValueError(f"Required step '{step.id}' has no default for non-interactive mode")
97
+
98
+ while True:
99
+ # Display the prompt
100
+ self._display_prompt(step)
101
+
102
+ # Get input
103
+ value = self._get_input(step)
104
+
105
+ # Handle navigation commands
106
+ nav_result = self._check_navigation(value, step)
107
+ if nav_result is not None:
108
+ raise nav_result
109
+
110
+ # Handle empty input for optional steps
111
+ if not value and not step.required:
112
+ return step.default_value or ""
113
+
114
+ # Validate if validator provided
115
+ if validator:
116
+ from ..services.input_validator import validate_step
117
+
118
+ result = validator.validate(value, step, {})
119
+ if not result.passed:
120
+ self._show_validation_error(result)
121
+ continue
122
+
123
+ return value
124
+
125
+ def prompt_choice(
126
+ self,
127
+ step: WorkflowStep,
128
+ options: dict[str, str],
129
+ ) -> str:
130
+ """Prompt user to select from options.
131
+
132
+ Args:
133
+ step: Step with choice options
134
+ options: {value: description} mapping
135
+
136
+ Returns:
137
+ Selected option value
138
+
139
+ Raises:
140
+ NavigationCommand: If user types 'back'
141
+ KeyboardInterrupt: If user presses Ctrl+C
142
+ """
143
+ # Non-TTY mode - return first option or default
144
+ if not self._is_interactive():
145
+ if step.default_value and step.default_value in options:
146
+ return step.default_value
147
+ return list(options.keys())[0]
148
+
149
+ while True:
150
+ # Display prompt and options
151
+ self._display_prompt(step)
152
+ self._display_options(options)
153
+
154
+ # Get choice
155
+ value = self._get_choice_input(step, options)
156
+
157
+ # Handle navigation
158
+ nav_result = self._check_navigation(value, step)
159
+ if nav_result is not None:
160
+ raise nav_result
161
+
162
+ # Validate choice
163
+ normalized = value.strip().lower()
164
+ for key in options:
165
+ if key.lower() == normalized:
166
+ return key
167
+
168
+ self._show_invalid_choice(value, options)
169
+
170
+ def prompt_confirm(
171
+ self,
172
+ message: str,
173
+ default: bool = True,
174
+ ) -> bool:
175
+ """Prompt for yes/no confirmation.
176
+
177
+ Args:
178
+ message: Confirmation prompt
179
+ default: Default if Enter pressed
180
+
181
+ Returns:
182
+ True for yes, False for no
183
+ """
184
+ # Non-TTY mode - return default
185
+ if not self._is_interactive():
186
+ return default
187
+
188
+ while True:
189
+ default_str = "Y/n" if default else "y/N"
190
+ self.console.print(f"[prompt]{message}[/prompt] [{default_str}] ", end="")
191
+
192
+ value = self._get_input(None)
193
+
194
+ if not value:
195
+ return default
196
+
197
+ normalized = value.strip().lower()
198
+ if normalized in ("y", "yes"):
199
+ return True
200
+ if normalized in ("n", "no"):
201
+ return False
202
+
203
+ self._show_invalid_confirm()
204
+
205
+ # =========================================================================
206
+ # Internal Methods
207
+ # =========================================================================
208
+
209
+ def _is_interactive(self) -> bool:
210
+ """Check if running in an interactive terminal.
211
+
212
+ Returns False if:
213
+ - force_non_interactive is True
214
+ - DOIT_NON_INTERACTIVE env var is set to "true", "1", or "yes"
215
+ - stdin is not a TTY
216
+ """
217
+ if self._force_non_interactive:
218
+ return False
219
+
220
+ # Check environment variable
221
+ env_value = os.environ.get(self.NON_INTERACTIVE_ENV_VAR, "").lower()
222
+ if env_value in ("true", "1", "yes"):
223
+ return False
224
+
225
+ return sys.stdin.isatty()
226
+
227
+ def _display_prompt(self, step: WorkflowStep) -> None:
228
+ """Display the prompt text for a step."""
229
+ self.console.print()
230
+ self.console.print(f"[prompt]{step.prompt_text}[/prompt]")
231
+
232
+ if step.default_value and not step.options:
233
+ self.console.print(f"[prompt.default](default: {step.default_value})[/prompt.default]")
234
+
235
+ if not step.required:
236
+ self.console.print("[info]Type 'skip' to skip this step[/info]")
237
+
238
+ self.console.print("[info]Type 'back' to go to previous step[/info]")
239
+
240
+ def _display_options(self, options: dict[str, str]) -> None:
241
+ """Display available options for a choice step."""
242
+ self.console.print()
243
+ for key, description in options.items():
244
+ self.console.print(f" [bold]{key}[/bold]: {description}")
245
+ self.console.print()
246
+
247
+ def _get_input(self, step: WorkflowStep | None) -> str:
248
+ """Get raw input from user."""
249
+ try:
250
+ return input("> ").strip()
251
+ except EOFError:
252
+ # Handle piped input ending
253
+ if step and step.default_value:
254
+ return step.default_value
255
+ return ""
256
+
257
+ def _get_choice_input(self, step: WorkflowStep, options: dict[str, str]) -> str:
258
+ """Get choice input from user."""
259
+ return self._get_input(step)
260
+
261
+ def _check_navigation(
262
+ self,
263
+ value: str,
264
+ step: WorkflowStep,
265
+ ) -> NavigationCommand | None:
266
+ """Check if input is a navigation command."""
267
+ normalized = value.strip().lower()
268
+
269
+ if normalized == "back":
270
+ return NavigationCommand("back")
271
+
272
+ if normalized == "skip":
273
+ if not step.required:
274
+ return NavigationCommand("skip")
275
+ # For required steps, 'skip' is treated as regular input
276
+
277
+ return None
278
+
279
+ def _show_validation_error(self, result: ValidationResult) -> None:
280
+ """Display a validation error message."""
281
+ self.console.print()
282
+ self.console.print(f"[error]Error: {result.error_message}[/error]")
283
+ if result.suggestion:
284
+ self.console.print(f"[info]Hint: {result.suggestion}[/info]")
285
+ self.console.print()
286
+
287
+ def _show_invalid_choice(self, value: str, options: dict[str, str]) -> None:
288
+ """Display an invalid choice error."""
289
+ valid_options = ", ".join(options.keys())
290
+ self.console.print()
291
+ self.console.print(f"[error]Invalid choice: '{value}'[/error]")
292
+ self.console.print(f"[info]Valid options: {valid_options}[/info]")
293
+ self.console.print()
294
+
295
+ def _show_invalid_confirm(self) -> None:
296
+ """Display an invalid confirmation error."""
297
+ self.console.print("[error]Please enter 'y' or 'n'[/error]")
298
+
299
+
300
+ # =============================================================================
301
+ # ProgressDisplay Class
302
+ # =============================================================================
303
+
304
+
305
+ class ProgressDisplay:
306
+ """Displays workflow progress to the user.
307
+
308
+ Shows the current step, completed steps, and overall progress
309
+ through a multi-step workflow.
310
+ """
311
+
312
+ def __init__(self, console: Console | None = None):
313
+ """Initialize the progress display.
314
+
315
+ Args:
316
+ console: Rich console for output. Creates a new one if not provided.
317
+ """
318
+ self.console = console or Console(theme=WORKFLOW_THEME)
319
+ self._completed_steps: list[str] = []
320
+ self._skipped_steps: list[str] = []
321
+
322
+ def show_step(
323
+ self,
324
+ step: WorkflowStep,
325
+ current: int,
326
+ total: int,
327
+ ) -> None:
328
+ """Show current step indicator.
329
+
330
+ Args:
331
+ step: Current step
332
+ current: Current step number (1-indexed)
333
+ total: Total step count
334
+ """
335
+ percentage = int((current - 1) / total * 100) if total > 0 else 0
336
+
337
+ header = f"Step {current} of {total}: {step.name}"
338
+ if not step.required:
339
+ header += " (optional)"
340
+
341
+ self.console.print()
342
+ self.console.rule(f"[step.current]{header}[/step.current]")
343
+
344
+ # Show progress percentage
345
+ self.console.print(f"[info]Progress: {percentage}% complete[/info]")
346
+
347
+ def mark_complete(self, step: WorkflowStep) -> None:
348
+ """Mark step as completed in display."""
349
+ self._completed_steps.append(step.id)
350
+ self.console.print(f"[success]\u2713 {step.name} completed[/success]")
351
+
352
+ def mark_skipped(self, step: WorkflowStep) -> None:
353
+ """Mark step as skipped in display."""
354
+ self._skipped_steps.append(step.id)
355
+ self.console.print(f"[step.skipped]- {step.name} skipped[/step.skipped]")
356
+
357
+ def show_error(
358
+ self,
359
+ step: WorkflowStep,
360
+ error: ValidationResult,
361
+ ) -> None:
362
+ """Show validation error with suggestion."""
363
+ self.console.print()
364
+ self.console.print(Panel(
365
+ f"[error]{error.error_message}[/error]\n\n"
366
+ f"[info]{error.suggestion or 'Please try again.'}[/info]",
367
+ title=f"Validation Error: {step.name}",
368
+ border_style="red",
369
+ ))
370
+
371
+ def show_summary(self, total_steps: int) -> None:
372
+ """Show final summary of workflow progress."""
373
+ completed = len(self._completed_steps)
374
+ skipped = len(self._skipped_steps)
375
+
376
+ self.console.print()
377
+ self.console.rule("[success]Workflow Complete[/success]")
378
+ self.console.print(f"[success]Completed: {completed} steps[/success]")
379
+ if skipped > 0:
380
+ self.console.print(f"[step.skipped]Skipped: {skipped} steps[/step.skipped]")
381
+ self.console.print()
382
+
383
+ def show_interrupted(self, current_step: int, total_steps: int) -> None:
384
+ """Show message when workflow is interrupted."""
385
+ self.console.print()
386
+ self.console.print(
387
+ f"[warning]Workflow interrupted at step {current_step} of {total_steps}[/warning]"
388
+ )
389
+ self.console.print("[info]Progress has been saved. Run the command again to resume.[/info]")
390
+ self.console.print()
@@ -0,0 +1,5 @@
1
+ """Validation rules for spec file linting."""
2
+
3
+ from .builtin_rules import BUILTIN_RULES, get_builtin_rules
4
+
5
+ __all__ = ["BUILTIN_RULES", "get_builtin_rules"]
@@ -0,0 +1,160 @@
1
+ """Built-in validation rules for spec files."""
2
+
3
+ from dataclasses import replace
4
+
5
+ from ..models.validation_models import Severity, ValidationRule
6
+
7
+ # Default validation rules
8
+ BUILTIN_RULES: list[ValidationRule] = [
9
+ # Structure rules (error severity)
10
+ ValidationRule(
11
+ id="missing-user-scenarios",
12
+ name="Missing User Scenarios",
13
+ description="Spec must include a User Scenarios section",
14
+ severity=Severity.ERROR,
15
+ category="structure",
16
+ pattern=r"^##\s+User\s+Scenarios",
17
+ ),
18
+ ValidationRule(
19
+ id="missing-requirements",
20
+ name="Missing Requirements",
21
+ description="Spec must include a Requirements section",
22
+ severity=Severity.ERROR,
23
+ category="structure",
24
+ pattern=r"^##\s+Requirements",
25
+ ),
26
+ ValidationRule(
27
+ id="missing-success-criteria",
28
+ name="Missing Success Criteria",
29
+ description="Spec must include a Success Criteria section",
30
+ severity=Severity.ERROR,
31
+ category="structure",
32
+ pattern=r"^##\s+Success\s+Criteria",
33
+ ),
34
+ # Requirements rules (warning severity)
35
+ ValidationRule(
36
+ id="fr-naming-convention",
37
+ name="FR Naming Convention",
38
+ description="Functional requirements should follow FR-XXX pattern",
39
+ severity=Severity.WARNING,
40
+ category="requirements",
41
+ pattern=r"^\s*-\s+\*\*FR-\d{3}\*\*:",
42
+ ),
43
+ ValidationRule(
44
+ id="sc-naming-convention",
45
+ name="SC Naming Convention",
46
+ description="Success criteria should follow SC-XXX pattern",
47
+ severity=Severity.WARNING,
48
+ category="requirements",
49
+ pattern=r"^\s*-\s+\*\*SC-\d{3}\*\*:",
50
+ ),
51
+ # Acceptance rules (warning severity)
52
+ ValidationRule(
53
+ id="missing-acceptance-scenarios",
54
+ name="Missing Acceptance Scenarios",
55
+ description="User stories should have acceptance scenarios with Given/When/Then",
56
+ severity=Severity.WARNING,
57
+ category="acceptance",
58
+ pattern=r"\*\*Given\*\*.*\*\*When\*\*.*\*\*Then\*\*",
59
+ ),
60
+ ValidationRule(
61
+ id="incomplete-given-when-then",
62
+ name="Incomplete Given/When/Then",
63
+ description="Acceptance scenarios should have complete Given/When/Then format",
64
+ severity=Severity.INFO,
65
+ category="acceptance",
66
+ pattern=r"Given.*When.*Then",
67
+ ),
68
+ # Clarity rules (warning severity)
69
+ ValidationRule(
70
+ id="unresolved-clarification",
71
+ name="Unresolved Clarification",
72
+ description="Spec should not have [NEEDS CLARIFICATION] markers",
73
+ severity=Severity.WARNING,
74
+ category="clarity",
75
+ pattern=r"\[NEEDS\s+CLARIFICATION",
76
+ ),
77
+ ValidationRule(
78
+ id="todo-in-approved-spec",
79
+ name="TODO in Approved Spec",
80
+ description="Approved specs should not have TODO or FIXME markers",
81
+ severity=Severity.WARNING,
82
+ category="clarity",
83
+ pattern=r"\b(TODO|FIXME)\b",
84
+ ),
85
+ # Naming rules (info severity)
86
+ ValidationRule(
87
+ id="feature-branch-format",
88
+ name="Feature Branch Format",
89
+ description="Feature branch should follow NNN-feature-name format",
90
+ severity=Severity.INFO,
91
+ category="naming",
92
+ pattern=r"\*\*Feature\s+Branch\*\*:\s+`\d{3}-[\w-]+`",
93
+ ),
94
+ # Traceability rules (cross-reference validation)
95
+ ValidationRule(
96
+ id="orphaned-task-reference",
97
+ name="Orphaned Task Reference",
98
+ description="Task references a requirement that does not exist in spec.md",
99
+ severity=Severity.ERROR,
100
+ category="traceability",
101
+ pattern=None, # Requires cross-file analysis
102
+ ),
103
+ ValidationRule(
104
+ id="uncovered-requirement",
105
+ name="Uncovered Requirement",
106
+ description="Requirement has no linked tasks in tasks.md",
107
+ severity=Severity.WARNING,
108
+ category="traceability",
109
+ pattern=None, # Requires cross-file analysis
110
+ ),
111
+ ]
112
+
113
+
114
+ def get_builtin_rules() -> list[ValidationRule]:
115
+ """Get all built-in validation rules.
116
+
117
+ Returns:
118
+ List of ValidationRule objects with default configuration.
119
+ Each call returns fresh copies to prevent mutation issues.
120
+ """
121
+ return [replace(rule) for rule in BUILTIN_RULES]
122
+
123
+
124
+ def get_rule_by_id(rule_id: str) -> ValidationRule | None:
125
+ """Get a specific built-in rule by ID.
126
+
127
+ Args:
128
+ rule_id: The unique identifier of the rule.
129
+
130
+ Returns:
131
+ The ValidationRule if found, None otherwise.
132
+ """
133
+ for rule in BUILTIN_RULES:
134
+ if rule.id == rule_id:
135
+ return rule
136
+ return None
137
+
138
+
139
+ def get_rules_by_category(category: str) -> list[ValidationRule]:
140
+ """Get all built-in rules in a category.
141
+
142
+ Args:
143
+ category: Category name (structure, requirements, acceptance, clarity, naming).
144
+
145
+ Returns:
146
+ List of ValidationRule objects in the category.
147
+ """
148
+ return [rule for rule in BUILTIN_RULES if rule.category == category]
149
+
150
+
151
+ def get_rules_by_severity(severity: Severity) -> list[ValidationRule]:
152
+ """Get all built-in rules with a specific severity.
153
+
154
+ Args:
155
+ severity: Severity level to filter by.
156
+
157
+ Returns:
158
+ List of ValidationRule objects with that severity.
159
+ """
160
+ return [rule for rule in BUILTIN_RULES if rule.severity == severity]
@@ -0,0 +1,79 @@
1
+ """Services for doit-cli project initialization and management."""
2
+
3
+ from .agent_detector import AgentDetector
4
+ from .backup_service import BackupService
5
+ from .context_loader import ContextLoader, estimate_tokens, truncate_content
6
+ from .scaffolder import Scaffolder
7
+ from .template_manager import TemplateManager
8
+ from .validator import Validator
9
+ from .input_validator import (
10
+ InputValidator,
11
+ RequiredValidator,
12
+ PathExistsValidator,
13
+ ChoiceValidator,
14
+ PatternValidator,
15
+ get_validator,
16
+ register_validator,
17
+ chain_validators,
18
+ validate_step,
19
+ )
20
+ from .workflow_engine import WorkflowEngine
21
+ from .state_manager import StateManager
22
+ from .spec_scanner import SpecScanner, NotADoitProjectError, SpecNotFoundError
23
+ from .status_reporter import StatusReporter
24
+ from .requirement_parser import RequirementParser
25
+ from .task_parser import TaskParser
26
+ from .coverage_calculator import CoverageCalculator
27
+ from .crossref_service import CrossReferenceService
28
+ from .diagram_service import DiagramService
29
+ from .section_parser import SectionParser
30
+ from .user_story_parser import UserStoryParser
31
+ from .entity_parser import EntityParser
32
+ from .user_journey_generator import UserJourneyGenerator
33
+ from .er_diagram_generator import ERDiagramGenerator
34
+ from .mermaid_validator import MermaidValidator
35
+ from .architecture_generator import ArchitectureGenerator
36
+
37
+ __all__ = [
38
+ "AgentDetector",
39
+ "BackupService",
40
+ "ContextLoader",
41
+ "Scaffolder",
42
+ "TemplateManager",
43
+ "Validator",
44
+ "estimate_tokens",
45
+ "truncate_content",
46
+ # Input validation
47
+ "InputValidator",
48
+ "RequiredValidator",
49
+ "PathExistsValidator",
50
+ "ChoiceValidator",
51
+ "PatternValidator",
52
+ "get_validator",
53
+ "register_validator",
54
+ "chain_validators",
55
+ "validate_step",
56
+ # Workflow engine
57
+ "WorkflowEngine",
58
+ # State management
59
+ "StateManager",
60
+ # Status dashboard
61
+ "SpecScanner",
62
+ "NotADoitProjectError",
63
+ "SpecNotFoundError",
64
+ "StatusReporter",
65
+ # Cross-reference services
66
+ "RequirementParser",
67
+ "TaskParser",
68
+ "CoverageCalculator",
69
+ "CrossReferenceService",
70
+ # Diagram services
71
+ "DiagramService",
72
+ "SectionParser",
73
+ "UserStoryParser",
74
+ "EntityParser",
75
+ "UserJourneyGenerator",
76
+ "ERDiagramGenerator",
77
+ "MermaidValidator",
78
+ "ArchitectureGenerator",
79
+ ]