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,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")