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.

Files changed (135) 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/roadmapit_command.py +10 -0
  11. doit_cli/cli/status_command.py +117 -0
  12. doit_cli/cli/sync_prompts_command.py +248 -0
  13. doit_cli/cli/validate_command.py +196 -0
  14. doit_cli/cli/verify_command.py +204 -0
  15. doit_cli/cli/workflow_mixin.py +224 -0
  16. doit_cli/cli/xref_command.py +555 -0
  17. doit_cli/formatters/__init__.py +8 -0
  18. doit_cli/formatters/base.py +38 -0
  19. doit_cli/formatters/json_formatter.py +126 -0
  20. doit_cli/formatters/markdown_formatter.py +97 -0
  21. doit_cli/formatters/rich_formatter.py +257 -0
  22. doit_cli/main.py +51 -0
  23. doit_cli/models/__init__.py +139 -0
  24. doit_cli/models/agent.py +74 -0
  25. doit_cli/models/analytics_models.py +384 -0
  26. doit_cli/models/context_config.py +464 -0
  27. doit_cli/models/crossref_models.py +182 -0
  28. doit_cli/models/diagram_models.py +363 -0
  29. doit_cli/models/fixit_models.py +355 -0
  30. doit_cli/models/hook_config.py +125 -0
  31. doit_cli/models/project.py +91 -0
  32. doit_cli/models/results.py +121 -0
  33. doit_cli/models/search_models.py +228 -0
  34. doit_cli/models/status_models.py +195 -0
  35. doit_cli/models/sync_models.py +146 -0
  36. doit_cli/models/template.py +77 -0
  37. doit_cli/models/validation_models.py +175 -0
  38. doit_cli/models/workflow_models.py +319 -0
  39. doit_cli/prompts/__init__.py +5 -0
  40. doit_cli/prompts/fixit_prompts.py +344 -0
  41. doit_cli/prompts/interactive.py +390 -0
  42. doit_cli/rules/__init__.py +5 -0
  43. doit_cli/rules/builtin_rules.py +160 -0
  44. doit_cli/services/__init__.py +79 -0
  45. doit_cli/services/agent_detector.py +168 -0
  46. doit_cli/services/analytics_service.py +218 -0
  47. doit_cli/services/architecture_generator.py +290 -0
  48. doit_cli/services/backup_service.py +204 -0
  49. doit_cli/services/config_loader.py +113 -0
  50. doit_cli/services/context_loader.py +1123 -0
  51. doit_cli/services/coverage_calculator.py +142 -0
  52. doit_cli/services/crossref_service.py +237 -0
  53. doit_cli/services/cycle_time_calculator.py +134 -0
  54. doit_cli/services/date_inferrer.py +349 -0
  55. doit_cli/services/diagram_service.py +337 -0
  56. doit_cli/services/drift_detector.py +109 -0
  57. doit_cli/services/entity_parser.py +301 -0
  58. doit_cli/services/er_diagram_generator.py +197 -0
  59. doit_cli/services/fixit_service.py +699 -0
  60. doit_cli/services/github_service.py +192 -0
  61. doit_cli/services/hook_manager.py +258 -0
  62. doit_cli/services/hook_validator.py +528 -0
  63. doit_cli/services/input_validator.py +322 -0
  64. doit_cli/services/memory_search.py +527 -0
  65. doit_cli/services/mermaid_validator.py +334 -0
  66. doit_cli/services/prompt_transformer.py +91 -0
  67. doit_cli/services/prompt_writer.py +133 -0
  68. doit_cli/services/query_interpreter.py +428 -0
  69. doit_cli/services/report_exporter.py +219 -0
  70. doit_cli/services/report_generator.py +256 -0
  71. doit_cli/services/requirement_parser.py +112 -0
  72. doit_cli/services/roadmap_summarizer.py +209 -0
  73. doit_cli/services/rule_engine.py +443 -0
  74. doit_cli/services/scaffolder.py +215 -0
  75. doit_cli/services/score_calculator.py +172 -0
  76. doit_cli/services/section_parser.py +204 -0
  77. doit_cli/services/spec_scanner.py +327 -0
  78. doit_cli/services/state_manager.py +355 -0
  79. doit_cli/services/status_reporter.py +143 -0
  80. doit_cli/services/task_parser.py +347 -0
  81. doit_cli/services/template_manager.py +710 -0
  82. doit_cli/services/template_reader.py +158 -0
  83. doit_cli/services/user_journey_generator.py +214 -0
  84. doit_cli/services/user_story_parser.py +232 -0
  85. doit_cli/services/validation_service.py +188 -0
  86. doit_cli/services/validator.py +232 -0
  87. doit_cli/services/velocity_tracker.py +173 -0
  88. doit_cli/services/workflow_engine.py +405 -0
  89. doit_cli/templates/agent-file-template.md +28 -0
  90. doit_cli/templates/checklist-template.md +39 -0
  91. doit_cli/templates/commands/doit.checkin.md +363 -0
  92. doit_cli/templates/commands/doit.constitution.md +187 -0
  93. doit_cli/templates/commands/doit.documentit.md +485 -0
  94. doit_cli/templates/commands/doit.fixit.md +181 -0
  95. doit_cli/templates/commands/doit.implementit.md +265 -0
  96. doit_cli/templates/commands/doit.planit.md +262 -0
  97. doit_cli/templates/commands/doit.reviewit.md +355 -0
  98. doit_cli/templates/commands/doit.roadmapit.md +389 -0
  99. doit_cli/templates/commands/doit.scaffoldit.md +458 -0
  100. doit_cli/templates/commands/doit.specit.md +521 -0
  101. doit_cli/templates/commands/doit.taskit.md +304 -0
  102. doit_cli/templates/commands/doit.testit.md +277 -0
  103. doit_cli/templates/config/context.yaml +134 -0
  104. doit_cli/templates/config/hooks.yaml +93 -0
  105. doit_cli/templates/config/validation-rules.yaml +64 -0
  106. doit_cli/templates/github-issue-templates/epic.yml +78 -0
  107. doit_cli/templates/github-issue-templates/feature.yml +116 -0
  108. doit_cli/templates/github-issue-templates/task.yml +129 -0
  109. doit_cli/templates/hooks/.gitkeep +0 -0
  110. doit_cli/templates/hooks/post-commit.sh +25 -0
  111. doit_cli/templates/hooks/post-merge.sh +75 -0
  112. doit_cli/templates/hooks/pre-commit.sh +17 -0
  113. doit_cli/templates/hooks/pre-push.sh +18 -0
  114. doit_cli/templates/memory/completed_roadmap.md +50 -0
  115. doit_cli/templates/memory/constitution.md +125 -0
  116. doit_cli/templates/memory/roadmap.md +61 -0
  117. doit_cli/templates/plan-template.md +146 -0
  118. doit_cli/templates/scripts/bash/check-prerequisites.sh +166 -0
  119. doit_cli/templates/scripts/bash/common.sh +156 -0
  120. doit_cli/templates/scripts/bash/create-new-feature.sh +297 -0
  121. doit_cli/templates/scripts/bash/setup-plan.sh +61 -0
  122. doit_cli/templates/scripts/bash/update-agent-context.sh +675 -0
  123. doit_cli/templates/scripts/powershell/check-prerequisites.ps1 +148 -0
  124. doit_cli/templates/scripts/powershell/common.ps1 +137 -0
  125. doit_cli/templates/scripts/powershell/create-new-feature.ps1 +283 -0
  126. doit_cli/templates/scripts/powershell/setup-plan.ps1 +61 -0
  127. doit_cli/templates/scripts/powershell/update-agent-context.ps1 +406 -0
  128. doit_cli/templates/spec-template.md +159 -0
  129. doit_cli/templates/tasks-template.md +313 -0
  130. doit_cli/templates/vscode-settings.json +14 -0
  131. doit_toolkit_cli-0.1.10.dist-info/METADATA +324 -0
  132. doit_toolkit_cli-0.1.10.dist-info/RECORD +135 -0
  133. doit_toolkit_cli-0.1.10.dist-info/WHEEL +4 -0
  134. doit_toolkit_cli-0.1.10.dist-info/entry_points.txt +2 -0
  135. doit_toolkit_cli-0.1.10.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,405 @@
