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,641 @@
|
|
|
1
|
+
"""CLI command for bug-fix workflow.
|
|
2
|
+
|
|
3
|
+
This module provides the fixit command and subcommands
|
|
4
|
+
for the doit CLI tool.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
|
|
14
|
+
from ..models.fixit_models import FixPhase
|
|
15
|
+
from ..prompts.fixit_prompts import (
|
|
16
|
+
display_workflow_started,
|
|
17
|
+
display_workflow_status,
|
|
18
|
+
prompt_select_issue,
|
|
19
|
+
)
|
|
20
|
+
from ..services.fixit_service import FixitService, FixitServiceError
|
|
21
|
+
from ..services.github_service import GitHubServiceError
|
|
22
|
+
|
|
23
|
+
app = typer.Typer(help="Bug-fix workflow commands")
|
|
24
|
+
console = Console()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.callback()
|
|
28
|
+
def main() -> None:
|
|
29
|
+
"""Bug-fix workflow commands.
|
|
30
|
+
|
|
31
|
+
Use subcommands to manage bug-fix workflows:
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
doit fixit start 123 # Start workflow for issue #123
|
|
35
|
+
doit fixit start # Select from open bugs interactively
|
|
36
|
+
doit fixit list # List all open bugs
|
|
37
|
+
doit fixit status # Show current workflow status
|
|
38
|
+
"""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.command("start")
|
|
43
|
+
def start(
|
|
44
|
+
issue_id: Optional[int] = typer.Argument(
|
|
45
|
+
None,
|
|
46
|
+
help="GitHub issue number to fix",
|
|
47
|
+
),
|
|
48
|
+
resume: bool = typer.Option(
|
|
49
|
+
False, "--resume", "-r", help="Resume existing workflow"
|
|
50
|
+
),
|
|
51
|
+
manual: bool = typer.Option(
|
|
52
|
+
False, "--manual", "-m", help="Skip automatic investigation"
|
|
53
|
+
),
|
|
54
|
+
branch: Optional[str] = typer.Option(
|
|
55
|
+
None, "--branch", "-b", help="Custom branch name"
|
|
56
|
+
),
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Start or continue a bug-fix workflow.
|
|
59
|
+
|
|
60
|
+
If no ISSUE_ID is provided, shows a list of open bugs to select from.
|
|
61
|
+
If a workflow already exists for the issue, use --resume to continue it.
|
|
62
|
+
|
|
63
|
+
Examples:
|
|
64
|
+
doit fixit start 123 # Start workflow for issue #123
|
|
65
|
+
doit fixit start # Select from open bugs
|
|
66
|
+
doit fixit start 123 --resume # Resume existing workflow
|
|
67
|
+
"""
|
|
68
|
+
try:
|
|
69
|
+
service = FixitService()
|
|
70
|
+
except GitHubServiceError as e:
|
|
71
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
72
|
+
raise typer.Exit(code=2)
|
|
73
|
+
|
|
74
|
+
# Check GitHub availability
|
|
75
|
+
if not service.is_github_available():
|
|
76
|
+
console.print(
|
|
77
|
+
"[yellow]Warning:[/yellow] GitHub API is not available. "
|
|
78
|
+
"Some features may not work."
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# If no issue_id, prompt for selection
|
|
82
|
+
if issue_id is None:
|
|
83
|
+
bugs = service.list_bugs()
|
|
84
|
+
if not bugs:
|
|
85
|
+
console.print("[yellow]No open bugs found.[/yellow]")
|
|
86
|
+
console.print("Use [cyan]doit fixit start <issue_id>[/cyan] to start a workflow.")
|
|
87
|
+
raise typer.Exit(code=0)
|
|
88
|
+
|
|
89
|
+
selected = prompt_select_issue(bugs)
|
|
90
|
+
if selected is None:
|
|
91
|
+
console.print("[yellow]No issue selected.[/yellow]")
|
|
92
|
+
raise typer.Exit(code=0)
|
|
93
|
+
|
|
94
|
+
issue_id = selected.number
|
|
95
|
+
|
|
96
|
+
# Start or resume workflow
|
|
97
|
+
try:
|
|
98
|
+
workflow = service.start_workflow(
|
|
99
|
+
issue_id=issue_id,
|
|
100
|
+
resume=resume,
|
|
101
|
+
manual_branch=branch,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if resume:
|
|
105
|
+
console.print(
|
|
106
|
+
f"[green]Resumed[/green] workflow for issue [cyan]#{issue_id}[/cyan]"
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
display_workflow_started(workflow, issue_id)
|
|
110
|
+
|
|
111
|
+
# Show next steps based on phase
|
|
112
|
+
_show_next_steps(workflow.phase, manual)
|
|
113
|
+
|
|
114
|
+
except FixitServiceError as e:
|
|
115
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
116
|
+
raise typer.Exit(code=1)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@app.command("list")
|
|
120
|
+
def list_bugs(
|
|
121
|
+
label: str = typer.Option(
|
|
122
|
+
"bug", "--label", "-l", help="Label to filter by"
|
|
123
|
+
),
|
|
124
|
+
limit: int = typer.Option(
|
|
125
|
+
20, "--limit", "-n", help="Maximum number of bugs to show"
|
|
126
|
+
),
|
|
127
|
+
output_format: str = typer.Option(
|
|
128
|
+
"table", "--format", "-f", help="Output format: table, json"
|
|
129
|
+
),
|
|
130
|
+
) -> None:
|
|
131
|
+
"""List open bugs from GitHub.
|
|
132
|
+
|
|
133
|
+
Shows open issues with the specified label (default: bug).
|
|
134
|
+
|
|
135
|
+
Examples:
|
|
136
|
+
doit fixit list # List bugs
|
|
137
|
+
doit fixit list --label high # List high priority issues
|
|
138
|
+
doit fixit list --limit 10 # Show only 10 bugs
|
|
139
|
+
"""
|
|
140
|
+
try:
|
|
141
|
+
service = FixitService()
|
|
142
|
+
except GitHubServiceError as e:
|
|
143
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
144
|
+
raise typer.Exit(code=2)
|
|
145
|
+
|
|
146
|
+
bugs = service.list_bugs(label=label, limit=limit)
|
|
147
|
+
|
|
148
|
+
if not bugs:
|
|
149
|
+
console.print(f"[yellow]No open issues with label '{label}' found.[/yellow]")
|
|
150
|
+
raise typer.Exit(code=0)
|
|
151
|
+
|
|
152
|
+
if output_format == "json":
|
|
153
|
+
import json
|
|
154
|
+
data = [bug.to_dict() for bug in bugs]
|
|
155
|
+
console.print(json.dumps(data, indent=2))
|
|
156
|
+
else:
|
|
157
|
+
table = Table(title=f"Open Issues ({label})")
|
|
158
|
+
table.add_column("#", style="cyan", width=6)
|
|
159
|
+
table.add_column("Title", style="white")
|
|
160
|
+
table.add_column("Labels", style="dim")
|
|
161
|
+
|
|
162
|
+
for bug in bugs:
|
|
163
|
+
labels_str = ", ".join(bug.labels) if bug.labels else ""
|
|
164
|
+
table.add_row(str(bug.number), bug.title, labels_str)
|
|
165
|
+
|
|
166
|
+
console.print(table)
|
|
167
|
+
|
|
168
|
+
console.print(f"\nUse [cyan]doit fixit <issue_id>[/cyan] to start a workflow.")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@app.command("status")
|
|
172
|
+
def status(
|
|
173
|
+
issue_id: Optional[int] = typer.Argument(
|
|
174
|
+
None,
|
|
175
|
+
help="GitHub issue number (or shows active workflow)",
|
|
176
|
+
),
|
|
177
|
+
) -> None:
|
|
178
|
+
"""Show status of a fixit workflow.
|
|
179
|
+
|
|
180
|
+
If no issue_id is provided, shows the currently active workflow.
|
|
181
|
+
|
|
182
|
+
Examples:
|
|
183
|
+
doit fixit status # Show active workflow
|
|
184
|
+
doit fixit status 123 # Show workflow for issue #123
|
|
185
|
+
"""
|
|
186
|
+
try:
|
|
187
|
+
service = FixitService()
|
|
188
|
+
except GitHubServiceError as e:
|
|
189
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
190
|
+
raise typer.Exit(code=2)
|
|
191
|
+
|
|
192
|
+
if issue_id is None:
|
|
193
|
+
# Show active workflow
|
|
194
|
+
workflow = service.get_active_workflow()
|
|
195
|
+
if workflow is None:
|
|
196
|
+
console.print("[yellow]No active fixit workflow.[/yellow]")
|
|
197
|
+
console.print("Use [cyan]doit fixit <issue_id>[/cyan] to start one.")
|
|
198
|
+
raise typer.Exit(code=0)
|
|
199
|
+
issue_id = workflow.issue_id
|
|
200
|
+
else:
|
|
201
|
+
workflow = service.get_workflow(issue_id)
|
|
202
|
+
if workflow is None:
|
|
203
|
+
console.print(f"[yellow]No workflow found for issue #{issue_id}.[/yellow]")
|
|
204
|
+
raise typer.Exit(code=1)
|
|
205
|
+
|
|
206
|
+
display_workflow_status(workflow)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@app.command("cancel")
|
|
210
|
+
def cancel(
|
|
211
|
+
issue_id: Optional[int] = typer.Argument(
|
|
212
|
+
None,
|
|
213
|
+
help="GitHub issue number to cancel",
|
|
214
|
+
),
|
|
215
|
+
force: bool = typer.Option(
|
|
216
|
+
False, "--force", "-f", help="Skip confirmation"
|
|
217
|
+
),
|
|
218
|
+
) -> None:
|
|
219
|
+
"""Cancel an active fixit workflow.
|
|
220
|
+
|
|
221
|
+
Marks the workflow as cancelled. The git branch is not deleted.
|
|
222
|
+
|
|
223
|
+
Examples:
|
|
224
|
+
doit fixit cancel # Cancel active workflow
|
|
225
|
+
doit fixit cancel 123 # Cancel workflow for issue #123
|
|
226
|
+
"""
|
|
227
|
+
try:
|
|
228
|
+
service = FixitService()
|
|
229
|
+
except GitHubServiceError as e:
|
|
230
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
231
|
+
raise typer.Exit(code=2)
|
|
232
|
+
|
|
233
|
+
if issue_id is None:
|
|
234
|
+
workflow = service.get_active_workflow()
|
|
235
|
+
if workflow is None:
|
|
236
|
+
console.print("[yellow]No active fixit workflow to cancel.[/yellow]")
|
|
237
|
+
raise typer.Exit(code=0)
|
|
238
|
+
issue_id = workflow.issue_id
|
|
239
|
+
|
|
240
|
+
# Confirm unless forced
|
|
241
|
+
if not force:
|
|
242
|
+
confirm = typer.confirm(
|
|
243
|
+
f"Cancel workflow for issue #{issue_id}?",
|
|
244
|
+
default=False,
|
|
245
|
+
)
|
|
246
|
+
if not confirm:
|
|
247
|
+
console.print("[yellow]Cancelled.[/yellow]")
|
|
248
|
+
raise typer.Exit(code=0)
|
|
249
|
+
|
|
250
|
+
success = service.cancel_workflow(issue_id)
|
|
251
|
+
if success:
|
|
252
|
+
console.print(
|
|
253
|
+
f"[green]Cancelled[/green] workflow for issue [cyan]#{issue_id}[/cyan]"
|
|
254
|
+
)
|
|
255
|
+
else:
|
|
256
|
+
console.print(f"[red]Failed to cancel workflow for issue #{issue_id}.[/red]")
|
|
257
|
+
raise typer.Exit(code=1)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@app.command("workflows")
|
|
261
|
+
def list_workflows() -> None:
|
|
262
|
+
"""List all fixit workflows.
|
|
263
|
+
|
|
264
|
+
Shows all workflows, including completed and cancelled ones.
|
|
265
|
+
"""
|
|
266
|
+
try:
|
|
267
|
+
service = FixitService()
|
|
268
|
+
except GitHubServiceError as e:
|
|
269
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
270
|
+
raise typer.Exit(code=2)
|
|
271
|
+
|
|
272
|
+
workflows = service.list_workflows()
|
|
273
|
+
|
|
274
|
+
if not workflows:
|
|
275
|
+
console.print("[yellow]No fixit workflows found.[/yellow]")
|
|
276
|
+
raise typer.Exit(code=0)
|
|
277
|
+
|
|
278
|
+
table = Table(title="Fixit Workflows")
|
|
279
|
+
table.add_column("Issue", style="cyan", width=8)
|
|
280
|
+
table.add_column("Branch", style="white")
|
|
281
|
+
table.add_column("Phase", style="dim")
|
|
282
|
+
table.add_column("Updated", style="dim")
|
|
283
|
+
|
|
284
|
+
for issue_id, workflow in workflows:
|
|
285
|
+
phase_style = _get_phase_style(workflow.phase)
|
|
286
|
+
table.add_row(
|
|
287
|
+
f"#{issue_id}",
|
|
288
|
+
workflow.branch_name,
|
|
289
|
+
f"[{phase_style}]{workflow.phase.value}[/{phase_style}]",
|
|
290
|
+
workflow.updated_at.strftime("%Y-%m-%d %H:%M"),
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
console.print(table)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@app.command("investigate")
|
|
297
|
+
def investigate(
|
|
298
|
+
issue_id: Optional[int] = typer.Argument(
|
|
299
|
+
None,
|
|
300
|
+
help="GitHub issue number (or uses active workflow)",
|
|
301
|
+
),
|
|
302
|
+
add_finding: Optional[str] = typer.Option(
|
|
303
|
+
None, "--add-finding", "-a", help="Add a finding to the investigation"
|
|
304
|
+
),
|
|
305
|
+
finding_type: str = typer.Option(
|
|
306
|
+
"hypothesis", "--type", "-t", help="Finding type: hypothesis, confirmed_cause, affected_file, reproduction_step"
|
|
307
|
+
),
|
|
308
|
+
checkpoint: Optional[str] = typer.Option(
|
|
309
|
+
None, "--checkpoint", "-c", help="Complete a checkpoint by ID"
|
|
310
|
+
),
|
|
311
|
+
done: bool = typer.Option(
|
|
312
|
+
False, "--done", "-d", help="Mark investigation complete"
|
|
313
|
+
),
|
|
314
|
+
) -> None:
|
|
315
|
+
"""Manage investigation for a fixit workflow.
|
|
316
|
+
|
|
317
|
+
Start, add findings, complete checkpoints, or finish investigation.
|
|
318
|
+
|
|
319
|
+
Examples:
|
|
320
|
+
doit fixit investigate # Start/view investigation
|
|
321
|
+
doit fixit investigate -a "Found null check missing" -t hypothesis
|
|
322
|
+
doit fixit investigate -c cp-1 # Complete checkpoint
|
|
323
|
+
doit fixit investigate --done # Finish investigation
|
|
324
|
+
"""
|
|
325
|
+
from ..models.fixit_models import FindingType
|
|
326
|
+
from ..prompts.fixit_prompts import display_investigation_findings
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
service = FixitService()
|
|
330
|
+
except GitHubServiceError as e:
|
|
331
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
332
|
+
raise typer.Exit(code=2)
|
|
333
|
+
|
|
334
|
+
# Get issue_id from active workflow if not provided
|
|
335
|
+
if issue_id is None:
|
|
336
|
+
workflow = service.get_active_workflow()
|
|
337
|
+
if workflow is None:
|
|
338
|
+
console.print("[yellow]No active fixit workflow.[/yellow]")
|
|
339
|
+
raise typer.Exit(code=0)
|
|
340
|
+
issue_id = workflow.issue_id
|
|
341
|
+
|
|
342
|
+
# Handle --done flag
|
|
343
|
+
if done:
|
|
344
|
+
success = service.complete_investigation(issue_id)
|
|
345
|
+
if success:
|
|
346
|
+
console.print(
|
|
347
|
+
f"[green]Investigation complete![/green] "
|
|
348
|
+
f"Run [cyan]doit fixit plan[/cyan] to create a fix plan."
|
|
349
|
+
)
|
|
350
|
+
else:
|
|
351
|
+
console.print(
|
|
352
|
+
"[red]Cannot complete investigation.[/red] "
|
|
353
|
+
"Add a confirmed_cause finding first."
|
|
354
|
+
)
|
|
355
|
+
raise typer.Exit(code=1)
|
|
356
|
+
return
|
|
357
|
+
|
|
358
|
+
# Handle --checkpoint flag
|
|
359
|
+
if checkpoint:
|
|
360
|
+
success = service.complete_checkpoint(issue_id, checkpoint)
|
|
361
|
+
if success:
|
|
362
|
+
console.print(f"[green]Checkpoint {checkpoint} completed.[/green]")
|
|
363
|
+
else:
|
|
364
|
+
console.print(f"[red]Checkpoint {checkpoint} not found.[/red]")
|
|
365
|
+
raise typer.Exit(code=1)
|
|
366
|
+
return
|
|
367
|
+
|
|
368
|
+
# Handle --add-finding flag
|
|
369
|
+
if add_finding:
|
|
370
|
+
try:
|
|
371
|
+
ft = FindingType(finding_type)
|
|
372
|
+
except ValueError:
|
|
373
|
+
console.print(f"[red]Invalid finding type: {finding_type}[/red]")
|
|
374
|
+
raise typer.Exit(code=1)
|
|
375
|
+
|
|
376
|
+
finding = service.add_finding(
|
|
377
|
+
issue_id=issue_id,
|
|
378
|
+
finding_type=ft,
|
|
379
|
+
description=add_finding,
|
|
380
|
+
)
|
|
381
|
+
if finding:
|
|
382
|
+
console.print(f"[green]Finding added:[/green] {finding.description}")
|
|
383
|
+
else:
|
|
384
|
+
console.print("[red]Failed to add finding.[/red] Start investigation first.")
|
|
385
|
+
raise typer.Exit(code=1)
|
|
386
|
+
return
|
|
387
|
+
|
|
388
|
+
# Default: start or show investigation
|
|
389
|
+
plan = service.get_investigation_plan(issue_id)
|
|
390
|
+
if plan is None:
|
|
391
|
+
# Start investigation
|
|
392
|
+
plan = service.start_investigation(issue_id)
|
|
393
|
+
if plan is None:
|
|
394
|
+
console.print(f"[red]No workflow found for issue #{issue_id}.[/red]")
|
|
395
|
+
raise typer.Exit(code=1)
|
|
396
|
+
console.print("[green]Investigation started![/green]")
|
|
397
|
+
console.print()
|
|
398
|
+
|
|
399
|
+
# Display investigation status
|
|
400
|
+
console.print(Panel(
|
|
401
|
+
f"[dim]Keywords:[/dim] {', '.join(plan.keywords[:5]) if plan.keywords else 'none'}\n\n"
|
|
402
|
+
"[dim]Checkpoints:[/dim]",
|
|
403
|
+
title=f"Investigation Plan: {plan.id}",
|
|
404
|
+
border_style="yellow",
|
|
405
|
+
))
|
|
406
|
+
|
|
407
|
+
# Show checkpoints
|
|
408
|
+
for cp in plan.checkpoints:
|
|
409
|
+
status = "[green]✓[/green]" if cp.completed else "[dim]○[/dim]"
|
|
410
|
+
console.print(f" {status} {cp.title} [dim]({cp.id})[/dim]")
|
|
411
|
+
|
|
412
|
+
console.print()
|
|
413
|
+
display_investigation_findings(plan.findings)
|
|
414
|
+
|
|
415
|
+
console.print(
|
|
416
|
+
"\n[dim]Use --add-finding to add findings, --checkpoint to complete steps, --done when ready.[/dim]"
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@app.command("plan")
|
|
421
|
+
def plan(
|
|
422
|
+
issue_id: Optional[int] = typer.Argument(
|
|
423
|
+
None,
|
|
424
|
+
help="GitHub issue number (or uses active workflow)",
|
|
425
|
+
),
|
|
426
|
+
generate: bool = typer.Option(
|
|
427
|
+
False, "--generate", "-g", help="Generate fix plan from investigation"
|
|
428
|
+
),
|
|
429
|
+
submit: bool = typer.Option(
|
|
430
|
+
False, "--submit", "-s", help="Submit plan for review"
|
|
431
|
+
),
|
|
432
|
+
) -> None:
|
|
433
|
+
"""Create or view a fix plan for a workflow.
|
|
434
|
+
|
|
435
|
+
Generate a fix plan from investigation findings, or view existing plan.
|
|
436
|
+
|
|
437
|
+
Examples:
|
|
438
|
+
doit fixit plan # View current fix plan
|
|
439
|
+
doit fixit plan --generate # Generate plan from findings
|
|
440
|
+
doit fixit plan --submit # Submit plan for review
|
|
441
|
+
"""
|
|
442
|
+
from ..prompts.fixit_prompts import display_fix_plan
|
|
443
|
+
|
|
444
|
+
try:
|
|
445
|
+
service = FixitService()
|
|
446
|
+
except GitHubServiceError as e:
|
|
447
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
448
|
+
raise typer.Exit(code=2)
|
|
449
|
+
|
|
450
|
+
# Get issue_id from active workflow if not provided
|
|
451
|
+
if issue_id is None:
|
|
452
|
+
workflow = service.get_active_workflow()
|
|
453
|
+
if workflow is None:
|
|
454
|
+
console.print("[yellow]No active fixit workflow.[/yellow]")
|
|
455
|
+
raise typer.Exit(code=0)
|
|
456
|
+
issue_id = workflow.issue_id
|
|
457
|
+
|
|
458
|
+
# Handle --generate flag
|
|
459
|
+
if generate:
|
|
460
|
+
plan_obj = service.generate_fix_plan(issue_id)
|
|
461
|
+
if plan_obj is None:
|
|
462
|
+
console.print(
|
|
463
|
+
"[red]Cannot generate fix plan.[/red] "
|
|
464
|
+
"Complete investigation with a confirmed_cause finding first."
|
|
465
|
+
)
|
|
466
|
+
raise typer.Exit(code=1)
|
|
467
|
+
console.print("[green]Fix plan generated![/green]")
|
|
468
|
+
display_fix_plan(plan_obj)
|
|
469
|
+
console.print(
|
|
470
|
+
"\n[dim]Run [cyan]doit fixit plan --submit[/cyan] when ready for review.[/dim]"
|
|
471
|
+
)
|
|
472
|
+
return
|
|
473
|
+
|
|
474
|
+
# Handle --submit flag
|
|
475
|
+
if submit:
|
|
476
|
+
success = service.submit_for_review(issue_id)
|
|
477
|
+
if success:
|
|
478
|
+
console.print(
|
|
479
|
+
"[green]Plan submitted for review![/green] "
|
|
480
|
+
"Run [cyan]doit fixit review[/cyan] to approve."
|
|
481
|
+
)
|
|
482
|
+
else:
|
|
483
|
+
console.print("[red]No fix plan found to submit.[/red]")
|
|
484
|
+
raise typer.Exit(code=1)
|
|
485
|
+
return
|
|
486
|
+
|
|
487
|
+
# Default: view existing plan
|
|
488
|
+
plan_obj = service.get_fix_plan(issue_id)
|
|
489
|
+
if plan_obj is None:
|
|
490
|
+
console.print(
|
|
491
|
+
"[yellow]No fix plan found.[/yellow] "
|
|
492
|
+
"Run [cyan]doit fixit plan --generate[/cyan] to create one."
|
|
493
|
+
)
|
|
494
|
+
raise typer.Exit(code=0)
|
|
495
|
+
|
|
496
|
+
display_fix_plan(plan_obj)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
@app.command("review")
|
|
500
|
+
def review(
|
|
501
|
+
issue_id: Optional[int] = typer.Argument(
|
|
502
|
+
None,
|
|
503
|
+
help="GitHub issue number (or uses active workflow)",
|
|
504
|
+
),
|
|
505
|
+
approve: bool = typer.Option(
|
|
506
|
+
False, "--approve", "-a", help="Approve the fix plan"
|
|
507
|
+
),
|
|
508
|
+
) -> None:
|
|
509
|
+
"""Review and approve a fix plan.
|
|
510
|
+
|
|
511
|
+
View the fix plan and optionally approve it for implementation.
|
|
512
|
+
|
|
513
|
+
Examples:
|
|
514
|
+
doit fixit review # View plan for review
|
|
515
|
+
doit fixit review --approve # Approve the plan
|
|
516
|
+
"""
|
|
517
|
+
from ..prompts.fixit_prompts import display_fix_plan
|
|
518
|
+
|
|
519
|
+
try:
|
|
520
|
+
service = FixitService()
|
|
521
|
+
except GitHubServiceError as e:
|
|
522
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
523
|
+
raise typer.Exit(code=2)
|
|
524
|
+
|
|
525
|
+
# Get issue_id from active workflow if not provided
|
|
526
|
+
if issue_id is None:
|
|
527
|
+
workflow = service.get_active_workflow()
|
|
528
|
+
if workflow is None:
|
|
529
|
+
console.print("[yellow]No active fixit workflow.[/yellow]")
|
|
530
|
+
raise typer.Exit(code=0)
|
|
531
|
+
issue_id = workflow.issue_id
|
|
532
|
+
|
|
533
|
+
# Get fix plan
|
|
534
|
+
plan_obj = service.get_fix_plan(issue_id)
|
|
535
|
+
if plan_obj is None:
|
|
536
|
+
console.print(
|
|
537
|
+
"[yellow]No fix plan found.[/yellow] "
|
|
538
|
+
"Run [cyan]doit fixit plan --generate[/cyan] first."
|
|
539
|
+
)
|
|
540
|
+
raise typer.Exit(code=0)
|
|
541
|
+
|
|
542
|
+
# Display plan
|
|
543
|
+
display_fix_plan(plan_obj)
|
|
544
|
+
console.print()
|
|
545
|
+
|
|
546
|
+
# Handle --approve flag
|
|
547
|
+
if approve:
|
|
548
|
+
success = service.approve_plan(issue_id)
|
|
549
|
+
if success:
|
|
550
|
+
console.print(
|
|
551
|
+
Panel(
|
|
552
|
+
"[green]Plan approved![/green]\n\n"
|
|
553
|
+
"Ready to implement the fix.\n"
|
|
554
|
+
"Run [cyan]doit implementit[/cyan] to execute the tasks.",
|
|
555
|
+
title="Approved",
|
|
556
|
+
border_style="green",
|
|
557
|
+
)
|
|
558
|
+
)
|
|
559
|
+
else:
|
|
560
|
+
console.print("[red]Failed to approve plan.[/red]")
|
|
561
|
+
raise typer.Exit(code=1)
|
|
562
|
+
return
|
|
563
|
+
|
|
564
|
+
# Prompt for action
|
|
565
|
+
console.print(
|
|
566
|
+
"\n[dim]Use --approve to approve this plan, or edit and resubmit.[/dim]"
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
# =============================================================================
|
|
571
|
+
# Helper Functions
|
|
572
|
+
# =============================================================================
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def _show_next_steps(phase: FixPhase, manual: bool = False) -> None:
|
|
576
|
+
"""Display next steps based on current phase."""
|
|
577
|
+
console.print()
|
|
578
|
+
|
|
579
|
+
if phase == FixPhase.INVESTIGATING:
|
|
580
|
+
if manual:
|
|
581
|
+
console.print(
|
|
582
|
+
Panel(
|
|
583
|
+
"[cyan]Manual mode enabled.[/cyan]\n\n"
|
|
584
|
+
"1. Investigate the issue manually\n"
|
|
585
|
+
"2. Create a fix plan with [cyan]doit fixit plan[/cyan]\n"
|
|
586
|
+
"3. Implement the fix using [cyan]doit implementit[/cyan]",
|
|
587
|
+
title="Next Steps",
|
|
588
|
+
border_style="dim",
|
|
589
|
+
)
|
|
590
|
+
)
|
|
591
|
+
else:
|
|
592
|
+
console.print(
|
|
593
|
+
Panel(
|
|
594
|
+
"The AI will help investigate the issue.\n\n"
|
|
595
|
+
"Run [cyan]doit fixit investigate[/cyan] to start the investigation.",
|
|
596
|
+
title="Next Steps",
|
|
597
|
+
border_style="dim",
|
|
598
|
+
)
|
|
599
|
+
)
|
|
600
|
+
elif phase == FixPhase.PLANNING:
|
|
601
|
+
console.print(
|
|
602
|
+
Panel(
|
|
603
|
+
"Investigation complete. Ready to create a fix plan.\n\n"
|
|
604
|
+
"Run [cyan]doit fixit plan[/cyan] to generate a fix plan.",
|
|
605
|
+
title="Next Steps",
|
|
606
|
+
border_style="dim",
|
|
607
|
+
)
|
|
608
|
+
)
|
|
609
|
+
elif phase == FixPhase.REVIEWING:
|
|
610
|
+
console.print(
|
|
611
|
+
Panel(
|
|
612
|
+
"Fix plan is ready for review.\n\n"
|
|
613
|
+
"Run [cyan]doit fixit review[/cyan] to review and approve.",
|
|
614
|
+
title="Next Steps",
|
|
615
|
+
border_style="dim",
|
|
616
|
+
)
|
|
617
|
+
)
|
|
618
|
+
elif phase == FixPhase.APPROVED:
|
|
619
|
+
console.print(
|
|
620
|
+
Panel(
|
|
621
|
+
"Fix plan approved! Ready to implement.\n\n"
|
|
622
|
+
"Run [cyan]doit implementit[/cyan] to execute the tasks.",
|
|
623
|
+
title="Next Steps",
|
|
624
|
+
border_style="dim",
|
|
625
|
+
)
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def _get_phase_style(phase: FixPhase) -> str:
|
|
630
|
+
"""Get Rich style for a phase."""
|
|
631
|
+
styles = {
|
|
632
|
+
FixPhase.INITIALIZED: "dim",
|
|
633
|
+
FixPhase.INVESTIGATING: "yellow",
|
|
634
|
+
FixPhase.PLANNING: "yellow",
|
|
635
|
+
FixPhase.REVIEWING: "cyan",
|
|
636
|
+
FixPhase.APPROVED: "green",
|
|
637
|
+
FixPhase.IMPLEMENTING: "blue",
|
|
638
|
+
FixPhase.COMPLETED: "green bold",
|
|
639
|
+
FixPhase.CANCELLED: "red dim",
|
|
640
|
+
}
|
|
641
|
+
return styles.get(phase, "white")
|