1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3use std::fs;
4use std::path::Path;
5
6use crate::tools::{Tool, ToolContext, ToolError, ToolResult};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct ThreeStageWorkflowTool {
20 name: String,
21 description: String,
22}
23
24impl Default for ThreeStageWorkflowTool {
25 fn default() -> Self {
26 Self {
27 name: "three_stage_workflow".to_string(),
28 description: "Implements three-stage workflow pattern for complex tasks. Manages task_plan.md, findings.md, and progress.md with automated context engineering and error learning.".to_string(),
29 }
30 }
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct WorkflowParams {
35 pub action: String,
36 pub project_name: Option<String>,
37 pub phase_update: Option<PhaseUpdate>,
38 pub finding: Option<String>,
39 pub progress_entry: Option<String>,
40 pub error_info: Option<ErrorInfo>,
41 pub decision: Option<DecisionInfo>,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct PhaseUpdate {
46 pub phase_number: u32,
47 pub status: String, pub notes: Option<String>,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct ErrorInfo {
53 pub error_description: String,
54 pub attempt_number: u32,
55 pub resolution: Option<String>,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct DecisionInfo {
60 pub decision: String,
61 pub rationale: String,
62}
63
64#[async_trait]
65impl Tool for ThreeStageWorkflowTool {
66 fn name(&self) -> &str {
67 &self.name
68 }
69
70 fn description(&self) -> &str {
71 &self.description
72 }
73
74 fn input_schema(&self) -> serde_json::Value {
75 serde_json::json!({
76 "type": "object",
77 "properties": {
78 "action": {
79 "type": "string",
80 "enum": [
81 "init_workflow",
82 "pre_action_check",
83 "post_action_update",
84 "update_phase",
85 "add_finding",
86 "add_progress",
87 "log_error",
88 "log_decision",
89 "check_completion",
90 "apply_2action_rule"
91 ],
92 "description": "Workflow action: init_workflow (create files), pre_action_check (read plan before action), post_action_update (update after action), update_phase (change phase status), add_finding (save discovery), add_progress (log action), log_error (track error), log_decision (record decision), check_completion (verify all phases complete), apply_2action_rule (save findings after 2 visual operations)"
93 },
94 "project_name": {
95 "type": "string",
96 "description": "Name of the project (used for init_workflow action)"
97 },
98 "phase_update": {
99 "type": "object",
100 "properties": {
101 "phase_number": {
102 "type": "integer",
103 "description": "Phase number to update (1-5)"
104 },
105 "status": {
106 "type": "string",
107 "enum": ["pending", "in_progress", "complete"],
108 "description": "New status for the phase"
109 },
110 "notes": {
111 "type": "string",
112 "description": "Optional notes about the phase update"
113 }
114 },
115 "required": ["phase_number", "status"]
116 },
117 "finding": {
118 "type": "string",
119 "description": "Finding or discovery to add to findings.md (use after visual operations)"
120 },
121 "progress_entry": {
122 "type": "string",
123 "description": "Progress entry to add to progress.md"
124 },
125 "error_info": {
126 "type": "object",
127 "properties": {
128 "error_description": {
129 "type": "string",
130 "description": "Description of the error encountered"
131 },
132 "attempt_number": {
133 "type": "integer",
134 "description": "Which attempt this was (1, 2, 3)"
135 },
136 "resolution": {
137 "type": "string",
138 "description": "How the error was resolved (if resolved)"
139 }
140 },
141 "required": ["error_description", "attempt_number"]
142 },
143 "decision": {
144 "type": "object",
145 "properties": {
146 "decision": {
147 "type": "string",
148 "description": "The decision made"
149 },
150 "rationale": {
151 "type": "string",
152 "description": "The reasoning behind the decision"
153 }
154 },
155 "required": ["decision", "rationale"]
156 }
157 },
158 "required": ["action"]
159 })
160 }
161
162 async fn execute(
163 &self,
164 params: serde_json::Value,
165 _context: &ToolContext,
166 ) -> Result<ToolResult, ToolError> {
167 let params: WorkflowParams =
168 serde_json::from_value(params).map_err(|e| ToolError::invalid_params(e.to_string()))?;
169
170 match params.action.as_str() {
171 "init_workflow" => self.init_workflow(params.project_name.as_deref()),
172 "pre_action_check" => self.pre_action_check(),
173 "post_action_update" => self.post_action_update(),
174 "update_phase" => {
175 if let Some(phase_update) = params.phase_update {
176 self.update_phase(phase_update)
177 } else {
178 Err(ToolError::invalid_params(
179 "phase_update required for update_phase action",
180 ))
181 }
182 }
183 "add_finding" => {
184 if let Some(finding) = params.finding {
185 self.add_finding(&finding)
186 } else {
187 Err(ToolError::invalid_params(
188 "finding required for add_finding action",
189 ))
190 }
191 }
192 "add_progress" => {
193 if let Some(progress_entry) = params.progress_entry {
194 self.add_progress(&progress_entry)
195 } else {
196 Err(ToolError::invalid_params(
197 "progress_entry required for add_progress action",
198 ))
199 }
200 }
201 "log_error" => {
202 if let Some(error_info) = params.error_info {
203 self.log_error(error_info)
204 } else {
205 Err(ToolError::invalid_params(
206 "error_info required for log_error action",
207 ))
208 }
209 }
210 "log_decision" => {
211 if let Some(decision) = params.decision {
212 self.log_decision(decision)
213 } else {
214 Err(ToolError::invalid_params(
215 "decision required for log_decision action",
216 ))
217 }
218 }
219 "check_completion" => self.check_completion(),
220 "apply_2action_rule" => {
221 if let Some(finding) = params.finding {
222 self.apply_2action_rule(&finding)
223 } else {
224 Err(ToolError::invalid_params(
225 "finding required for apply_2action_rule action",
226 ))
227 }
228 }
229 _ => Err(ToolError::invalid_params(format!(
230 "Unknown action: {}",
231 params.action
232 ))),
233 }
234 }
235}
236
237impl ThreeStageWorkflowTool {
238 fn init_workflow(&self, project_name: Option<&str>) -> Result<ToolResult, ToolError> {
240 let project_name = project_name.unwrap_or("project");
241 let date = chrono::Utc::now().format("%Y-%m-%d").to_string();
242
243 let task_plan_content = format!(
245 r#"# Task Plan: {}
246
247## Goal
248[One sentence describing the end state]
249
250## Current Phase
251Phase 1
252
253## Phases
254
255### Phase 1: Requirements & Discovery
256- [ ] Understand user intent and requirements
257- [ ] Identify constraints and dependencies
258- [ ] Document findings in findings.md
259- **Status:** in_progress
260
261### Phase 2: Planning & Structure
262- [ ] Define technical approach and architecture
263- [ ] Create project structure if needed
264- [ ] Document key decisions with rationale
265- **Status:** pending
266
267### Phase 3: Implementation
268- [ ] Execute the plan step by step
269- [ ] Write code to files before executing
270- [ ] Test incrementally and document results
271- **Status:** pending
272
273### Phase 4: Testing & Verification
274- [ ] Verify all requirements are met
275- [ ] Document test results in progress.md
276- [ ] Fix any issues found and log resolutions
277- **Status:** pending
278
279### Phase 5: Delivery & Completion
280- [ ] Review all output files and deliverables
281- [ ] Ensure completeness and quality
282- [ ] Deliver final results to user
283- **Status:** pending
284
285## Key Questions
2861. [Important question to answer during the task]
2872. [Another key question that guides decisions]
288
289## Decisions Made
290| Decision | Rationale |
291|----------|-----------|
292| | |
293
294## Errors Encountered
295| Error | Attempt | Resolution |
296|-------|---------|------------|
297| | 1 | |
298
299## Notes
300- **CRITICAL**: Re-read this plan before major decisions (attention manipulation)
301- **2-Action Rule**: Save findings after every 2 view/browser operations
302- **3-Strike Protocol**: Never repeat the same failed action 3 times
303- Update phase status: pending → in_progress → complete
304"#,
305 project_name
306 );
307
308 let findings_content = r#"# Findings & Research
310
311## Requirements Analysis
312-
313
314## Research Findings
315-
316
317## Technical Decisions
318| Decision | Rationale | Impact |
319|----------|-----------|--------|
320
321## Issues & Solutions
322| Issue | Root Cause | Resolution |
323|-------|------------|------------|
324
325## Resources & References
326-
327
328## Key Insights
329-
330"#;
331
332 let progress_content = format!(
334 r#"# Progress Log
335
336## Session: {}
337
338### Current Status
339- **Phase:** 1 - Requirements & Discovery
340- **Started:** {}
341- **Visual Operations Count:** 0
342
343### Actions Taken
344-
345
346### Test Results
347| Test | Expected | Actual | Status |
348|------|----------|--------|--------|
349
350### Error Log
351| Time | Error | Attempt | Resolution |
352|------|-------|---------|------------|
353
354### Context Refreshes
355| Time | Reason | Action |
356|------|--------|--------|
357"#,
358 date, date
359 );
360
361 fs::write("task_plan.md", task_plan_content)?;
363 fs::write("findings.md", findings_content)?;
364 fs::write("progress.md", progress_content)?;
365
366 Ok(ToolResult::success("✅ Three-stage workflow initialized! Created task_plan.md, findings.md, progress.md with automated context engineering.")
367 .with_metadata("files_created", serde_json::json!(["task_plan.md", "findings.md", "progress.md"]))
368 .with_metadata("project_name", serde_json::json!(project_name))
369 .with_metadata("workflow_stage", serde_json::json!("initialized")))
370 }
371
372 fn pre_action_check(&self) -> Result<ToolResult, ToolError> {
374 if !Path::new("task_plan.md").exists() {
375 return Err(ToolError::execution_failed(
376 "task_plan.md not found. Run init_workflow first.",
377 ));
378 }
379
380 let content = fs::read_to_string("task_plan.md")?;
381
382 let goal = self.extract_goal(&content);
384 let current_phase = self.extract_current_phase(&content);
385 let pending_tasks = self.extract_pending_tasks(&content);
386
387 let _ = self.log_context_refresh("Pre-action context refresh");
389
390 Ok(ToolResult::success(format!(
391 "🔄 Pre-Action Context Refresh:\n\n**Goal:** {}\n**Current Phase:** {}\n**Pending Tasks:**\n{}\n\n⚠️ Keep these goals in mind for the next action!",
392 goal,
393 current_phase,
394 pending_tasks.join("\n")
395 ))
396 .with_metadata("goal", serde_json::json!(goal))
397 .with_metadata("current_phase", serde_json::json!(current_phase))
398 .with_metadata("pending_tasks", serde_json::json!(pending_tasks))
399 .with_metadata("workflow_stage", serde_json::json!("pre_action")))
400 }
401
402 fn post_action_update(&self) -> Result<ToolResult, ToolError> {
404 Ok(ToolResult::success("📝 Post-Action Reminder:\n\n1. Did this action complete a phase? Update task_plan.md status\n2. Any new findings? Add to findings.md\n3. Any errors encountered? Log them for learning\n4. Update progress.md with what was accomplished")
405 .with_metadata("workflow_stage", serde_json::json!("post_action"))
406 .with_metadata("reminder_type", serde_json::json!("status_update")))
407 }
408
409 fn apply_2action_rule(&self, finding: &str) -> Result<ToolResult, ToolError> {
411 let _ = self.increment_visual_operations();
413
414 self.add_finding(finding)?;
416
417 Ok(ToolResult::success(format!(
418 "🎯 2-Action Rule Applied: Saved finding after visual operations\n\nFinding: {}",
419 finding
420 ))
421 .with_metadata("rule_applied", serde_json::json!("2_action_rule"))
422 .with_metadata("finding", serde_json::json!(finding)))
423 }
424
425 fn log_error(&self, error_info: ErrorInfo) -> Result<ToolResult, ToolError> {
427 if !Path::new("task_plan.md").exists() {
428 return Err(ToolError::execution_failed(
429 "task_plan.md not found. Run init_workflow first.",
430 ));
431 }
432
433 let mut content = fs::read_to_string("task_plan.md")?;
434
435 let resolution = error_info.resolution.as_deref().unwrap_or("In progress");
437 let error_entry = format!(
438 "| {} | {} | {} |",
439 error_info.error_description, error_info.attempt_number, resolution
440 );
441
442 if let Some(pos) = content.find("## Errors Encountered\n| Error | Attempt | Resolution |\n|-------|---------|------------|") {
443 let insert_pos = pos + "## Errors Encountered\n| Error | Attempt | Resolution |\n|-------|---------|------------|\n".len();
444 content.insert_str(insert_pos, &format!("{}\n", error_entry));
445 }
446
447 fs::write("task_plan.md", content)?;
448
449 let _ = self.log_error_to_progress(&error_info);
451
452 let warning = if error_info.attempt_number >= 3 {
453 "\n⚠️ WARNING: 3rd attempt! Consider escalating to user or changing approach completely."
454 } else {
455 ""
456 };
457
458 Ok(ToolResult::success(format!(
459 "🚨 Error Logged (Attempt {}): {}{}",
460 error_info.attempt_number, error_info.error_description, warning
461 ))
462 .with_metadata("error_logged", serde_json::json!(true))
463 .with_metadata(
464 "attempt_number",
465 serde_json::json!(error_info.attempt_number),
466 )
467 .with_metadata(
468 "needs_escalation",
469 serde_json::json!(error_info.attempt_number >= 3),
470 ))
471 }
472
473 fn log_decision(&self, decision_info: DecisionInfo) -> Result<ToolResult, ToolError> {
475 if !Path::new("task_plan.md").exists() {
476 return Err(ToolError::execution_failed(
477 "task_plan.md not found. Run init_workflow first.",
478 ));
479 }
480
481 let mut content = fs::read_to_string("task_plan.md")?;
482
483 let decision_entry = format!(
485 "| {} | {} |",
486 decision_info.decision, decision_info.rationale
487 );
488
489 if let Some(pos) =
490 content.find("## Decisions Made\n| Decision | Rationale |\n|----------|-----------|")
491 {
492 let insert_pos = pos
493 + "## Decisions Made\n| Decision | Rationale |\n|----------|-----------|\n".len();
494 content.insert_str(insert_pos, &format!("{}\n", decision_entry));
495 }
496
497 fs::write("task_plan.md", content)?;
498
499 Ok(ToolResult::success(format!(
500 "📋 Decision Logged: {} (Rationale: {})",
501 decision_info.decision, decision_info.rationale
502 ))
503 .with_metadata("decision", serde_json::json!(decision_info.decision))
504 .with_metadata("rationale", serde_json::json!(decision_info.rationale)))
505 }
506
507 fn check_completion(&self) -> Result<ToolResult, ToolError> {
509 if !Path::new("task_plan.md").exists() {
510 return Err(ToolError::execution_failed(
511 "task_plan.md not found. Run init_workflow first.",
512 ));
513 }
514
515 let content = fs::read_to_string("task_plan.md")?;
516
517 let total_phases = content.matches("### Phase").count();
519 let complete_phases = content.matches("**Status:** complete").count();
520 let in_progress_phases = content.matches("**Status:** in_progress").count();
521 let pending_phases = content.matches("**Status:** pending").count();
522
523 let is_complete = complete_phases == total_phases && total_phases > 0;
524 let completion_percentage = if total_phases > 0 {
525 (complete_phases as f64 / total_phases as f64 * 100.0) as u32
526 } else {
527 0
528 };
529
530 let status_message = if is_complete {
531 "🎉 ALL PHASES COMPLETE! Task ready for delivery."
532 } else {
533 "⏳ Task in progress. Do not stop until all phases are complete."
534 };
535
536 Ok(ToolResult::success(format!(
537 "� Task Completion Status:\n\n{}\n\n📈 Progress: {}% ({}/{} phases complete)\n📋 Breakdown: {} complete, {} in progress, {} pending",
538 status_message,
539 completion_percentage,
540 complete_phases,
541 total_phases,
542 complete_phases,
543 in_progress_phases,
544 pending_phases
545 ))
546 .with_metadata("total_phases", serde_json::json!(total_phases))
547 .with_metadata("complete_phases", serde_json::json!(complete_phases))
548 .with_metadata("in_progress_phases", serde_json::json!(in_progress_phases))
549 .with_metadata("pending_phases", serde_json::json!(pending_phases))
550 .with_metadata("is_complete", serde_json::json!(is_complete))
551 .with_metadata("completion_percentage", serde_json::json!(completion_percentage)))
552 }
553
554 fn update_phase(&self, phase_update: PhaseUpdate) -> Result<ToolResult, ToolError> {
556 if !Path::new("task_plan.md").exists() {
557 return Err(ToolError::execution_failed(
558 "task_plan.md not found. Run init_workflow first.",
559 ));
560 }
561
562 let content = fs::read_to_string("task_plan.md")?;
563
564 let phase_pattern = format!("### Phase {}", phase_update.phase_number);
566 let mut updated_content = content.clone();
567
568 if let Some(phase_start) = updated_content.find(&phase_pattern) {
570 let search_start = phase_start + phase_pattern.len();
572 let next_phase_pos = updated_content
573 .get(search_start..)
574 .and_then(|s| s.find("### Phase"))
575 .map(|pos| search_start + pos)
576 .unwrap_or(updated_content.len());
577
578 let phase_section = updated_content
579 .get(phase_start..next_phase_pos)
580 .unwrap_or("");
581
582 let old_status_pattern = "**Status:**";
584 if let Some(status_pos) = phase_section.find(old_status_pattern) {
585 let status_line_start = phase_start + status_pos;
586 let status_line_end = updated_content
587 .get(status_line_start..)
588 .and_then(|s| s.find('\n'))
589 .map(|pos| status_line_start + pos)
590 .unwrap_or(updated_content.len());
591
592 let new_status_line = format!("- **Status:** {}", phase_update.status);
593 updated_content.replace_range(status_line_start..status_line_end, &new_status_line);
594 }
595 }
596 fs::write("task_plan.md", updated_content)?;
597
598 if phase_update.status == "in_progress" {
600 let _ = self.update_current_phase(phase_update.phase_number);
601 }
602
603 Ok(ToolResult::success(format!(
604 "✅ Phase {} status updated to: {}",
605 phase_update.phase_number, phase_update.status
606 ))
607 .with_metadata("phase", serde_json::json!(phase_update.phase_number))
608 .with_metadata("status", serde_json::json!(phase_update.status)))
609 }
610
611 fn add_finding(&self, finding: &str) -> Result<ToolResult, ToolError> {
612 if !Path::new("findings.md").exists() {
613 return Err(ToolError::execution_failed(
614 "findings.md not found. Run init_workflow first.",
615 ));
616 }
617
618 let mut content = fs::read_to_string("findings.md")?;
619
620 let timestamp = chrono::Utc::now().format("%H:%M").to_string();
621 let finding_entry = format!("- [{}] {}", timestamp, finding);
622
623 if let Some(pos) = content.find("## Research Findings\n-") {
624 let insert_pos = pos + "## Research Findings\n".len();
625 content.insert_str(insert_pos, &format!("{}\n", finding_entry));
626 } else {
627 content.push_str(&format!("\n{}\n", finding_entry));
628 }
629
630 fs::write("findings.md", content)?;
631
632 Ok(
633 ToolResult::success(format!("💡 Finding saved: {}", finding))
634 .with_metadata("timestamp", serde_json::json!(timestamp))
635 .with_metadata("finding", serde_json::json!(finding)),
636 )
637 }
638
639 fn add_progress(&self, progress_entry: &str) -> Result<ToolResult, ToolError> {
640 if !Path::new("progress.md").exists() {
641 return Err(ToolError::execution_failed(
642 "progress.md not found. Run init_workflow first.",
643 ));
644 }
645
646 let mut content = fs::read_to_string("progress.md")?;
647
648 let timestamp = chrono::Utc::now().format("%H:%M").to_string();
649 let progress_line = format!("- [{}] {}", timestamp, progress_entry);
650
651 if let Some(pos) = content.find("### Actions Taken\n-") {
652 let insert_pos = pos + "### Actions Taken\n".len();
653 content.insert_str(insert_pos, &format!("{}\n", progress_line));
654 } else {
655 content.push_str(&format!("\n{}\n", progress_line));
656 }
657
658 fs::write("progress.md", content)?;
659
660 Ok(
661 ToolResult::success(format!("📝 Progress logged: {}", progress_entry))
662 .with_metadata("timestamp", serde_json::json!(timestamp))
663 .with_metadata("progress", serde_json::json!(progress_entry)),
664 )
665 }
666
667 fn extract_goal(&self, content: &str) -> String {
669 content
670 .lines()
671 .skip_while(|line| !line.starts_with("## Goal"))
672 .nth(1)
673 .unwrap_or("[Goal not found]")
674 .trim()
675 .to_string()
676 }
677
678 fn extract_current_phase(&self, content: &str) -> String {
679 content
680 .lines()
681 .skip_while(|line| !line.starts_with("## Current Phase"))
682 .nth(1)
683 .unwrap_or("Phase 1")
684 .trim()
685 .to_string()
686 }
687
688 fn extract_pending_tasks(&self, content: &str) -> Vec<String> {
689 content
690 .lines()
691 .filter(|line| line.contains("- [ ]"))
692 .map(|line| line.trim().to_string())
693 .collect()
694 }
695
696 fn log_context_refresh(&self, reason: &str) -> Result<(), std::io::Error> {
697 if !Path::new("progress.md").exists() {
698 return Ok(());
699 }
700
701 let mut content = fs::read_to_string("progress.md")?;
702 let timestamp = chrono::Utc::now().format("%H:%M").to_string();
703 let refresh_entry = format!("| {} | {} | Context refreshed |", timestamp, reason);
704
705 if let Some(pos) = content.find("### Context Refreshes\n| Time | Reason | Action |") {
706 let insert_pos = pos + "### Context Refreshes\n| Time | Reason | Action |\n|------|--------|---------|\n".len();
707 content.insert_str(insert_pos, &format!("{}\n", refresh_entry));
708 fs::write("progress.md", content)?;
709 }
710
711 Ok(())
712 }
713
714 fn increment_visual_operations(&self) -> Result<(), std::io::Error> {
715 if !Path::new("progress.md").exists() {
716 return Ok(());
717 }
718
719 let content = fs::read_to_string("progress.md")?;
720
721 let timestamp = chrono::Utc::now().format("%H:%M").to_string();
723 let updated_content = content.replace(
724 "### Actions Taken\n-",
725 &format!(
726 "### Actions Taken\n- [{}] Visual operation recorded (2-Action Rule tracking)\n-",
727 timestamp
728 ),
729 );
730
731 fs::write("progress.md", updated_content)?;
732 Ok(())
733 }
734
735 fn log_error_to_progress(&self, error_info: &ErrorInfo) -> Result<(), std::io::Error> {
736 if !Path::new("progress.md").exists() {
737 return Ok(());
738 }
739
740 let mut content = fs::read_to_string("progress.md")?;
741 let timestamp = chrono::Utc::now().format("%H:%M").to_string();
742 let error_entry = format!(
743 "| {} | {} | {} | {} |",
744 timestamp,
745 error_info.error_description,
746 error_info.attempt_number,
747 error_info.resolution.as_deref().unwrap_or("In progress")
748 );
749
750 if let Some(pos) = content.find("### Error Log\n| Time | Error | Attempt | Resolution |") {
751 let insert_pos = pos + "### Error Log\n| Time | Error | Attempt | Resolution |\n|------|-------|---------|------------|\n".len();
752 content.insert_str(insert_pos, &format!("{}\n", error_entry));
753 fs::write("progress.md", content)?;
754 }
755
756 Ok(())
757 }
758
759 fn update_current_phase(&self, phase_number: u32) -> Result<(), std::io::Error> {
760 if !Path::new("task_plan.md").exists() {
761 return Ok(());
762 }
763
764 let content = fs::read_to_string("task_plan.md")?;
765 let updated_content = content.replace(
766 "## Current Phase\nPhase",
767 &format!("## Current Phase\nPhase {}", phase_number),
768 );
769
770 fs::write("task_plan.md", updated_content)?;
771 Ok(())
772 }
773}