doit-toolkit-cli 0.1.10__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.
Potentially problematic release.
This version of doit-toolkit-cli might be problematic. Click here for more details.
- 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/roadmapit_command.py +10 -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 +51 -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 +1123 -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 +389 -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.10.dist-info/METADATA +324 -0
- doit_toolkit_cli-0.1.10.dist-info/RECORD +135 -0
- doit_toolkit_cli-0.1.10.dist-info/WHEEL +4 -0
- doit_toolkit_cli-0.1.10.dist-info/entry_points.txt +2 -0
- doit_toolkit_cli-0.1.10.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""JSON formatter for status output."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..models.status_models import SpecState, StatusReport
|
|
8
|
+
from .base import StatusFormatter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class JsonFormatter(StatusFormatter):
|
|
12
|
+
"""Formats status as JSON for machine consumption.
|
|
13
|
+
|
|
14
|
+
Produces structured JSON output suitable for parsing by other tools,
|
|
15
|
+
CI/CD systems, or programmatic analysis.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def format(self, report: StatusReport, verbose: bool = False) -> str:
|
|
19
|
+
"""Format the status report as JSON.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
report: The StatusReport to format.
|
|
23
|
+
verbose: Include detailed validation errors.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
JSON string representation.
|
|
27
|
+
"""
|
|
28
|
+
data = self._build_json_data(report, verbose)
|
|
29
|
+
return json.dumps(data, indent=2, default=self._json_serializer)
|
|
30
|
+
|
|
31
|
+
def _build_json_data(self, report: StatusReport, verbose: bool) -> dict[str, Any]:
|
|
32
|
+
"""Build the JSON data structure.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
report: The StatusReport to convert.
|
|
36
|
+
verbose: Include detailed validation errors.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Dictionary ready for JSON serialization.
|
|
40
|
+
"""
|
|
41
|
+
# Build summary
|
|
42
|
+
summary = {
|
|
43
|
+
"total": report.total_count,
|
|
44
|
+
"by_status": {
|
|
45
|
+
"draft": report.by_status.get(SpecState.DRAFT, 0),
|
|
46
|
+
"in_progress": report.by_status.get(SpecState.IN_PROGRESS, 0),
|
|
47
|
+
"complete": report.by_status.get(SpecState.COMPLETE, 0),
|
|
48
|
+
"approved": report.by_status.get(SpecState.APPROVED, 0),
|
|
49
|
+
},
|
|
50
|
+
"blocking": report.blocking_count,
|
|
51
|
+
"validation_pass": sum(
|
|
52
|
+
1 for s in report.specs if s.validation_passed
|
|
53
|
+
),
|
|
54
|
+
"validation_fail": sum(
|
|
55
|
+
1 for s in report.specs
|
|
56
|
+
if s.validation_result is not None and not s.validation_passed
|
|
57
|
+
),
|
|
58
|
+
"completion_percentage": round(report.completion_percentage, 2),
|
|
59
|
+
"ready_to_commit": report.is_ready_to_commit,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Build specs list
|
|
63
|
+
specs = []
|
|
64
|
+
for spec in report.specs:
|
|
65
|
+
spec_data = {
|
|
66
|
+
"name": spec.name,
|
|
67
|
+
"path": str(spec.path),
|
|
68
|
+
"status": spec.status.value,
|
|
69
|
+
"last_modified": spec.last_modified.isoformat(),
|
|
70
|
+
"is_blocking": spec.is_blocking,
|
|
71
|
+
"error": spec.error,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# Add validation info
|
|
75
|
+
if spec.validation_result is not None:
|
|
76
|
+
validation_data = {
|
|
77
|
+
"passed": spec.validation_passed,
|
|
78
|
+
"score": spec.validation_score,
|
|
79
|
+
"error_count": spec.validation_result.error_count,
|
|
80
|
+
"warning_count": spec.validation_result.warning_count,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if verbose and spec.validation_result.issues:
|
|
84
|
+
validation_data["issues"] = [
|
|
85
|
+
{
|
|
86
|
+
"rule_id": issue.rule_id,
|
|
87
|
+
"severity": issue.severity.value,
|
|
88
|
+
"line": issue.line_number,
|
|
89
|
+
"message": issue.message,
|
|
90
|
+
"suggestion": issue.suggestion,
|
|
91
|
+
}
|
|
92
|
+
for issue in spec.validation_result.issues
|
|
93
|
+
]
|
|
94
|
+
else:
|
|
95
|
+
validation_data["issues"] = []
|
|
96
|
+
|
|
97
|
+
spec_data["validation"] = validation_data
|
|
98
|
+
else:
|
|
99
|
+
spec_data["validation"] = None
|
|
100
|
+
|
|
101
|
+
specs.append(spec_data)
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
"generated_at": report.generated_at.isoformat(),
|
|
105
|
+
"project_root": str(report.project_root),
|
|
106
|
+
"summary": summary,
|
|
107
|
+
"specs": specs,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
def _json_serializer(self, obj: Any) -> Any:
|
|
111
|
+
"""Custom JSON serializer for non-standard types.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
obj: Object to serialize.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
JSON-serializable representation.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
TypeError: If object cannot be serialized.
|
|
121
|
+
"""
|
|
122
|
+
if isinstance(obj, datetime):
|
|
123
|
+
return obj.isoformat()
|
|
124
|
+
if hasattr(obj, "__dict__"):
|
|
125
|
+
return obj.__dict__
|
|
126
|
+
raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Markdown formatter for status output."""
|
|
2
|
+
|
|
3
|
+
from ..models.status_models import SpecState, StatusReport
|
|
4
|
+
from .base import StatusFormatter
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MarkdownFormatter(StatusFormatter):
|
|
8
|
+
"""Formats status as markdown table for documentation.
|
|
9
|
+
|
|
10
|
+
Produces GitHub-flavored markdown output suitable for embedding
|
|
11
|
+
in documentation, README files, or issue descriptions.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def format(self, report: StatusReport, verbose: bool = False) -> str:
|
|
15
|
+
"""Format the status report as markdown.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
report: The StatusReport to format.
|
|
19
|
+
verbose: Include detailed validation errors.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Markdown string representation.
|
|
23
|
+
"""
|
|
24
|
+
lines = []
|
|
25
|
+
|
|
26
|
+
# Header
|
|
27
|
+
lines.append("# Spec Status Report")
|
|
28
|
+
lines.append("")
|
|
29
|
+
lines.append(f"**Generated**: {report.generated_at.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
30
|
+
lines.append(f"**Project**: {report.project_root.name}")
|
|
31
|
+
lines.append("")
|
|
32
|
+
|
|
33
|
+
# Specifications table
|
|
34
|
+
lines.append("## Specifications")
|
|
35
|
+
lines.append("")
|
|
36
|
+
|
|
37
|
+
if report.specs:
|
|
38
|
+
lines.append("| Spec | Status | Validation | Last Modified |")
|
|
39
|
+
lines.append("|------|--------|------------|---------------|")
|
|
40
|
+
|
|
41
|
+
for spec in report.specs:
|
|
42
|
+
status_display = f"{spec.status.emoji} {spec.status.display_name}"
|
|
43
|
+
|
|
44
|
+
# Validation indicator
|
|
45
|
+
if spec.error:
|
|
46
|
+
validation = "❌ Error"
|
|
47
|
+
elif spec.validation_result is None:
|
|
48
|
+
validation = "—"
|
|
49
|
+
elif spec.validation_passed:
|
|
50
|
+
validation = "✅ Pass"
|
|
51
|
+
else:
|
|
52
|
+
validation = f"❌ Fail ({spec.validation_result.error_count})"
|
|
53
|
+
|
|
54
|
+
if spec.is_blocking:
|
|
55
|
+
validation += " ⛔"
|
|
56
|
+
|
|
57
|
+
modified = spec.last_modified.strftime("%Y-%m-%d")
|
|
58
|
+
|
|
59
|
+
lines.append(f"| {spec.name} | {status_display} | {validation} | {modified} |")
|
|
60
|
+
|
|
61
|
+
# Add verbose details
|
|
62
|
+
if verbose:
|
|
63
|
+
lines.append("")
|
|
64
|
+
lines.append("### Validation Details")
|
|
65
|
+
lines.append("")
|
|
66
|
+
for spec in report.specs:
|
|
67
|
+
if spec.validation_result and spec.validation_result.issues:
|
|
68
|
+
lines.append(f"#### {spec.name}")
|
|
69
|
+
lines.append("")
|
|
70
|
+
for issue in spec.validation_result.issues:
|
|
71
|
+
severity = issue.severity.value.upper()
|
|
72
|
+
lines.append(f"- **{severity}** (line {issue.line_number}): {issue.message}")
|
|
73
|
+
if issue.suggestion:
|
|
74
|
+
lines.append(f" - Suggestion: {issue.suggestion}")
|
|
75
|
+
lines.append("")
|
|
76
|
+
else:
|
|
77
|
+
lines.append("*No specifications found.*")
|
|
78
|
+
lines.append("")
|
|
79
|
+
|
|
80
|
+
# Summary
|
|
81
|
+
lines.append("## Summary")
|
|
82
|
+
lines.append("")
|
|
83
|
+
lines.append(f"- **Total Specs**: {report.total_count}")
|
|
84
|
+
lines.append(f"- **Draft**: {report.by_status.get(SpecState.DRAFT, 0)}")
|
|
85
|
+
lines.append(f"- **In Progress**: {report.by_status.get(SpecState.IN_PROGRESS, 0)}")
|
|
86
|
+
lines.append(f"- **Complete**: {report.by_status.get(SpecState.COMPLETE, 0)}")
|
|
87
|
+
lines.append(f"- **Approved**: {report.by_status.get(SpecState.APPROVED, 0)}")
|
|
88
|
+
lines.append(f"- **Completion**: {report.completion_percentage:.0f}%")
|
|
89
|
+
|
|
90
|
+
if report.is_ready_to_commit:
|
|
91
|
+
lines.append("- **Status**: ✅ Ready to commit")
|
|
92
|
+
else:
|
|
93
|
+
lines.append(f"- **Status**: ⛔ {report.blocking_count} spec(s) blocking")
|
|
94
|
+
|
|
95
|
+
lines.append("")
|
|
96
|
+
|
|
97
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""Rich terminal formatter for status output."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from ..models.status_models import SpecState, SpecStatus, StatusReport
|
|
11
|
+
from .base import StatusFormatter
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RichFormatter(StatusFormatter):
|
|
15
|
+
"""Formats status as rich terminal output with colors and tables.
|
|
16
|
+
|
|
17
|
+
Uses the Rich library to produce attractive terminal output with:
|
|
18
|
+
- Colored status indicators
|
|
19
|
+
- Formatted tables
|
|
20
|
+
- Summary panels
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, console: Optional[Console] = None) -> None:
|
|
24
|
+
"""Initialize formatter with optional console.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
console: Rich Console instance. Creates new one if not provided.
|
|
28
|
+
"""
|
|
29
|
+
self.console = console or Console()
|
|
30
|
+
|
|
31
|
+
def format(self, report: StatusReport, verbose: bool = False) -> str:
|
|
32
|
+
"""Format the status report as a string.
|
|
33
|
+
|
|
34
|
+
Note: For rich terminal output, use format_to_console() instead.
|
|
35
|
+
This method captures the output as a string.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
report: The StatusReport to format.
|
|
39
|
+
verbose: Include detailed validation errors.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Formatted string representation (with ANSI codes).
|
|
43
|
+
"""
|
|
44
|
+
# Capture output to string
|
|
45
|
+
from io import StringIO
|
|
46
|
+
|
|
47
|
+
string_io = StringIO()
|
|
48
|
+
temp_console = Console(file=string_io, force_terminal=True)
|
|
49
|
+
|
|
50
|
+
self._render_to_console(report, verbose, temp_console)
|
|
51
|
+
|
|
52
|
+
return string_io.getvalue()
|
|
53
|
+
|
|
54
|
+
def format_to_console(self, report: StatusReport, verbose: bool = False) -> None:
|
|
55
|
+
"""Format and print directly to console with rich formatting.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
report: The StatusReport to format.
|
|
59
|
+
verbose: Include detailed validation errors.
|
|
60
|
+
"""
|
|
61
|
+
self._render_to_console(report, verbose, self.console)
|
|
62
|
+
|
|
63
|
+
def _render_to_console(
|
|
64
|
+
self, report: StatusReport, verbose: bool, console: Console
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Internal method to render report to a console.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
report: The StatusReport to format.
|
|
70
|
+
verbose: Include detailed validation errors.
|
|
71
|
+
console: Console to render to.
|
|
72
|
+
"""
|
|
73
|
+
# Header panel
|
|
74
|
+
project_name = report.project_root.name
|
|
75
|
+
generated = report.generated_at.strftime("%Y-%m-%d %H:%M:%S")
|
|
76
|
+
|
|
77
|
+
header = f"[bold]📊 Project:[/bold] {project_name}\n"
|
|
78
|
+
header += f"[bold]📅 Generated:[/bold] {generated}"
|
|
79
|
+
|
|
80
|
+
console.print(
|
|
81
|
+
Panel(header, title="Spec Status Dashboard", border_style="blue")
|
|
82
|
+
)
|
|
83
|
+
console.print()
|
|
84
|
+
|
|
85
|
+
# Specs table
|
|
86
|
+
if report.specs:
|
|
87
|
+
table = self._create_specs_table(report, verbose)
|
|
88
|
+
console.print(table)
|
|
89
|
+
console.print()
|
|
90
|
+
|
|
91
|
+
# Summary panel
|
|
92
|
+
summary = self._create_summary_panel(report)
|
|
93
|
+
console.print(summary)
|
|
94
|
+
else:
|
|
95
|
+
console.print(
|
|
96
|
+
"[yellow]No specifications found.[/yellow] "
|
|
97
|
+
"Run 'doit specit' to create one."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def _create_specs_table(
|
|
101
|
+
self, report: StatusReport, verbose: bool
|
|
102
|
+
) -> Table:
|
|
103
|
+
"""Create the main specs table.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
report: The StatusReport containing specs.
|
|
107
|
+
verbose: Include validation error details.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Rich Table with spec information.
|
|
111
|
+
"""
|
|
112
|
+
table = Table(show_header=True, header_style="bold")
|
|
113
|
+
|
|
114
|
+
# Columns
|
|
115
|
+
table.add_column("Spec", style="cyan", no_wrap=True)
|
|
116
|
+
table.add_column("Status", justify="center")
|
|
117
|
+
table.add_column("Validation", justify="center")
|
|
118
|
+
table.add_column("Modified", justify="right")
|
|
119
|
+
|
|
120
|
+
if verbose:
|
|
121
|
+
table.add_column("Details", style="dim")
|
|
122
|
+
|
|
123
|
+
for spec in report.specs:
|
|
124
|
+
# Status with emoji
|
|
125
|
+
status_display = f"{spec.status.emoji} {spec.status.display_name}"
|
|
126
|
+
status_style = self._get_status_style(spec.status)
|
|
127
|
+
|
|
128
|
+
# Validation indicator
|
|
129
|
+
if spec.error:
|
|
130
|
+
validation_display = "[red]❌ Error[/red]"
|
|
131
|
+
elif spec.validation_result is None:
|
|
132
|
+
validation_display = "[dim]—[/dim]"
|
|
133
|
+
elif spec.validation_passed:
|
|
134
|
+
validation_display = "[green]✅ Pass[/green]"
|
|
135
|
+
else:
|
|
136
|
+
validation_display = f"[red]❌ Fail ({spec.validation_result.error_count})[/red]"
|
|
137
|
+
|
|
138
|
+
# Blocking indicator
|
|
139
|
+
if spec.is_blocking:
|
|
140
|
+
validation_display += " [bold red]⛔[/bold red]"
|
|
141
|
+
|
|
142
|
+
# Modified date (relative)
|
|
143
|
+
modified = self._format_relative_date(spec.last_modified)
|
|
144
|
+
|
|
145
|
+
# Build row
|
|
146
|
+
row = [
|
|
147
|
+
spec.name,
|
|
148
|
+
f"[{status_style}]{status_display}[/{status_style}]",
|
|
149
|
+
validation_display,
|
|
150
|
+
modified,
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
if verbose:
|
|
154
|
+
details = self._get_spec_details(spec)
|
|
155
|
+
row.append(details)
|
|
156
|
+
|
|
157
|
+
table.add_row(*row)
|
|
158
|
+
|
|
159
|
+
return table
|
|
160
|
+
|
|
161
|
+
def _create_summary_panel(self, report: StatusReport) -> Panel:
|
|
162
|
+
"""Create the summary panel with statistics.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
report: The StatusReport to summarize.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Rich Panel with summary statistics.
|
|
169
|
+
"""
|
|
170
|
+
# Status counts
|
|
171
|
+
counts = report.by_status
|
|
172
|
+
status_line = (
|
|
173
|
+
f"[dim]Draft:[/dim] {counts.get(SpecState.DRAFT, 0)} │ "
|
|
174
|
+
f"[dim]In Progress:[/dim] {counts.get(SpecState.IN_PROGRESS, 0)} │ "
|
|
175
|
+
f"[dim]Complete:[/dim] {counts.get(SpecState.COMPLETE, 0)} │ "
|
|
176
|
+
f"[dim]Approved:[/dim] {counts.get(SpecState.APPROVED, 0)}"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Completion percentage
|
|
180
|
+
completion = f"[bold]Completion:[/bold] {report.completion_percentage:.0f}%"
|
|
181
|
+
|
|
182
|
+
# Ready to commit indicator
|
|
183
|
+
if report.is_ready_to_commit:
|
|
184
|
+
ready = "[green]✅ Ready to commit[/green]"
|
|
185
|
+
else:
|
|
186
|
+
ready = f"[red]⛔ {report.blocking_count} spec(s) blocking[/red]"
|
|
187
|
+
|
|
188
|
+
summary = f"{status_line}\n{completion} │ {ready}"
|
|
189
|
+
|
|
190
|
+
return Panel(summary, title="Summary", border_style="green" if report.is_ready_to_commit else "red")
|
|
191
|
+
|
|
192
|
+
def _get_status_style(self, status: SpecState) -> str:
|
|
193
|
+
"""Get Rich style for a status.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
status: The SpecState to style.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Rich style string.
|
|
200
|
+
"""
|
|
201
|
+
styles = {
|
|
202
|
+
SpecState.DRAFT: "dim",
|
|
203
|
+
SpecState.IN_PROGRESS: "yellow",
|
|
204
|
+
SpecState.COMPLETE: "green",
|
|
205
|
+
SpecState.APPROVED: "bold green",
|
|
206
|
+
SpecState.ERROR: "red",
|
|
207
|
+
}
|
|
208
|
+
return styles.get(status, "white")
|
|
209
|
+
|
|
210
|
+
def _format_relative_date(self, dt: datetime) -> str:
|
|
211
|
+
"""Format a datetime as relative time (Today, Yesterday, etc.).
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
dt: Datetime to format.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Human-readable relative date string.
|
|
218
|
+
"""
|
|
219
|
+
now = datetime.now()
|
|
220
|
+
diff = now - dt
|
|
221
|
+
|
|
222
|
+
if diff.days == 0:
|
|
223
|
+
return "Today"
|
|
224
|
+
elif diff.days == 1:
|
|
225
|
+
return "Yesterday"
|
|
226
|
+
elif diff.days < 7:
|
|
227
|
+
return f"{diff.days} days ago"
|
|
228
|
+
else:
|
|
229
|
+
return dt.strftime("%Y-%m-%d")
|
|
230
|
+
|
|
231
|
+
def _get_spec_details(self, spec: SpecStatus) -> str:
|
|
232
|
+
"""Get detailed information about a spec for verbose mode.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
spec: The SpecStatus to describe.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Details string with validation errors if any.
|
|
239
|
+
"""
|
|
240
|
+
if spec.error:
|
|
241
|
+
return spec.error
|
|
242
|
+
|
|
243
|
+
if spec.validation_result is None:
|
|
244
|
+
return "Not validated"
|
|
245
|
+
|
|
246
|
+
if spec.validation_passed:
|
|
247
|
+
return f"Score: {spec.validation_score}"
|
|
248
|
+
|
|
249
|
+
# Show validation errors
|
|
250
|
+
errors = []
|
|
251
|
+
for issue in spec.validation_result.issues[:3]: # Limit to 3
|
|
252
|
+
errors.append(f"• {issue.message}")
|
|
253
|
+
|
|
254
|
+
if len(spec.validation_result.issues) > 3:
|
|
255
|
+
errors.append(f"• ... and {len(spec.validation_result.issues) - 3} more")
|
|
256
|
+
|
|
257
|
+
return "\n".join(errors)
|
doit_cli/main.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Main CLI application for doit-cli new commands (init, verify)."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from .cli.analytics_command import app as analytics_app
|
|
6
|
+
from .cli.context_command import context_app
|
|
7
|
+
from .cli.diagram_command import diagram_app
|
|
8
|
+
from .cli.fixit_command import app as fixit_app
|
|
9
|
+
from .cli.hooks_command import hooks_app
|
|
10
|
+
from .cli.init_command import init_command
|
|
11
|
+
from .cli.memory_command import memory_app
|
|
12
|
+
from .cli.roadmapit_command import app as roadmapit_app
|
|
13
|
+
from .cli.status_command import status_command
|
|
14
|
+
from .cli.sync_prompts_command import sync_prompts_command
|
|
15
|
+
from .cli.validate_command import validate_command
|
|
16
|
+
from .cli.verify_command import verify_command
|
|
17
|
+
from .cli.xref_command import xref_app
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Create a new typer app for the refactored commands
|
|
21
|
+
app = typer.Typer(
|
|
22
|
+
name="doit",
|
|
23
|
+
help="Doit CLI - Setup tool for Doit spec-driven development projects",
|
|
24
|
+
add_completion=False,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Register commands
|
|
28
|
+
app.command(name="init")(init_command)
|
|
29
|
+
app.command(name="status")(status_command)
|
|
30
|
+
app.command(name="sync-prompts")(sync_prompts_command)
|
|
31
|
+
app.command(name="validate")(validate_command)
|
|
32
|
+
app.command(name="verify")(verify_command)
|
|
33
|
+
|
|
34
|
+
# Register subcommand groups
|
|
35
|
+
app.add_typer(analytics_app, name="analytics")
|
|
36
|
+
app.add_typer(context_app, name="context")
|
|
37
|
+
app.add_typer(diagram_app, name="diagram")
|
|
38
|
+
app.add_typer(fixit_app, name="fixit")
|
|
39
|
+
app.add_typer(hooks_app, name="hooks")
|
|
40
|
+
app.add_typer(memory_app, name="memory")
|
|
41
|
+
app.add_typer(roadmapit_app, name="roadmapit")
|
|
42
|
+
app.add_typer(xref_app, name="xref")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def main():
|
|
46
|
+
"""Main entry point for the CLI."""
|
|
47
|
+
app()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
main()
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Data models for doit-cli."""
|
|
2
|
+
|
|
3
|
+
from .agent import Agent
|
|
4
|
+
from .project import Project
|
|
5
|
+
from .template import Template
|
|
6
|
+
from .results import InitResult, VerifyResult, VerifyCheck, VerifyStatus
|
|
7
|
+
from .context_config import (
|
|
8
|
+
ContextConfig,
|
|
9
|
+
SourceConfig,
|
|
10
|
+
CommandOverride,
|
|
11
|
+
ContextSource,
|
|
12
|
+
LoadedContext,
|
|
13
|
+
)
|
|
14
|
+
from .validation_models import (
|
|
15
|
+
Severity,
|
|
16
|
+
ValidationStatus,
|
|
17
|
+
ValidationRule,
|
|
18
|
+
ValidationIssue,
|
|
19
|
+
ValidationResult,
|
|
20
|
+
RuleOverride,
|
|
21
|
+
CustomRule,
|
|
22
|
+
ValidationConfig,
|
|
23
|
+
)
|
|
24
|
+
from .workflow_models import (
|
|
25
|
+
WorkflowStatus,
|
|
26
|
+
WorkflowStep,
|
|
27
|
+
Workflow,
|
|
28
|
+
StepResponse,
|
|
29
|
+
WorkflowState,
|
|
30
|
+
ValidationResult as WorkflowValidationResult,
|
|
31
|
+
WorkflowError,
|
|
32
|
+
ValidationError as WorkflowValidationError,
|
|
33
|
+
NavigationCommand,
|
|
34
|
+
StateCorruptionError,
|
|
35
|
+
)
|
|
36
|
+
from .status_models import (
|
|
37
|
+
SpecState,
|
|
38
|
+
SpecStatus,
|
|
39
|
+
StatusReport,
|
|
40
|
+
)
|
|
41
|
+
from .crossref_models import (
|
|
42
|
+
CoverageStatus,
|
|
43
|
+
Requirement,
|
|
44
|
+
TaskReference,
|
|
45
|
+
Task,
|
|
46
|
+
CrossReference,
|
|
47
|
+
RequirementCoverage,
|
|
48
|
+
CoverageReport,
|
|
49
|
+
)
|
|
50
|
+
from .diagram_models import (
|
|
51
|
+
DiagramType,
|
|
52
|
+
Cardinality,
|
|
53
|
+
AcceptanceScenario,
|
|
54
|
+
ParsedUserStory,
|
|
55
|
+
EntityAttribute,
|
|
56
|
+
EntityRelationship,
|
|
57
|
+
ParsedEntity,
|
|
58
|
+
DiagramSection,
|
|
59
|
+
ValidationResult as DiagramValidationResult,
|
|
60
|
+
GeneratedDiagram,
|
|
61
|
+
DiagramResult,
|
|
62
|
+
)
|
|
63
|
+
from .search_models import (
|
|
64
|
+
QueryType,
|
|
65
|
+
SourceType,
|
|
66
|
+
SourceFilter,
|
|
67
|
+
SearchQuery,
|
|
68
|
+
SearchResult,
|
|
69
|
+
MemorySource,
|
|
70
|
+
ContentSnippet,
|
|
71
|
+
SearchHistory,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
__all__ = [
|
|
75
|
+
"Agent",
|
|
76
|
+
"Project",
|
|
77
|
+
"Template",
|
|
78
|
+
"InitResult",
|
|
79
|
+
"VerifyResult",
|
|
80
|
+
"VerifyCheck",
|
|
81
|
+
"VerifyStatus",
|
|
82
|
+
"ContextConfig",
|
|
83
|
+
"SourceConfig",
|
|
84
|
+
"CommandOverride",
|
|
85
|
+
"ContextSource",
|
|
86
|
+
"LoadedContext",
|
|
87
|
+
"Severity",
|
|
88
|
+
"ValidationStatus",
|
|
89
|
+
"ValidationRule",
|
|
90
|
+
"ValidationIssue",
|
|
91
|
+
"ValidationResult",
|
|
92
|
+
"RuleOverride",
|
|
93
|
+
"CustomRule",
|
|
94
|
+
"ValidationConfig",
|
|
95
|
+
# Workflow models
|
|
96
|
+
"WorkflowStatus",
|
|
97
|
+
"WorkflowStep",
|
|
98
|
+
"Workflow",
|
|
99
|
+
"StepResponse",
|
|
100
|
+
"WorkflowState",
|
|
101
|
+
"WorkflowValidationResult",
|
|
102
|
+
"WorkflowError",
|
|
103
|
+
"WorkflowValidationError",
|
|
104
|
+
"NavigationCommand",
|
|
105
|
+
"StateCorruptionError",
|
|
106
|
+
# Status models
|
|
107
|
+
"SpecState",
|
|
108
|
+
"SpecStatus",
|
|
109
|
+
"StatusReport",
|
|
110
|
+
# Cross-reference models
|
|
111
|
+
"CoverageStatus",
|
|
112
|
+
"Requirement",
|
|
113
|
+
"TaskReference",
|
|
114
|
+
"Task",
|
|
115
|
+
"CrossReference",
|
|
116
|
+
"RequirementCoverage",
|
|
117
|
+
"CoverageReport",
|
|
118
|
+
# Diagram models
|
|
119
|
+
"DiagramType",
|
|
120
|
+
"Cardinality",
|
|
121
|
+
"AcceptanceScenario",
|
|
122
|
+
"ParsedUserStory",
|
|
123
|
+
"EntityAttribute",
|
|
124
|
+
"EntityRelationship",
|
|
125
|
+
"ParsedEntity",
|
|
126
|
+
"DiagramSection",
|
|
127
|
+
"DiagramValidationResult",
|
|
128
|
+
"GeneratedDiagram",
|
|
129
|
+
"DiagramResult",
|
|
130
|
+
# Search models
|
|
131
|
+
"QueryType",
|
|
132
|
+
"SourceType",
|
|
133
|
+
"SourceFilter",
|
|
134
|
+
"SearchQuery",
|
|
135
|
+
"SearchResult",
|
|
136
|
+
"MemorySource",
|
|
137
|
+
"ContentSnippet",
|
|
138
|
+
"SearchHistory",
|
|
139
|
+
]
|