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,322 @@
1
+ """Input validators for workflow steps.
2
+
3
+ This module provides validators for step inputs with real-time feedback.
4
+ """
5
+
6
+ import os
7
+ import re
8
+ from abc import ABC, abstractmethod
9
+ from pathlib import Path
10
+ from typing import Protocol, runtime_checkable
11
+
12
+ from ..models.workflow_models import (
13
+ WorkflowStep,
14
+ StepResponse,
15
+ ValidationResult,
16
+ )
17
+
18
+
19
+ # =============================================================================
20
+ # Validator Protocol and Base Class
21
+ # =============================================================================
22
+
23
+
24
+ @runtime_checkable
25
+ class InputValidator(Protocol):
26
+ """Protocol for step validators."""
27
+
28
+ def validate(
29
+ self,
30
+ value: str,
31
+ step: WorkflowStep,
32
+ context: dict[str, StepResponse],
33
+ ) -> ValidationResult:
34
+ """Validate a step input value.
35
+
36
+ Args:
37
+ value: User-provided input
38
+ step: Step being validated
39
+ context: Previous step responses
40
+
41
+ Returns:
42
+ ValidationResult with pass/fail and messages
43
+ """
44
+ ...
45
+
46
+
47
+ class BaseValidator(ABC):
48
+ """Abstract base class for validators with common functionality."""
49
+
50
+ @abstractmethod
51
+ def validate(
52
+ self,
53
+ value: str,
54
+ step: WorkflowStep,
55
+ context: dict[str, StepResponse],
56
+ ) -> ValidationResult:
57
+ """Validate the input value."""
58
+ ...
59
+
60
+
61
+ # =============================================================================
62
+ # Built-in Validators
63
+ # =============================================================================
64
+
65
+
66
+ class RequiredValidator(BaseValidator):
67
+ """Validates that a value is non-empty."""
68
+
69
+ def validate(
70
+ self,
71
+ value: str,
72
+ step: WorkflowStep,
73
+ context: dict[str, StepResponse],
74
+ ) -> ValidationResult:
75
+ """Check that value is not empty or whitespace-only."""
76
+ if not value or not value.strip():
77
+ return ValidationResult.failure(
78
+ error_message="This field is required",
79
+ suggestion="Please enter a value",
80
+ )
81
+ return ValidationResult.success()
82
+
83
+
84
+ class PathExistsValidator(BaseValidator):
85
+ """Validates that a file or directory path exists."""
86
+
87
+ def __init__(self, must_be_file: bool = False, must_be_dir: bool = False):
88
+ """Initialize the validator.
89
+
90
+ Args:
91
+ must_be_file: If True, path must be a file (not directory)
92
+ must_be_dir: If True, path must be a directory (not file)
93
+ """
94
+ self.must_be_file = must_be_file
95
+ self.must_be_dir = must_be_dir
96
+
97
+ def validate(
98
+ self,
99
+ value: str,
100
+ step: WorkflowStep,
101
+ context: dict[str, StepResponse],
102
+ ) -> ValidationResult:
103
+ """Check that the path exists."""
104
+ if not value:
105
+ return ValidationResult.failure(
106
+ error_message="Path is required",
107
+ suggestion="Enter a valid file or directory path",
108
+ )
109
+
110
+ # Expand ~ to home directory
111
+ path = Path(os.path.expanduser(value))
112
+
113
+ if not path.exists():
114
+ return ValidationResult.failure(
115
+ error_message=f"Path does not exist: {value}",
116
+ suggestion="Check the path and try again. Use absolute paths for reliability.",
117
+ )
118
+
119
+ if self.must_be_file and not path.is_file():
120
+ return ValidationResult.failure(
121
+ error_message=f"Path is not a file: {value}",
122
+ suggestion="Please provide a path to a file, not a directory",
123
+ )
124
+
125
+ if self.must_be_dir and not path.is_dir():
126
+ return ValidationResult.failure(
127
+ error_message=f"Path is not a directory: {value}",
128
+ suggestion="Please provide a path to a directory, not a file",
129
+ )
130
+
131
+ return ValidationResult.success()
132
+
133
+
134
+ class ChoiceValidator(BaseValidator):
135
+ """Validates that a value is one of the allowed options."""
136
+
137
+ def __init__(self, case_sensitive: bool = False):
138
+ """Initialize the validator.
139
+
140
+ Args:
141
+ case_sensitive: Whether to match case-sensitively
142
+ """
143
+ self.case_sensitive = case_sensitive
144
+
145
+ def validate(
146
+ self,
147
+ value: str,
148
+ step: WorkflowStep,
149
+ context: dict[str, StepResponse],
150
+ ) -> ValidationResult:
151
+ """Check that value is one of the step's options."""
152
+ if not step.options:
153
+ return ValidationResult.failure(
154
+ error_message="No options defined for this step",
155
+ suggestion="This step is misconfigured",
156
+ )
157
+
158
+ # Normalize input
159
+ normalized_value = value.strip()
160
+ if not self.case_sensitive:
161
+ normalized_value = normalized_value.lower()
162
+
163
+ # Check against options
164
+ for option_key in step.options:
165
+ compare_key = option_key if self.case_sensitive else option_key.lower()
166
+ if normalized_value == compare_key:
167
+ return ValidationResult.success()
168
+
169
+ # Build helpful error message
170
+ valid_options = ", ".join(step.options.keys())
171
+ return ValidationResult.failure(
172
+ error_message=f"Invalid choice: '{value}'",
173
+ suggestion=f"Valid options are: {valid_options}",
174
+ )
175
+
176
+
177
+ class PatternValidator(BaseValidator):
178
+ """Validates that a value matches a regex pattern."""
179
+
180
+ def __init__(self, pattern: str, description: str | None = None):
181
+ """Initialize the validator.
182
+
183
+ Args:
184
+ pattern: Regex pattern to match
185
+ description: Human-readable description of expected format
186
+ """
187
+ self.pattern = pattern
188
+ self.regex = re.compile(pattern)
189
+ self.description = description
190
+
191
+ def validate(
192
+ self,
193
+ value: str,
194
+ step: WorkflowStep,
195
+ context: dict[str, StepResponse],
196
+ ) -> ValidationResult:
197
+ """Check that value matches the pattern."""
198
+ if self.regex.match(value):
199
+ return ValidationResult.success()
200
+
201
+ suggestion = self.description or f"Value must match pattern: {self.pattern}"
202
+ return ValidationResult.failure(
203
+ error_message="Invalid format",
204
+ suggestion=suggestion,
205
+ )
206
+
207
+
208
+ # =============================================================================
209
+ # Validator Registry
210
+ # =============================================================================
211
+
212
+
213
+ _VALIDATORS: dict[str, type[BaseValidator]] = {
214
+ "RequiredValidator": RequiredValidator,
215
+ "PathExistsValidator": PathExistsValidator,
216
+ "ChoiceValidator": ChoiceValidator,
217
+ "PatternValidator": PatternValidator,
218
+ }
219
+
220
+
221
+ def register_validator(name: str, validator_class: type[BaseValidator]) -> None:
222
+ """Register a custom validator class.
223
+
224
+ Args:
225
+ name: Name to register the validator under
226
+ validator_class: The validator class to register
227
+ """
228
+ _VALIDATORS[name] = validator_class
229
+
230
+
231
+ def get_validator(name: str) -> BaseValidator | None:
232
+ """Get a validator instance by name.
233
+
234
+ Args:
235
+ name: Name of the validator type
236
+
237
+ Returns:
238
+ Validator instance or None if not found
239
+ """
240
+ validator_class = _VALIDATORS.get(name)
241
+ if validator_class:
242
+ return validator_class()
243
+ return None
244
+
245
+
246
+ def get_validator_class(name: str) -> type[BaseValidator] | None:
247
+ """Get a validator class by name.
248
+
249
+ Args:
250
+ name: Name of the validator type
251
+
252
+ Returns:
253
+ Validator class or None if not found
254
+ """
255
+ return _VALIDATORS.get(name)
256
+
257
+
258
+ # =============================================================================
259
+ # Utility Functions
260
+ # =============================================================================
261
+
262
+
263
+ def chain_validators(
264
+ validators: list[BaseValidator],
265
+ value: str,
266
+ step: WorkflowStep,
267
+ context: dict[str, StepResponse],
268
+ ) -> ValidationResult:
269
+ """Run multiple validators in sequence, stopping at first failure.
270
+
271
+ Args:
272
+ validators: List of validators to run
273
+ value: Value to validate
274
+ step: Current workflow step
275
+ context: Previous step responses
276
+
277
+ Returns:
278
+ First failing ValidationResult or success if all pass
279
+ """
280
+ for validator in validators:
281
+ result = validator.validate(value, step, context)
282
+ if not result.passed:
283
+ return result
284
+ return ValidationResult.success()
285
+
286
+
287
+ def validate_step(
288
+ value: str,
289
+ step: WorkflowStep,
290
+ context: dict[str, StepResponse],
291
+ ) -> ValidationResult:
292
+ """Validate a step input using the step's configured validator.
293
+
294
+ Args:
295
+ value: Value to validate
296
+ step: Workflow step with validation_type
297
+ context: Previous step responses
298
+
299
+ Returns:
300
+ ValidationResult from the step's validator
301
+ """
302
+ # Required validation for required steps
303
+ if step.required:
304
+ required_validator = RequiredValidator()
305
+ result = required_validator.validate(value, step, context)
306
+ if not result.passed:
307
+ return result
308
+
309
+ # Choice validation for steps with options
310
+ if step.options:
311
+ choice_validator = ChoiceValidator()
312
+ result = choice_validator.validate(value, step, context)
313
+ if not result.passed:
314
+ return result
315
+
316
+ # Custom validator if specified
317
+ if step.validation_type:
318
+ validator = get_validator(step.validation_type)
319
+ if validator:
320
+ return validator.validate(value, step, context)
321
+
322
+ return ValidationResult.success()