1
+ """Workflow engine for orchestrating guided workflows.
2
+
3
+ This module provides the WorkflowEngine class that manages workflow execution,
4
+ step navigation, state persistence, and user input collection.
5
+ """
6
+
7
+ import signal
8
+ from datetime import datetime
9
+ from typing import Protocol, runtime_checkable
10
+
11
+ from rich.console import Console
12
+
13
+ from ..models.workflow_models import (
14
+ Workflow,
15
+ WorkflowStep,
16
+ WorkflowState,
17
+ WorkflowStatus,
18
+ StepResponse,
19
+ ValidationResult,
20
+ WorkflowError,
21
+ NavigationCommand,
22
+ )
23
+ from ..prompts.interactive import InteractivePrompt, ProgressDisplay
24
+ from .input_validator import validate_step, get_validator
25
+
26
+
27
+ # =============================================================================
28
+ # WorkflowEngine Protocol
29
+ # =============================================================================
30
+
31
+
32
+ @runtime_checkable
33
+ class WorkflowEngineProtocol(Protocol):
34
+ """Protocol defining the WorkflowEngine interface."""
35
+
36
+ def start(self, workflow: Workflow) -> WorkflowState:
37
+ """Start a new workflow or resume interrupted one."""
38
+ ...
39
+
40
+ def execute_step(
41
+ self,
42
+ state: WorkflowState,
43
+ step: WorkflowStep,
44
+ ) -> tuple[WorkflowState, StepResponse]:
45
+ """Execute a single workflow step."""
46
+ ...
47
+
48
+ def complete(self, state: WorkflowState) -> dict:
49
+ """Complete workflow and return collected responses."""
50
+ ...
51
+
52
+ def cancel(self, state: WorkflowState) -> None:
53
+ """Cancel workflow and save state for resume."""
54
+ ...
55
+
56
+
57
+ # =============================================================================
58
+ # WorkflowEngine Implementation
59
+ # =============================================================================
60
+
61
+
62
+ class WorkflowEngine:
63
+ """Orchestrates guided workflow execution.
64
+
65
+ Manages the full lifecycle of a workflow including:
66
+ - Starting new workflows or resuming interrupted ones
67
+ - Executing individual steps with validation
68
+ - Handling navigation (back/skip)
69
+ - State persistence for recovery
70
+ - Progress display
71
+ """
72
+
73
+ def __init__(
74
+ self,
75
+ console: Console | None = None,
76
+ state_manager: "StateManager | None" = None,
77
+ ):
78
+ """Initialize the workflow engine.
79
+
80
+ Args:
81
+ console: Rich console for output
82
+ state_manager: Optional state manager for persistence
83
+ """
84
+ self.console = console or Console()
85
+ self.state_manager = state_manager
86
+ self.prompt = InteractivePrompt(console)
87
+ self.progress = ProgressDisplay(console)
88
+ self._interrupted = False
89
+
90
+ def start(self, workflow: Workflow) -> WorkflowState:
91
+ """Start a new workflow or resume interrupted one.
92
+
93
+ Args:
94
+ workflow: Workflow definition to execute
95
+
96
+ Returns:
97
+ Initial or resumed WorkflowState
98
+
99
+ Raises:
100
+ WorkflowError: If workflow cannot be started
101
+ """
102
+ # Check for existing interrupted state
103
+ if self.state_manager:
104
+ existing_state = self.state_manager.load(workflow.command_name)
105
+ if existing_state and existing_state.status == WorkflowStatus.INTERRUPTED:
106
+ if self._prompt_resume(existing_state):
107
+ existing_state.status = WorkflowStatus.RUNNING
108
+ return existing_state
109
+ else:
110
+ # User chose to start fresh
111
+ self.state_manager.delete(existing_state)
112
+
113
+ # Create new state
114
+ state = WorkflowState(
115
+ id=self._generate_state_id(workflow.command_name),
116
+ workflow_id=workflow.id,
117
+ command_name=workflow.command_name,
118
+ current_step=0,
119
+ total_steps=len(workflow.steps),
120
+ status=WorkflowStatus.PENDING,
121
+ )
122
+
123
+ return state
124
+
125
+ def execute_step(
126
+ self,
127
+ state: WorkflowState,
128
+ step: WorkflowStep,
129
+ ) -> tuple[WorkflowState, StepResponse]:
130
+ """Execute a single workflow step.
131
+
132
+ Args:
133
+ state: Current workflow state
134
+ step: Step to execute
135
+
136
+ Returns:
137
+ Updated state and step response
138
+
139
+ Raises:
140
+ NavigationCommand: If user navigates back/skip
141
+ KeyboardInterrupt: If user cancels
142
+ """
143
+ # Update status to running
144
+ if state.status == WorkflowStatus.PENDING:
145
+ state.status = WorkflowStatus.RUNNING
146
+
147
+ # Get validator if specified
148
+ validator = None
149
+ if step.validation_type:
150
+ validator = get_validator(step.validation_type)
151
+
152
+ # Show progress
153
+ total_steps = self._count_steps(state)
154
+ self.progress.show_step(step, state.current_step + 1, total_steps)
155
+
156
+ # Get input based on step type
157
+ if step.options:
158
+ value = self.prompt.prompt_choice(step, step.options)
159
+ else:
160
+ value = self.prompt.prompt(step, validator)
161
+
162
+ # Create response
163
+ response = StepResponse(
164
+ step_id=step.id,
165
+ value=value,
166
+ skipped=False,
167
+ )
168
+
169
+ # Update state
170
+ state.set_response(response)
171
+ state.advance_step()
172
+
173
+ # Show completion
174
+ self.progress.mark_complete(step)
175
+
176
+ return state, response
177
+
178
+ def run(
179
+ self,
180
+ workflow: Workflow,
181
+ initial_responses: dict[str, str] | None = None,
182
+ ) -> dict:
183
+ """Run a complete workflow from start to finish.
184
+
185
+ Args:
186
+ workflow: Workflow to execute
187
+ initial_responses: Optional pre-populated responses to skip steps.
188
+ Maps step_id -> value. Steps with initial responses will be
189
+ skipped and use the provided value.
190
+
191
+ Returns:
192
+ Dictionary of step_id -> response value
193
+
194
+ Raises:
195
+ WorkflowError: If workflow fails
196
+ KeyboardInterrupt: If user cancels
197
+ """
198
+ # Setup interrupt handler
199
+ self._setup_interrupt_handler()
200
+
201
+ state = self.start(workflow)
202
+
203
+ # Sort steps by order
204
+ steps = sorted(workflow.steps, key=lambda s: s.order)
205
+
206
+ # Pre-populate state with initial responses (Fix MT-007)
207
+ if initial_responses:
208
+ for step_id, value in initial_responses.items():
209
+ state.responses[step_id] = StepResponse(
210
+ step_id=step_id,
211
+ value=value,
212
+ skipped=False,
213
+ )
214
+
215
+ try:
216
+ while state.current_step < len(steps):
217
+ if self._interrupted:
218
+ self._handle_interrupt(state)
219
+ raise KeyboardInterrupt
220
+
221
+ step = steps[state.current_step]
222
+
223
+ # Skip steps that already have responses (Fix MT-007)
224
+ if step.id in state.responses:
225
+ self.console.print(
226
+ f"[dim]Skipping {step.name} (provided via CLI)[/dim]"
227
+ )
228
+ state.current_step += 1
229
+ continue
230
+
231
+ try:
232
+ state, response = self.execute_step(state, step)
233
+ except NavigationCommand as nav:
234
+ state = self._handle_navigation(state, nav, steps)
235
+
236
+ # Save state after each step (if state manager available)
237
+ if self.state_manager:
238
+ self.state_manager.save(state)
239
+
240
+ # Complete workflow
241
+ return self.complete(state)
242
+
243
+ except KeyboardInterrupt:
244
+ self.cancel(state)
245
+ raise
246
+
247
+ def complete(self, state: WorkflowState) -> dict:
248
+ """Complete workflow and return collected responses.
249
+
250
+ Args:
251
+ state: Final workflow state
252
+
253
+ Returns:
254
+ Dictionary of step_id -> response value
255
+ """
256
+ state.status = WorkflowStatus.COMPLETED
257
+
258
+ # Delete state file on success
259
+ if self.state_manager:
260
+ self.state_manager.delete(state)
261
+
262
+ # Show summary
263
+ self.progress.show_summary(len(state.responses))
264
+
265
+ # Return responses as simple dict
266
+ return {
267
+ step_id: resp.value
268
+ for step_id, resp in state.responses.items()
269
+ }
270
+
271
+ def cancel(self, state: WorkflowState) -> None:
272
+ """Cancel workflow and save state for resume.
273
+
274
+ Args:
275
+ state: Current workflow state
276
+ """
277
+ state.status = WorkflowStatus.INTERRUPTED
278
+
279
+ # Save state for resume
280
+ if self.state_manager:
281
+ self.state_manager.save(state)
282
+
283
+ # Show interrupted message
284
+ total_steps = self._count_steps(state)
285
+ self.progress.show_interrupted(state.current_step + 1, total_steps)
286
+
287
+ # =========================================================================
288
+ # Navigation Handling (T011)
289
+ # =========================================================================
290
+
291
+ def _handle_navigation(
292
+ self,
293
+ state: WorkflowState,
294
+ nav: NavigationCommand,
295
+ steps: list[WorkflowStep],
296
+ ) -> WorkflowState:
297
+ """Handle navigation commands (back/skip).
298
+
299
+ Args:
300
+ state: Current workflow state
301
+ nav: Navigation command
302
+ steps: List of workflow steps
303
+
304
+ Returns:
305
+ Updated workflow state
306
+ """
307
+ if nav.command == "back":
308
+ return self._go_back(state, steps)
309
+ elif nav.command == "skip":
310
+ return self._skip_step(state, steps)
311
+ return state
312
+
313
+ def _go_back(
314
+ self,
315
+ state: WorkflowState,
316
+ steps: list[WorkflowStep],
317
+ ) -> WorkflowState:
318
+ """Go back to the previous step.
319
+
320
+ Args:
321
+ state: Current workflow state
322
+ steps: List of workflow steps
323
+
324
+ Returns:
325
+ Updated state with decreased step index
326
+ """
327
+ if state.current_step > 0:
328
+ state.go_back()
329
+ self.console.print("[info]Going back to previous step...[/info]")
330
+ else:
331
+ self.console.print("[warning]Already at first step[/warning]")
332
+ return state
333
+
334
+ def _skip_step(
335
+ self,
336
+ state: WorkflowState,
337
+ steps: list[WorkflowStep],
338
+ ) -> WorkflowState:
339
+ """Skip the current optional step.
340
+
341
+ Args:
342
+ state: Current workflow state
343
+ steps: List of workflow steps
344
+
345
+ Returns:
346
+ Updated state with step skipped
347
+ """
348
+ step = steps[state.current_step]
349
+
350
+ if step.required:
351
+ self.console.print("[error]Cannot skip required step[/error]")
352
+ return state
353
+
354
+ # Create skip response with default value
355
+ response = StepResponse(
356
+ step_id=step.id,
357
+ value=step.default_value or "",
358
+ skipped=True,
359
+ )
360
+
361
+ state.set_response(response)
362
+ state.advance_step()
363
+ self.progress.mark_skipped(step)
364
+
365
+ return state
366
+
367
+ # =========================================================================
368
+ # Internal Methods
369
+ # =========================================================================
370
+
371
+ def _generate_state_id(self, command_name: str) -> str:
372
+ """Generate a unique state ID."""
373
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
374
+ return f"{command_name}_{timestamp}"
375
+
376
+ def _count_steps(self, state: WorkflowState) -> int:
377
+ """Get total step count from state."""
378
+ if state.total_steps > 0:
379
+ return state.total_steps
380
+ # Fallback for backwards compatibility
381
+ return max(len(state.responses) + 1, state.current_step + 1)
382
+
383
+ def _prompt_resume(self, state: WorkflowState) -> bool:
384
+ """Prompt user to resume interrupted workflow."""
385
+ self.console.print()
386
+ self.console.print(
387
+ f"[warning]Found interrupted workflow from "
388
+ f"{state.updated_at.strftime('%Y-%m-%d %H:%M')}[/warning]"
389
+ )
390
+ self.console.print(
391
+ f"[info]Progress: {len(state.responses)} steps completed[/info]"
392
+ )
393
+ return self.prompt.prompt_confirm("Resume where you left off?", default=True)
394
+
395
+ def _setup_interrupt_handler(self) -> None:
396
+ """Setup signal handler for Ctrl+C."""
397
+ def handler(signum, frame):
398
+ self._interrupted = True
399
+
400
+ signal.signal(signal.SIGINT, handler)
401
+
402
+ def _handle_interrupt(self, state: WorkflowState) -> None:
403
+ """Handle interrupt signal."""
404
+ self._interrupted = False
405
+ self.cancel(state)
@@ -0,0 +1,28 @@
1
+ # [PROJECT NAME] Development Guidelines
2
+
3
+ Auto-generated from all feature plans. Last updated: [DATE]
4
+
5
+ ## Active Technologies
6
+
7
+ [EXTRACTED FROM ALL PLAN.MD FILES]
8
+
9
+ ## Project Structure
10
+
11
+ ```text
12
+ [ACTUAL STRUCTURE FROM PLANS]
13
+ ```
14
+
15
+ ## Commands
16
+
17
+ [ONLY COMMANDS FOR ACTIVE TECHNOLOGIES]
18
+
19
+ ## Code Style
20
+
21
+ [LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE]
22
+
23
+ ## Recent Changes
24
+
25
+ [LAST 3 FEATURES AND WHAT THEY ADDED]
26
+
27
+ <!-- MANUAL ADDITIONS START -->
28
+ <!-- MANUAL ADDITIONS END -->
@@ -0,0 +1,39 @@
1
+ # [CHECKLIST TYPE] Checklist: [FEATURE NAME]
2
+
3
+ **Purpose**: [Brief description of what this checklist covers]
4
+ **Created**: [DATE]
5
+ **Feature**: [Link to spec.md or relevant documentation]
6
+
7
+ **Note**: This checklist is generated by `/doit.specit` as part of spec validation.
8
+
9
+ <!--
10
+ ============================================================================
11
+ IMPORTANT: The checklist items below are SAMPLE ITEMS for illustration only.
12
+
13
+ The checklist generation process MUST replace these with actual items based on:
14
+ - Feature requirements from spec.md
15
+ - Technical context from plan.md
16
+ - Implementation details from tasks.md
17
+
18
+ DO NOT keep these sample items in the generated checklist file.
19
+ ============================================================================
20
+ -->
21
+
22
+ ## [Category 1]
23
+
24
+ - [ ] CHK001 First checklist item with clear action
25
+ - [ ] CHK002 Second checklist item
26
+ - [ ] CHK003 Third checklist item
27
+
28
+ ## [Category 2]
29
+
30
+ - [ ] CHK004 Another category item
31
+ - [ ] CHK005 Item with specific criteria
32
+ - [ ] CHK006 Final item in this category
33
+
34
+ ## Notes
35
+
36
+ - Check items off as completed: `[x]`
37
+ - Add comments or findings inline
38
+ - Link to relevant resources or documentation
39
+ - Items are numbered sequentially for easy reference