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.
- doit_cli/__init__.py +1356 -0
- doit_cli/cli/__init__.py +26 -0
- doit_cli/cli/analytics_command.py +616 -0
- doit_cli/cli/context_command.py +213 -0
- doit_cli/cli/diagram_command.py +304 -0
- doit_cli/cli/fixit_command.py +641 -0
- doit_cli/cli/hooks_command.py +211 -0
- doit_cli/cli/init_command.py +613 -0
- doit_cli/cli/memory_command.py +293 -0
- doit_cli/cli/status_command.py +117 -0
- doit_cli/cli/sync_prompts_command.py +248 -0
- doit_cli/cli/validate_command.py +196 -0
- doit_cli/cli/verify_command.py +204 -0
- doit_cli/cli/workflow_mixin.py +224 -0
- doit_cli/cli/xref_command.py +555 -0
- doit_cli/formatters/__init__.py +8 -0
- doit_cli/formatters/base.py +38 -0
- doit_cli/formatters/json_formatter.py +126 -0
- doit_cli/formatters/markdown_formatter.py +97 -0
- doit_cli/formatters/rich_formatter.py +257 -0
- doit_cli/main.py +49 -0
- doit_cli/models/__init__.py +139 -0
- doit_cli/models/agent.py +74 -0
- doit_cli/models/analytics_models.py +384 -0
- doit_cli/models/context_config.py +464 -0
- doit_cli/models/crossref_models.py +182 -0
- doit_cli/models/diagram_models.py +363 -0
- doit_cli/models/fixit_models.py +355 -0
- doit_cli/models/hook_config.py +125 -0
- doit_cli/models/project.py +91 -0
- doit_cli/models/results.py +121 -0
- doit_cli/models/search_models.py +228 -0
- doit_cli/models/status_models.py +195 -0
- doit_cli/models/sync_models.py +146 -0
- doit_cli/models/template.py +77 -0
- doit_cli/models/validation_models.py +175 -0
- doit_cli/models/workflow_models.py +319 -0
- doit_cli/prompts/__init__.py +5 -0
- doit_cli/prompts/fixit_prompts.py +344 -0
- doit_cli/prompts/interactive.py +390 -0
- doit_cli/rules/__init__.py +5 -0
- doit_cli/rules/builtin_rules.py +160 -0
- doit_cli/services/__init__.py +79 -0
- doit_cli/services/agent_detector.py +168 -0
- doit_cli/services/analytics_service.py +218 -0
- doit_cli/services/architecture_generator.py +290 -0
- doit_cli/services/backup_service.py +204 -0
- doit_cli/services/config_loader.py +113 -0
- doit_cli/services/context_loader.py +1121 -0
- doit_cli/services/coverage_calculator.py +142 -0
- doit_cli/services/crossref_service.py +237 -0
- doit_cli/services/cycle_time_calculator.py +134 -0
- doit_cli/services/date_inferrer.py +349 -0
- doit_cli/services/diagram_service.py +337 -0
- doit_cli/services/drift_detector.py +109 -0
- doit_cli/services/entity_parser.py +301 -0
- doit_cli/services/er_diagram_generator.py +197 -0
- doit_cli/services/fixit_service.py +699 -0
- doit_cli/services/github_service.py +192 -0
- doit_cli/services/hook_manager.py +258 -0
- doit_cli/services/hook_validator.py +528 -0
- doit_cli/services/input_validator.py +322 -0
- doit_cli/services/memory_search.py +527 -0
- doit_cli/services/mermaid_validator.py +334 -0
- doit_cli/services/prompt_transformer.py +91 -0
- doit_cli/services/prompt_writer.py +133 -0
- doit_cli/services/query_interpreter.py +428 -0
- doit_cli/services/report_exporter.py +219 -0
- doit_cli/services/report_generator.py +256 -0
- doit_cli/services/requirement_parser.py +112 -0
- doit_cli/services/roadmap_summarizer.py +209 -0
- doit_cli/services/rule_engine.py +443 -0
- doit_cli/services/scaffolder.py +215 -0
- doit_cli/services/score_calculator.py +172 -0
- doit_cli/services/section_parser.py +204 -0
- doit_cli/services/spec_scanner.py +327 -0
- doit_cli/services/state_manager.py +355 -0
- doit_cli/services/status_reporter.py +143 -0
- doit_cli/services/task_parser.py +347 -0
- doit_cli/services/template_manager.py +710 -0
- doit_cli/services/template_reader.py +158 -0
- doit_cli/services/user_journey_generator.py +214 -0
- doit_cli/services/user_story_parser.py +232 -0
- doit_cli/services/validation_service.py +188 -0
- doit_cli/services/validator.py +232 -0
- doit_cli/services/velocity_tracker.py +173 -0
- doit_cli/services/workflow_engine.py +405 -0
- doit_cli/templates/agent-file-template.md +28 -0
- doit_cli/templates/checklist-template.md +39 -0
- doit_cli/templates/commands/doit.checkin.md +363 -0
- doit_cli/templates/commands/doit.constitution.md +187 -0
- doit_cli/templates/commands/doit.documentit.md +485 -0
- doit_cli/templates/commands/doit.fixit.md +181 -0
- doit_cli/templates/commands/doit.implementit.md +265 -0
- doit_cli/templates/commands/doit.planit.md +262 -0
- doit_cli/templates/commands/doit.reviewit.md +355 -0
- doit_cli/templates/commands/doit.roadmapit.md +368 -0
- doit_cli/templates/commands/doit.scaffoldit.md +458 -0
- doit_cli/templates/commands/doit.specit.md +521 -0
- doit_cli/templates/commands/doit.taskit.md +304 -0
- doit_cli/templates/commands/doit.testit.md +277 -0
- doit_cli/templates/config/context.yaml +134 -0
- doit_cli/templates/config/hooks.yaml +93 -0
- doit_cli/templates/config/validation-rules.yaml +64 -0
- doit_cli/templates/github-issue-templates/epic.yml +78 -0
- doit_cli/templates/github-issue-templates/feature.yml +116 -0
- doit_cli/templates/github-issue-templates/task.yml +129 -0
- doit_cli/templates/hooks/.gitkeep +0 -0
- doit_cli/templates/hooks/post-commit.sh +25 -0
- doit_cli/templates/hooks/post-merge.sh +75 -0
- doit_cli/templates/hooks/pre-commit.sh +17 -0
- doit_cli/templates/hooks/pre-push.sh +18 -0
- doit_cli/templates/memory/completed_roadmap.md +50 -0
- doit_cli/templates/memory/constitution.md +125 -0
- doit_cli/templates/memory/roadmap.md +61 -0
- doit_cli/templates/plan-template.md +146 -0
- doit_cli/templates/scripts/bash/check-prerequisites.sh +166 -0
- doit_cli/templates/scripts/bash/common.sh +156 -0
- doit_cli/templates/scripts/bash/create-new-feature.sh +297 -0
- doit_cli/templates/scripts/bash/setup-plan.sh +61 -0
- doit_cli/templates/scripts/bash/update-agent-context.sh +675 -0
- doit_cli/templates/scripts/powershell/check-prerequisites.ps1 +148 -0
- doit_cli/templates/scripts/powershell/common.ps1 +137 -0
- doit_cli/templates/scripts/powershell/create-new-feature.ps1 +283 -0
- doit_cli/templates/scripts/powershell/setup-plan.ps1 +61 -0
- doit_cli/templates/scripts/powershell/update-agent-context.ps1 +406 -0
- doit_cli/templates/spec-template.md +159 -0
- doit_cli/templates/tasks-template.md +313 -0
- doit_cli/templates/vscode-settings.json +14 -0
- doit_toolkit_cli-0.1.9.dist-info/METADATA +324 -0
- doit_toolkit_cli-0.1.9.dist-info/RECORD +134 -0
- doit_toolkit_cli-0.1.9.dist-info/WHEEL +4 -0
- doit_toolkit_cli-0.1.9.dist-info/entry_points.txt +2 -0
- 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()
|