diff options
| author | soryu <soryu@soryu.co> | 2026-05-17 21:23:20 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-05-17 21:23:20 +0100 |
| commit | 0d996cf7590e3e52f424859c7d6f0e68640f119e (patch) | |
| tree | 0f3898d9e2e2a3c312358dbf70c44f4ab1cf3648 /makima/src/llm/phase_guidance.rs | |
| parent | ce29ae801bcc5a0ba76d5a8d1565242ab267a47d (diff) | |
| download | soryu-0d996cf7590e3e52f424859c7d6f0e68640f119e.tar.gz soryu-0d996cf7590e3e52f424859c7d6f0e68640f119e.zip | |
chore: remove LLM module + all dependent surfaces (#135)
Wholesale removal of the LLM integration layer. ~14,200 LOC deleted
across backend and frontend. All chat-driven UIs go with it.
## Backend
- Delete `src/llm/` (7,400 LOC): claude/groq clients, contract_tools,
contract_evaluator, discuss_tools, mesh_tools, phase_guidance,
task_output, templates, markdown round-trip, tools, transcript_analyzer.
- Delete handlers wholly dependent on LLM:
- `chat.rs` (file-level LLM chat at /files/{id}/chat)
- `mesh_chat.rs` (mesh & task LLM chat + history)
- `templates.rs` (/contract-types listing)
- Strip LLM uses from `mesh_daemon.rs`:
- `compute_action_directive` (used phase_guidance::check_deliverables_met
to nudge supervisors with "all tasks done" messages). The auto-PR
path below still fires when all tasks finish, so no behaviour lost.
- `crate::llm::markdown_to_body` → inline 1-line replacement that
wraps markdown content in a single BodyElement::Markdown. The
editor re-parses on display, so round-trip is preserved.
- Drop routes: /files/{id}/chat, /mesh/chat, /mesh/chat/history,
/mesh/tasks/{id}/chat, /contract-types.
- Drop the matching openapi registrations.
## Frontend
- Delete components that were LLM-only:
- `mesh/UnifiedMeshChatInput.tsx`
- `listen/DiscussContractModal.tsx`
- `listen/TranscriptAnalysisPanel.tsx`
- `listen/ContractPickerModal.tsx`
- `files/CliInput.tsx`
- Delete the entire /listen page (its primary value-add was
voice → LLM analysis → contract creation; without LLM the page is
just a transcript display with no obvious user purpose).
- Delete `hooks/useMeshChatHistory.ts` and `lib/listenApi.ts`
(transcript-analysis API client to the already-Phase-5-removed
listen handlers).
- Strip api.ts of LLM exports: LlmModel, ChatMessage/Request/Response,
UserQuestion/Answer, chatWithFile, MeshChat* types & functions,
getMeshChatHistory, clearMeshChatHistory, chatWithMeshContext,
ContractTypeTemplate, listContractTypes, chatWithContract,
getContractChatHistory, clearContractChatHistory, discussContract,
PhaseDefinition, DeliverableDefinition.
- mesh.tsx: drop UnifiedMeshChatInput render + the chatContext memo +
handleTaskUpdatedFromCli (only consumer was the input).
- files.tsx: drop CliInput render + handleGenerateFromElement +
handleBodyUpdate + handleClearFocus + suggestedPrompt state (all
CliInput-only).
- NavStrip: drop the /listen link.
- main.tsx: drop the /listen route.
## Net diff: 37 files changed, 58 insertions, 14,281 deletions.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'makima/src/llm/phase_guidance.rs')
| -rw-r--r-- | makima/src/llm/phase_guidance.rs | 1032 |
1 files changed, 0 insertions, 1032 deletions
diff --git a/makima/src/llm/phase_guidance.rs b/makima/src/llm/phase_guidance.rs deleted file mode 100644 index 712e8bb..0000000 --- a/makima/src/llm/phase_guidance.rs +++ /dev/null @@ -1,1032 +0,0 @@ -//! Phase guidance and deliverables tracking for contract management. -//! -//! This module provides structured guidance for each contract phase, tracking -//! expected deliverables and completion criteria. -//! -//! ## Contract Types -//! -//! ### Simple -//! - **Plan phase**: One required deliverable: "Plan" -//! - **Execute phase**: One required deliverable: "PR" -//! -//! ### Specification -//! - **Research phase**: One required deliverable: "Research Notes" -//! - **Specify phase**: One required deliverable: "Requirements Document" -//! - **Plan phase**: One required deliverable: "Plan" -//! - **Execute phase**: One required deliverable: "PR" -//! - **Review phase**: One required deliverable: "Release Notes" -//! -//! ### Execute -//! - **Execute phase only**: No deliverables at all - -use serde::{Deserialize, Serialize}; -use utoipa::ToSchema; - -/// Priority level for deliverables -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum DeliverablePriority { - /// Must be completed before advancing phase - Required, - /// Strongly suggested for phase completion - Recommended, - /// Nice to have, not blocking - Optional, -} - -/// A deliverable for a phase -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -pub struct Deliverable { - /// Unique identifier for the deliverable - pub id: String, - /// Display name - pub name: String, - /// Priority level - pub priority: DeliverablePriority, - /// Brief description of purpose - pub description: String, -} - -/// Expected deliverables for a phase -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -pub struct PhaseDeliverables { - /// Phase name - pub phase: String, - /// Deliverables for this phase - pub deliverables: Vec<Deliverable>, - /// Whether a repository is required for this phase - pub requires_repository: bool, - /// Whether tasks should be completed in this phase - pub requires_tasks: bool, - /// Guidance text for this phase - pub guidance: String, -} - -/// Status of a deliverable -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -pub struct DeliverableStatus { - /// Deliverable ID - pub id: String, - /// Display name - pub name: String, - /// Priority - pub priority: DeliverablePriority, - /// Whether it has been completed - pub completed: bool, -} - -/// Checklist for phase completion -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -pub struct PhaseChecklist { - /// Current phase - pub phase: String, - /// Deliverable status list - pub deliverables: Vec<DeliverableStatus>, - /// Whether repository is configured - pub has_repository: bool, - /// Whether repository was required - pub repository_required: bool, - /// Task statistics (for execute phase) - pub task_stats: Option<TaskStats>, - /// Overall completion percentage (0-100) - pub completion_percentage: u8, - /// Summary message - pub summary: String, - /// Suggestions for next actions - pub suggestions: Vec<String>, -} - -/// Task statistics for execute phase -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -pub struct TaskStats { - pub total: usize, - pub pending: usize, - pub running: usize, - pub done: usize, - pub failed: usize, -} - -/// Minimal task info for checklist building -pub struct TaskInfo { - pub name: String, - pub status: String, -} - -use crate::db::models::PhaseConfig; - -/// Get phase deliverables configuration (legacy, defaults to "simple" contract type) -pub fn get_phase_deliverables(phase: &str) -> PhaseDeliverables { - get_phase_deliverables_for_type(phase, "simple") -} - -/// Get phase deliverables configuration for a specific contract type -pub fn get_phase_deliverables_for_type(phase: &str, contract_type: &str) -> PhaseDeliverables { - match contract_type { - "execute" => get_execute_type_deliverables(phase), - "specification" => get_specification_type_deliverables(phase), - "simple" | _ => get_simple_type_deliverables(phase), - } -} - -/// Get phase deliverables from a custom PhaseConfig -/// This is used for contracts with custom templates -pub fn get_phase_deliverables_from_config(phase: &str, config: &PhaseConfig) -> PhaseDeliverables { - // Check if this phase exists in the config - let phase_exists = config.phases.iter().any(|p| p.id == phase); - if !phase_exists { - return PhaseDeliverables { - phase: phase.to_string(), - deliverables: vec![], - requires_repository: false, - requires_tasks: false, - guidance: format!("Phase '{}' is not defined in this contract template", phase), - }; - } - - // Get deliverables for this phase from the config - let deliverables: Vec<Deliverable> = config - .deliverables - .get(phase) - .map(|defs| { - defs.iter() - .map(|d| Deliverable { - id: d.id.clone(), - name: d.name.clone(), - priority: match d.priority.as_str() { - "recommended" => DeliverablePriority::Recommended, - "optional" => DeliverablePriority::Optional, - _ => DeliverablePriority::Required, - }, - description: format!("{} deliverable", d.name), - }) - .collect() - }) - .unwrap_or_default(); - - // Determine if repository is required (typically for execute-like phases) - let requires_repository = phase == "execute" || phase == "plan"; - - // Determine if tasks are required (typically for execute phase) - let requires_tasks = phase == "execute"; - - // Find the phase name for better guidance - let phase_name = config - .phases - .iter() - .find(|p| p.id == phase) - .map(|p| p.name.clone()) - .unwrap_or_else(|| phase.to_string()); - - let guidance = if deliverables.is_empty() { - format!("Complete the {} phase. No specific deliverables are required.", phase_name) - } else { - let deliverable_names: Vec<_> = deliverables.iter().map(|d| d.name.clone()).collect(); - format!( - "Complete the {} phase by producing the following deliverables: {}", - phase_name, - deliverable_names.join(", ") - ) - }; - - PhaseDeliverables { - phase: phase.to_string(), - deliverables, - requires_repository, - requires_tasks, - guidance, - } -} - -/// Get phase deliverables, checking custom config first, then falling back to built-in types -pub fn get_phase_deliverables_with_config( - phase: &str, - contract_type: &str, - phase_config: Option<&PhaseConfig>, -) -> PhaseDeliverables { - // If we have a custom phase config, use it - if let Some(config) = phase_config { - return get_phase_deliverables_from_config(phase, config); - } - - // Otherwise, fall back to built-in contract types - get_phase_deliverables_for_type(phase, contract_type) -} - -/// Get deliverables for 'simple' contract type -fn get_simple_type_deliverables(phase: &str) -> PhaseDeliverables { - match phase { - "plan" => PhaseDeliverables { - phase: "plan".to_string(), - deliverables: vec![Deliverable { - id: "plan-document".to_string(), - name: "Plan".to_string(), - priority: DeliverablePriority::Required, - description: "Implementation plan detailing the approach and tasks".to_string(), - }], - requires_repository: true, - requires_tasks: false, - guidance: "Create a plan document that outlines the implementation approach. A repository must be configured before moving to Execute phase.".to_string(), - }, - "execute" => PhaseDeliverables { - phase: "execute".to_string(), - deliverables: vec![Deliverable { - id: "pull-request".to_string(), - name: "Pull Request".to_string(), - priority: DeliverablePriority::Required, - description: "Pull request with the implemented changes".to_string(), - }], - requires_repository: true, - requires_tasks: true, - guidance: "Execute the plan and create a PR with the implemented changes. Complete all tasks to finish the contract.".to_string(), - }, - _ => PhaseDeliverables { - phase: phase.to_string(), - deliverables: vec![], - requires_repository: false, - requires_tasks: false, - guidance: "Unknown phase for simple contract type".to_string(), - }, - } -} - -/// Get deliverables for 'specification' contract type -fn get_specification_type_deliverables(phase: &str) -> PhaseDeliverables { - match phase { - "research" => PhaseDeliverables { - phase: "research".to_string(), - deliverables: vec![Deliverable { - id: "research-notes".to_string(), - name: "Research Notes".to_string(), - priority: DeliverablePriority::Required, - description: "Document findings and insights during research".to_string(), - }], - requires_repository: false, - requires_tasks: false, - guidance: "Focus on understanding the problem space and document your findings in the Research Notes before moving to Specify phase.".to_string(), - }, - "specify" => PhaseDeliverables { - phase: "specify".to_string(), - deliverables: vec![Deliverable { - id: "requirements-document".to_string(), - name: "Requirements Document".to_string(), - priority: DeliverablePriority::Required, - description: "Define functional and non-functional requirements".to_string(), - }], - requires_repository: false, - requires_tasks: false, - guidance: "Define what needs to be built with clear requirements in the Requirements Document. Ensure specifications are detailed enough for planning.".to_string(), - }, - "plan" => PhaseDeliverables { - phase: "plan".to_string(), - deliverables: vec![Deliverable { - id: "plan-document".to_string(), - name: "Plan".to_string(), - priority: DeliverablePriority::Required, - description: "Implementation plan detailing the approach and tasks".to_string(), - }], - requires_repository: true, - requires_tasks: false, - guidance: "Create a plan document that outlines the implementation approach. A repository must be configured before moving to Execute phase.".to_string(), - }, - "execute" => PhaseDeliverables { - phase: "execute".to_string(), - deliverables: vec![Deliverable { - id: "pull-request".to_string(), - name: "Pull Request".to_string(), - priority: DeliverablePriority::Required, - description: "Pull request with the implemented changes".to_string(), - }], - requires_repository: true, - requires_tasks: true, - guidance: "Execute the plan and create a PR with the implemented changes. Complete all tasks before moving to Review phase.".to_string(), - }, - "review" => PhaseDeliverables { - phase: "review".to_string(), - deliverables: vec![Deliverable { - id: "release-notes".to_string(), - name: "Release Notes".to_string(), - priority: DeliverablePriority::Required, - description: "Document changes for release communication".to_string(), - }], - requires_repository: false, - requires_tasks: false, - guidance: "Review completed work and document the release in the Release Notes. The contract can be completed after review.".to_string(), - }, - _ => PhaseDeliverables { - phase: phase.to_string(), - deliverables: vec![], - requires_repository: false, - requires_tasks: false, - guidance: "Unknown phase for specification contract type".to_string(), - }, - } -} - -/// Get deliverables for 'execute' contract type -fn get_execute_type_deliverables(phase: &str) -> PhaseDeliverables { - match phase { - "execute" => PhaseDeliverables { - phase: "execute".to_string(), - deliverables: vec![], // No deliverables for execute-only contract type - requires_repository: true, - requires_tasks: true, - guidance: "Execute the tasks directly. No deliverable documents are required for this contract type.".to_string(), - }, - _ => PhaseDeliverables { - phase: phase.to_string(), - deliverables: vec![], - requires_repository: false, - requires_tasks: false, - guidance: "The 'execute' contract type only supports the 'execute' phase.".to_string(), - }, - } -} - -/// Build a phase checklist comparing expected vs actual deliverables -pub fn get_phase_checklist_for_type( - phase: &str, - completed_deliverables: &[String], - tasks: &[TaskInfo], - has_repository: bool, - contract_type: &str, -) -> PhaseChecklist { - let phase_config = get_phase_deliverables_for_type(phase, contract_type); - - // Build deliverable status list - let deliverables: Vec<DeliverableStatus> = phase_config - .deliverables - .iter() - .map(|d| DeliverableStatus { - id: d.id.clone(), - name: d.name.clone(), - priority: d.priority, - completed: completed_deliverables.contains(&d.id), - }) - .collect(); - - // Calculate task stats for execute phase - let task_stats = if phase == "execute" { - let total = tasks.len(); - let pending = tasks.iter().filter(|t| t.status == "pending").count(); - let running = tasks.iter().filter(|t| t.status == "running").count(); - let done = tasks.iter().filter(|t| t.status == "done").count(); - let failed = tasks - .iter() - .filter(|t| t.status == "failed" || t.status == "error") - .count(); - - Some(TaskStats { - total, - pending, - running, - done, - failed, - }) - } else { - None - }; - - // Calculate completion percentage - let mut completed_items = 0; - let mut total_items = 0; - - // Count required and recommended deliverables (not optional) - for status in &deliverables { - if status.priority != DeliverablePriority::Optional { - total_items += 1; - if status.completed { - completed_items += 1; - } - } - } - - // Count repository if required - if phase_config.requires_repository { - total_items += 1; - if has_repository { - completed_items += 1; - } - } - - // Count tasks if in execute phase - if let Some(ref stats) = task_stats { - if stats.total > 0 { - total_items += 1; - if stats.done == stats.total && stats.total > 0 { - completed_items += 1; - } - } - } - - let completion_percentage = if total_items > 0 { - ((completed_items as f64 / total_items as f64) * 100.0) as u8 - } else { - 100 // No requirements means complete - }; - - // Generate suggestions - let mut suggestions = Vec::new(); - - // Suggest missing deliverables - for status in &deliverables { - if !status.completed { - match status.priority { - DeliverablePriority::Required => { - suggestions.push(format!( - "Mark '{}' as complete using mark_deliverable_complete (required)", - status.name - )); - } - DeliverablePriority::Recommended => { - suggestions.push(format!( - "Consider completing '{}' (recommended)", - status.name - )); - } - DeliverablePriority::Optional => {} - } - } - } - - // Suggest repository if needed - if phase_config.requires_repository && !has_repository { - suggestions.push("Configure a repository for task execution".to_string()); - } - - // Suggest task actions for execute phase - if let Some(ref stats) = task_stats { - if stats.total == 0 { - suggestions.push("Create tasks to implement the plan".to_string()); - } else if stats.pending > 0 { - suggestions.push(format!("Run {} pending task(s)", stats.pending)); - } else if stats.running > 0 { - suggestions.push(format!( - "Wait for {} running task(s) to complete", - stats.running - )); - } else if stats.failed > 0 { - suggestions.push(format!("Address {} failed task(s)", stats.failed)); - } else if stats.done == stats.total && stats.total > 0 { - suggestions.push("All tasks complete. Mark deliverables and advance phase.".to_string()); - } - } - - // Generate summary - let summary = generate_phase_summary( - phase, - &deliverables, - has_repository, - &task_stats, - completion_percentage, - ); - - PhaseChecklist { - phase: phase.to_string(), - deliverables, - has_repository, - repository_required: phase_config.requires_repository, - task_stats, - completion_percentage, - summary, - suggestions, - } -} - -fn generate_phase_summary( - phase: &str, - deliverables: &[DeliverableStatus], - has_repository: bool, - task_stats: &Option<TaskStats>, - completion_percentage: u8, -) -> String { - let completed_count = deliverables.iter().filter(|d| d.completed).count(); - let total_count = deliverables.len(); - - match phase { - "research" => { - if completed_count == 0 { - "Research phase needs documentation. Mark deliverables complete when ready." - .to_string() - } else { - format!( - "{}/{} deliverables complete. Ready to transition to Specify phase.", - completed_count, total_count - ) - } - } - "specify" => { - let has_required = deliverables - .iter() - .filter(|d| d.priority == DeliverablePriority::Required) - .all(|d| d.completed); - - if !has_required { - "Specify phase requires completing the Requirements Document deliverable." - .to_string() - } else { - "Specifications ready. Consider transitioning to Plan phase.".to_string() - } - } - "plan" => { - let has_required = deliverables - .iter() - .filter(|d| d.priority == DeliverablePriority::Required) - .all(|d| d.completed); - - if !has_required { - "Plan phase requires completing the Plan deliverable.".to_string() - } else if !has_repository { - "Repository not configured. Configure a repository before Execute phase." - .to_string() - } else { - "Planning complete. Ready to transition to Execute phase.".to_string() - } - } - "execute" => { - if let Some(stats) = task_stats { - if stats.total == 0 { - "No tasks created. Create tasks to implement the plan.".to_string() - } else if stats.done == stats.total { - "All tasks complete! Mark deliverables and advance to Review phase (or complete contract).".to_string() - } else { - format!( - "{}/{} tasks completed ({}% done)", - stats.done, - stats.total, - if stats.total > 0 { - (stats.done * 100) / stats.total - } else { - 0 - } - ) - } - } else { - "Execute phase in progress.".to_string() - } - } - "review" => { - let has_required = deliverables - .iter() - .filter(|d| d.priority == DeliverablePriority::Required) - .all(|d| d.completed); - - if !has_required { - "Review phase requires completing the Release Notes deliverable.".to_string() - } else { - "Review documentation complete. Contract can be marked as done.".to_string() - } - } - _ => format!("Phase {} - {}% complete", phase, completion_percentage), - } -} - -/// Result of checking if deliverables are met for the current phase -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -pub struct DeliverableCheckResult { - /// Whether all required deliverables are met - pub deliverables_met: bool, - /// Whether the phase is ready to advance (includes all readiness checks) - pub ready_to_advance: bool, - /// Current phase - pub phase: String, - /// Next phase (if available) - pub next_phase: Option<String>, - /// List of required deliverables and their status - pub required_deliverables: Vec<DeliverableItem>, - /// List of what's missing (if any) - pub missing: Vec<String>, - /// Human-readable summary - pub summary: String, - /// Whether auto-progress is recommended - pub auto_progress_recommended: bool, -} - -/// A single deliverable item status -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -pub struct DeliverableItem { - /// ID of the deliverable - pub id: String, - /// Name of the deliverable - pub name: String, - /// Type: "deliverable", "repository", "tasks" - pub deliverable_type: String, - /// Whether it's met - pub met: bool, - /// Additional details - pub details: Option<String>, -} - -/// Check if all required deliverables for the current phase are met -pub fn check_deliverables_met( - phase: &str, - contract_type: &str, - completed_deliverables: &[String], - tasks: &[TaskInfo], - has_repository: bool, -) -> DeliverableCheckResult { - let mut required_items = Vec::new(); - let mut missing = Vec::new(); - - // Get the deliverables for this contract type and phase - let phase_config = get_phase_deliverables_for_type(phase, contract_type); - - // Check required deliverables for this phase - for deliverable in &phase_config.deliverables { - if deliverable.priority == DeliverablePriority::Required { - let is_complete = completed_deliverables.contains(&deliverable.id); - - required_items.push(DeliverableItem { - id: deliverable.id.clone(), - name: deliverable.name.clone(), - deliverable_type: "deliverable".to_string(), - met: is_complete, - details: if is_complete { - Some("Marked complete".to_string()) - } else { - None - }, - }); - - if !is_complete { - missing.push(format!( - "Mark '{}' as complete (required)", - deliverable.name - )); - } - } - } - - // Check repository for phases that require it - if phase_config.requires_repository { - required_items.push(DeliverableItem { - id: "repository".to_string(), - name: "Repository".to_string(), - deliverable_type: "repository".to_string(), - met: has_repository, - details: if has_repository { - Some("Repository configured".to_string()) - } else { - None - }, - }); - - if !has_repository { - missing.push("Configure a repository".to_string()); - } - } - - // Check tasks for execute phase - if phase_config.requires_tasks { - let total_tasks = tasks.len(); - let done_tasks = tasks.iter().filter(|t| t.status == "done").count(); - let tasks_complete = total_tasks > 0 && done_tasks == total_tasks; - - required_items.push(DeliverableItem { - id: "tasks".to_string(), - name: "Tasks Completed".to_string(), - deliverable_type: "tasks".to_string(), - met: tasks_complete, - details: Some(format!("{}/{} tasks done", done_tasks, total_tasks)), - }); - - if !tasks_complete { - if total_tasks == 0 { - missing.push("Create and complete tasks".to_string()); - } else { - missing.push(format!( - "Complete remaining {} task(s)", - total_tasks - done_tasks - )); - } - } - } - - let deliverables_met = required_items.iter().all(|d| d.met); - let next_phase = get_next_phase_for_contract(contract_type, phase); - let ready_to_advance = deliverables_met && next_phase.is_some(); - - let summary = if deliverables_met { - if let Some(ref next) = next_phase { - format!( - "All deliverables met for {} phase. Ready to advance to {} phase.", - phase, next - ) - } else { - format!( - "All deliverables met for {} phase. This is the final phase.", - phase - ) - } - } else { - format!( - "{} deliverable(s) still needed for {} phase.", - missing.len(), - phase - ) - }; - - DeliverableCheckResult { - deliverables_met, - ready_to_advance, - phase: phase.to_string(), - next_phase, - required_deliverables: required_items, - missing, - summary, - auto_progress_recommended: deliverables_met && ready_to_advance, - } -} - -/// Get the next phase based on contract type -pub fn get_next_phase_for_contract(contract_type: &str, current_phase: &str) -> Option<String> { - match contract_type { - "simple" => match current_phase { - "plan" => Some("execute".to_string()), - "execute" => None, // Terminal phase for simple contracts - _ => None, - }, - "execute" => None, // Execute-only contracts don't have phase transitions - "specification" | _ => match current_phase { - "research" => Some("specify".to_string()), - "specify" => Some("plan".to_string()), - "plan" => Some("execute".to_string()), - "execute" => Some("review".to_string()), - "review" => None, // Final phase - _ => None, - }, - } -} - -/// Determine if the contract should auto-progress to the next phase -pub fn should_auto_progress( - phase: &str, - contract_type: &str, - completed_deliverables: &[String], - tasks: &[TaskInfo], - has_repository: bool, - autonomous_loop: bool, -) -> AutoProgressDecision { - let check = check_deliverables_met( - phase, - contract_type, - completed_deliverables, - tasks, - has_repository, - ); - - if !check.deliverables_met { - return AutoProgressDecision { - should_progress: false, - next_phase: None, - reason: format!("Deliverables not met: {}", check.missing.join(", ")), - action: AutoProgressAction::WaitForDeliverables, - }; - } - - if check.next_phase.is_none() { - return AutoProgressDecision { - should_progress: false, - next_phase: None, - reason: "This is the terminal phase. Contract can be completed.".to_string(), - action: AutoProgressAction::CompleteContract, - }; - } - - if autonomous_loop { - AutoProgressDecision { - should_progress: true, - next_phase: check.next_phase, - reason: "All deliverables met and autonomous_loop is enabled.".to_string(), - action: AutoProgressAction::AdvancePhase, - } - } else { - AutoProgressDecision { - should_progress: false, - next_phase: check.next_phase, - reason: "All deliverables met. Suggest advancing to next phase.".to_string(), - action: AutoProgressAction::SuggestAdvance, - } - } -} - -/// Result of auto-progress decision -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AutoProgressDecision { - /// Whether to automatically progress - pub should_progress: bool, - /// The next phase to progress to - pub next_phase: Option<String>, - /// Reason for the decision - pub reason: String, - /// Recommended action - pub action: AutoProgressAction, -} - -/// Actions that can be taken based on auto-progress decision -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum AutoProgressAction { - /// Wait for required deliverables - WaitForDeliverables, - /// Automatically advance to next phase - AdvancePhase, - /// Suggest user to advance (when not autonomous) - SuggestAdvance, - /// Contract is complete, mark as done - CompleteContract, -} - -/// Generate enhanced prompt guidance for deliverable checking -pub fn generate_deliverable_prompt_guidance( - phase: &str, - contract_type: &str, - check_result: &DeliverableCheckResult, -) -> String { - let mut guidance = String::new(); - - guidance.push_str("\n## Phase Deliverables Status\n\n"); - guidance.push_str(&format!( - "**Current Phase**: {} | **Contract Type**: {}\n\n", - capitalize(phase), - contract_type - )); - - // Show required deliverables checklist - guidance.push_str("### Required Deliverables Checklist\n"); - for item in &check_result.required_deliverables { - let status = if item.met { "[x]" } else { "[ ]" }; - let details = item - .details - .as_ref() - .map(|d| format!(" - {}", d)) - .unwrap_or_default(); - guidance.push_str(&format!( - "{} **{}** ({}){}\n", - status, item.name, item.deliverable_type, details - )); - } - - // Show status and next actions - guidance.push_str("\n### Status\n"); - if check_result.deliverables_met { - guidance.push_str("**All deliverables are met.**\n\n"); - if let Some(ref next) = check_result.next_phase { - guidance.push_str(&format!("Ready to advance to **{}** phase.\n", next)); - if check_result.auto_progress_recommended { - guidance.push_str(&format!("\n**ACTION REQUIRED**: Since all deliverables are met, you should call `advance_phase` with `new_phase=\"{}\"` to progress the contract.\n", next)); - } - } else { - guidance.push_str( - "This is the terminal phase. The contract can be marked as completed.\n", - ); - } - } else { - guidance.push_str("**Deliverables not yet met.**\n\n"); - guidance.push_str("Missing:\n"); - for item in &check_result.missing { - guidance.push_str(&format!("- {}\n", item)); - } - guidance.push_str( - "\nUse `mark_deliverable_complete` to mark deliverables as complete when ready.\n", - ); - } - - guidance -} - -/// Format checklist as markdown for LLM context -pub fn format_checklist_markdown(checklist: &PhaseChecklist) -> String { - let mut md = format!( - "## Phase Progress ({} Phase)\n\n", - capitalize(&checklist.phase) - ); - - // Deliverables - md.push_str("### Deliverables\n"); - for status in &checklist.deliverables { - let check = if status.completed { "+" } else { "-" }; - let priority_label = match status.priority { - DeliverablePriority::Required => " (required)", - DeliverablePriority::Recommended => " (recommended)", - DeliverablePriority::Optional => " (optional)", - }; - - if status.completed { - md.push_str(&format!("[{}] {} - completed\n", check, status.name)); - } else { - md.push_str(&format!("[{}] {}{}\n", check, status.name, priority_label)); - } - } - - // Repository status - if checklist.repository_required { - let check = if checklist.has_repository { "+" } else { "-" }; - md.push_str(&format!("[{}] Repository configured (required)\n", check)); - } - - // Task stats for execute phase - if let Some(ref stats) = checklist.task_stats { - md.push_str("\n### Task Progress\n"); - md.push_str(&format!("- Total: {}\n", stats.total)); - md.push_str(&format!("- Done: {}\n", stats.done)); - if stats.pending > 0 { - md.push_str(&format!("- Pending: {}\n", stats.pending)); - } - if stats.running > 0 { - md.push_str(&format!("- Running: {}\n", stats.running)); - } - if stats.failed > 0 { - md.push_str(&format!("- Failed: {}\n", stats.failed)); - } - } - - // Summary - md.push_str(&format!( - "\n**Status**: {} ({}% complete)\n", - checklist.summary, checklist.completion_percentage - )); - - // Suggestions - if !checklist.suggestions.is_empty() { - md.push_str("\n**Next Steps**:\n"); - for suggestion in &checklist.suggestions { - md.push_str(&format!("- {}\n", suggestion)); - } - } - - md -} - -fn capitalize(s: &str) -> String { - let mut chars = s.chars(); - match chars.next() { - None => String::new(), - Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_get_phase_deliverables_simple() { - let plan = get_phase_deliverables_for_type("plan", "simple"); - assert_eq!(plan.phase, "plan"); - assert!(plan.requires_repository); - assert_eq!(plan.deliverables.len(), 1); - assert_eq!(plan.deliverables[0].id, "plan-document"); - assert_eq!(plan.deliverables[0].priority, DeliverablePriority::Required); - - let execute = get_phase_deliverables_for_type("execute", "simple"); - assert_eq!(execute.phase, "execute"); - assert!(execute.requires_repository); - assert!(execute.requires_tasks); - assert_eq!(execute.deliverables.len(), 1); - assert_eq!(execute.deliverables[0].id, "pull-request"); - } - - #[test] - fn test_get_phase_deliverables_specification() { - let research = get_phase_deliverables_for_type("research", "specification"); - assert_eq!(research.deliverables.len(), 1); - assert_eq!(research.deliverables[0].id, "research-notes"); - - let specify = get_phase_deliverables_for_type("specify", "specification"); - assert_eq!(specify.deliverables.len(), 1); - assert_eq!(specify.deliverables[0].id, "requirements-document"); - - let review = get_phase_deliverables_for_type("review", "specification"); - assert_eq!(review.deliverables.len(), 1); - assert_eq!(review.deliverables[0].id, "release-notes"); - } - - #[test] - fn test_get_phase_deliverables_execute_type() { - let execute = get_phase_deliverables_for_type("execute", "execute"); - assert!(execute.deliverables.is_empty()); - assert!(execute.requires_repository); - assert!(execute.requires_tasks); - } - - #[test] - fn test_check_deliverables_met() { - // No deliverables marked complete - let result = check_deliverables_met("plan", "simple", &[], &[], true); - assert!(!result.deliverables_met); - assert!(!result.missing.is_empty()); - - // Deliverable marked complete - let completed = vec!["plan-document".to_string()]; - let result = check_deliverables_met("plan", "simple", &completed, &[], true); - assert!(result.deliverables_met); - assert!(result.ready_to_advance); - } - - #[test] - fn test_phase_checklist() { - let completed = vec!["plan-document".to_string()]; - let checklist = get_phase_checklist_for_type("plan", &completed, &[], true, "simple"); - assert_eq!(checklist.completion_percentage, 100); - assert!(checklist.deliverables[0].completed); - } -} |
