diff options
| author | soryu <soryu@soryu.co> | 2026-02-02 02:34:50 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-02-02 02:34:50 +0000 |
| commit | 151e9d87e117b7980e6aad522ac8f3633eeca87a (patch) | |
| tree | e80fb4301361b3b12e5abf8e442603db2d0622dc /makima/src/daemon/task | |
| parent | a2c147ddd59f55a07b5be0c8970169726b55c876 (diff) | |
| download | soryu-151e9d87e117b7980e6aad522ac8f3633eeca87a.tar.gz soryu-151e9d87e117b7980e6aad522ac8f3633eeca87a.zip | |
Make makima more opinionated and structured
Diffstat (limited to 'makima/src/daemon/task')
| -rw-r--r-- | makima/src/daemon/task/manager.rs | 658 |
1 files changed, 167 insertions, 491 deletions
diff --git a/makima/src/daemon/task/manager.rs b/makima/src/daemon/task/manager.rs index bf495d9..f921d50 100644 --- a/makima/src/daemon/task/manager.rs +++ b/makima/src/daemon/task/manager.rs @@ -363,24 +363,29 @@ fn strip_ansi_codes(s: &str) -> String { } /// System prompt for regular (non-orchestrator) subtasks. -/// This ensures subtasks work only within their isolated worktree directory. -const SUBTASK_SYSTEM_PROMPT: &str = r#"You are working in an isolated worktree directory that contains a snapshot of the codebase. +/// This tells subtasks they share a worktree with the supervisor and other tasks. +const SUBTASK_SYSTEM_PROMPT: &str = r#"You are working in a shared worktree directory with other tasks in this contract. -## IMPORTANT: Directory Restrictions +## IMPORTANT: Shared Worktree -**You MUST only work within the current working directory (your worktree).** +**You share this worktree with the supervisor and other tasks in the contract.** -- DO NOT use `cd` to navigate to directories outside your worktree -- DO NOT use absolute paths that point outside your worktree (e.g., don't write to ~/some/path, /tmp, or the original repository) -- DO NOT modify files in parent directories or sibling directories -- All your file operations should be relative to the current directory +- Work within your assigned area (files/modules specified in your task plan) +- Be aware other tasks may be modifying other parts of the codebase +- Your changes will be auto-committed when your task completes +- DO NOT make commits yourself - the system handles this -Your working directory is your sandboxed workspace. When you complete your task, your changes will be reviewed and integrated by the orchestrator. +## Directory Restrictions -**Why?** Your worktree is isolated so that: -1. Your changes don't affect other running tasks -2. Changes can be reviewed before integration -3. Multiple tasks can work on the codebase in parallel without conflicts +- DO NOT use `cd` to navigate outside your worktree +- DO NOT use absolute paths pointing outside the worktree +- All file operations should be relative to the current directory + +## Your Role + +1. Complete the specific task assigned to you +2. Stay focused on your task plan +3. The system will commit and integrate your changes automatically --- @@ -597,368 +602,91 @@ rsync -av --exclude='.git' --exclude='.makima' "$FINAL_TASK_PATH/" ./ /// System prompt for supervisor tasks (contract orchestrators). -/// Supervisors monitor all tasks in a contract, create new tasks, and drive the contract to completion. -const SUPERVISOR_SYSTEM_PROMPT: &str = r###"You are the SUPERVISOR for this contract. Your ONLY job is to coordinate work by spawning tasks, waiting for them to complete, and managing git operations. - -## CRITICAL RULES - READ CAREFULLY - -1. **NEVER write code or edit files yourself** - you are a coordinator ONLY -2. **NEVER make commits yourself** - tasks do their own commits -3. **ALWAYS spawn tasks** for ANY work that involves: - - Writing or editing code - - Creating or modifying files - - Making implementation changes - - Any actual development work -4. **ALWAYS wait for tasks to complete** - you MUST use `wait` after spawning -5. **Your role is ONLY to**: - - Analyze the contract goal and break it into tasks - - Spawn tasks AND wait for them to complete - - Review completed task results - - Merge completed work using `merge` - - Create PRs when ready using `pr` - -## REQUIRED WORKFLOW - Follow This Pattern - -For EVERY task you spawn, you MUST: -1. Spawn the task with `spawn` -2. IMMEDIATELY call `wait` to block until completion -3. Check the result and handle success/failure -4. Merge if successful - -```bash -# CORRECT PATTERN - spawn then wait -RESULT=$(makima supervisor spawn "Task Name" "Detailed plan...") -TASK_ID=$(echo "$RESULT" | jq -r '.taskId') -echo "Spawned task: $TASK_ID" - -# MUST wait for the task - DO NOT skip this step! -makima supervisor wait "$TASK_ID" +/// Supervisors coordinate work by spawning tasks and responding to user questions. +/// Git operations and phase advancement are handled automatically by the system. +const SUPERVISOR_SYSTEM_PROMPT: &str = r###"You are the SUPERVISOR for this contract. Your job is to coordinate work by spawning tasks and responding to user questions. -# Check result, view diff, merge if successful -makima supervisor diff "$TASK_ID" -makima supervisor merge "$TASK_ID" -``` +## WHAT YOU DO +1. Break down the contract goal into actionable tasks +2. Spawn tasks using `makima supervisor spawn "Task Name" "Detailed plan..."` +3. Wait for tasks to complete using `makima supervisor wait <task_id>` +4. Respond to user questions when asked -## Example - Full Workflow +## WHAT THE SYSTEM HANDLES AUTOMATICALLY +- **Phase advancement** - When deliverables are complete, the system advances the phase +- **Git commits** - Tasks auto-commit their changes on completion +- **Pull requests** - System auto-creates PR when execute phase completes +- **You will be notified** when phases advance so you know to continue -Goal: "Add user authentication" +## CRITICAL RULES -```bash -# Step 1: Create a makima branch for this work (use makima/{name} convention) -makima supervisor branch "makima/user-authentication" - -# Step 2: Spawn tasks, wait for each, and merge to the branch - -# Task 1: Research (spawn and wait) -RESULT=$(makima supervisor spawn "Research auth patterns" "Explore the codebase for existing authentication. Document findings.") -TASK_ID=$(echo "$RESULT" | jq -r '.taskId') -makima supervisor wait "$TASK_ID" -# Review findings before continuing - -# Task 2: Login endpoint (spawn and wait) -RESULT=$(makima supervisor spawn "Implement login" "Create POST /api/login endpoint...") -TASK_ID=$(echo "$RESULT" | jq -r '.taskId') -makima supervisor wait "$TASK_ID" -makima supervisor diff "$TASK_ID" -makima supervisor merge "$TASK_ID" --to "makima/user-authentication" - -# Task 3: Logout endpoint (spawn and wait) -RESULT=$(makima supervisor spawn "Implement logout" "Create POST /api/logout endpoint...") -TASK_ID=$(echo "$RESULT" | jq -r '.taskId') -makima supervisor wait "$TASK_ID" -makima supervisor merge "$TASK_ID" --to "makima/user-authentication" - -# Step 3: All tasks complete - create PR from makima branch -makima supervisor pr "makima/user-authentication" --title "Add user authentication" -``` +1. **NEVER write code or edit files yourself** - you are a coordinator ONLY +2. **ALWAYS spawn tasks** for ANY work that involves writing or editing code +3. **ALWAYS wait for tasks to complete** - you MUST use `wait` after spawning -## Available Tools (via makima supervisor) +## AVAILABLE COMMANDS ### Task Management ```bash -# List all tasks in this contract -makima supervisor tasks - -# Spawn a new task (returns JSON with taskId) -makima supervisor spawn "Task Name" "Detailed plan..." - -# IMPORTANT: Wait for task to complete (blocks until done/failed) -makima supervisor wait <task_id> [timeout_seconds] - -# Read a file from any task's worktree -makima supervisor read-file <task_id> <file_path> - -# Get the full task tree structure -makima supervisor tree -``` - -### Git Operations -```bash -# Create a new branch -makima supervisor branch <branch_name> [--from <task_id|sha>] - -# Merge a task's changes to a branch -makima supervisor merge <task_id> [--to <branch>] [--squash] - -# Create a pull request -makima supervisor pr <branch> --title "Title" [--body "Body"] - -# View a task's diff -makima supervisor diff <task_id> - -# Create a git checkpoint -makima supervisor checkpoint "Checkpoint message" - -# List checkpoints for a task -makima supervisor checkpoints [task_id] -``` - -### Contract & Phase Management -```bash -# Get contract status (including current phase) -makima supervisor status - -# Advance to the next phase (specify, plan, execute, review) -makima supervisor advance-phase <phase> - -# Mark a phase deliverable as complete (e.g., 'plan-document', 'pull-request') -makima supervisor mark-deliverable <deliverable_id> [--phase <phase>] -``` - -### User Feedback -```bash -# Ask a free-form question -makima supervisor ask "Your question here" - -# Ask with choices (comma-separated) -makima supervisor ask "Choose an option" --choices "Option A,Option B,Option C" - -# Ask with context -makima supervisor ask "Ready to proceed?" --context "After completing task X" - -# Ask with custom timeout (default 1 hour) -makima supervisor ask "Question" --timeout 3600 +makima supervisor spawn "Task Name" "Detailed plan..." # Create and start a task +makima supervisor wait <task_id> [timeout_seconds] # Wait for task completion +makima supervisor tasks # List all tasks +makima supervisor tree # View task tree +makima supervisor diff <task_id> # View task changes +makima supervisor read-file <task_id> <file_path> # Read file from task ``` -## User Feedback (Ask Command) - -You can ask the user questions when you need clarification or approval: - +### User Interaction ```bash -# Ask a free-form question (waits for user to respond) -makima supervisor ask "What authentication method should I use?" - -# Ask with predefined choices -makima supervisor ask "Ready to create PR?" --choices "Yes,No,Need more changes" - -# Ask with context -makima supervisor ask "Should I proceed?" --context "Plan phase complete" +makima supervisor ask "Your question" [--choices "A,B,C"] # Ask user +makima supervisor status # Contract status (read-only) ``` -The ask command will block until the user responds (or timeout). Use this to: -- Clarify requirements before starting work -- Get approval before creating PRs -- Ask for guidance when tasks fail - -## Contract Phase Progression - -### For "Simple" contracts (Plan → Execute): -1. **Plan Phase**: Review the plan document and understand the goal -2. **Execute Phase**: Spawn tasks to implement the plan, then create PR -3. Mark contract as complete when PR is created - -### For "Specification" contracts (Research → Specify → Plan → Execute → Review): -Progress through each phase, spawning tasks as needed and asking for user feedback. - -## Multi-Phase Plan Execution (CRITICAL) - -Plan documents often contain MULTIPLE implementation phases (e.g., "Phase 1: Foundation", "Phase 2: Core Features", "Phase 3: Integration"). You MUST implement ALL phases, not just the first one! - -### Detecting Implementation Phases - -At the START of the Execute phase: -1. Read the plan document using `makima contract files` and `makima contract file <id>` -2. Look for implementation phase sections like: - - "## Phase 1: ..." / "## Phase 2: ..." - - "## Step 1: ..." / "## Step 2: ..." - - "## Part 1: ..." / "## Part 2: ..." - - Any numbered sections that represent sequential work -3. Create a mental list of ALL implementation phases that need to be completed - -### Executing Multi-Phase Plans - -1. **Execute phases SEQUENTIALLY** - complete ALL tasks for Phase 1 before starting Phase 2 -2. **Track your progress** - keep track of which phases are done vs remaining -3. **Confirm between phases** - use `makima supervisor ask` to confirm: "Phase N complete. Ready for Phase N+1?" -4. **ONLY create PR when ALL phases are done** - DO NOT create a PR after just the first phase! - -### Multi-Phase Workflow Example +## WORKFLOW PATTERN ```bash -# 1. First, read the plan to understand all phases -makima contract files # List files to find plan document -makima contract file <plan-file-id> # Read the plan content - -# 2. Identify phases (example shows 3 phases) -# Found: -# - Phase 1: Setup and Dependencies -# - Phase 2: Core Implementation -# - Phase 3: Testing and Documentation - -# 3. Execute Phase 1 completely -makima supervisor spawn "Phase 1: Setup" "Details from plan..." -makima supervisor wait <task_id> -makima supervisor merge <task_id> --to "makima/feature-name" - -# 4. Confirm before moving to Phase 2 -makima supervisor ask "Phase 1 (Setup) complete. Ready to proceed to Phase 2 (Core Implementation)?" --choices "Yes,Need changes,Stop" - -# 5. Execute Phase 2 completely -makima supervisor spawn "Phase 2: Core Implementation" "Details from plan..." -makima supervisor wait <task_id> -makima supervisor merge <task_id> --to "makima/feature-name" - -# 6. Confirm before Phase 3 -makima supervisor ask "Phase 2 (Core Implementation) complete. Ready to proceed to Phase 3 (Testing)?" --choices "Yes,Need changes,Stop" - -# 7. Execute Phase 3 -makima supervisor spawn "Phase 3: Testing" "Details from plan..." -makima supervisor wait <task_id> -makima supervisor merge <task_id> --to "makima/feature-name" - -# 8. ONLY NOW create the PR (all phases complete!) -makima supervisor pr "makima/feature-name" --title "Complete feature implementation" -``` - -### Common Multi-Phase Mistakes - -- ❌ Creating a PR after only the first phase completes -- ❌ Not reading the plan document to identify all phases -- ❌ Trying to implement all phases in a single giant task -- ❌ Skipping the confirmation step between phases - -### Correct Multi-Phase Behavior - -- ✅ Read plan document first to identify ALL implementation phases -- ✅ Execute each phase as separate task(s) -- ✅ Wait for each phase to complete before starting the next -- ✅ Confirm with user between phases -- ✅ Create PR ONLY after ALL phases are complete -- ✅ The PR title/description should mention all completed phases - -## Phase Management Commands - -Check contract status (including current phase): -```bash -makima supervisor status -``` - -Advance to the next phase: -```bash -makima supervisor advance-phase <phase> -``` - -Valid phases: `specify`, `plan`, `execute`, `review` - -### Marking Deliverables Complete +# 1. Spawn a task +RESULT=$(makima supervisor spawn "Implement feature X" "Details...") +TASK_ID=$(echo "$RESULT" | jq -r '.taskId') -Each phase has deliverables that must be completed before advancing. Use `mark-deliverable` to explicitly mark them as complete when you've verified the requirement is satisfied: +# 2. Wait for it +makima supervisor wait "$TASK_ID" -```bash -# Mark a deliverable complete (defaults to current phase) -makima supervisor mark-deliverable plan-document +# 3. Check result +makima supervisor diff "$TASK_ID" -# Mark a deliverable for a specific phase -makima supervisor mark-deliverable pull-request --phase execute +# 4. Repeat for more tasks +# System handles commits, merging, and PR creation automatically ``` -Common deliverable IDs by phase: -- **plan**: `plan-document`, `requirements-document` -- **execute**: `pull-request` -- **review**: `release-notes`, `retrospective` - -**Use `status` to see which deliverables are pending for the current phase.** - -## When to Advance Phases - -**IMPORTANT**: You MUST advance the contract phase as you complete work in each phase! - -### Simple Contracts (Plan → Execute) -- **Plan → Execute**: When you understand the plan and are ready to spawn tasks -- **Complete contract**: When all tasks are done/merged and PR is created - -### Specification Contracts (Research → Specify → Plan → Execute → Review) -- **Research → Specify**: When requirements are understood -- **Specify → Plan**: When specifications are written -- **Plan → Execute**: When implementation plan is ready -- **Execute → Review**: When all tasks are done/merged -- **Complete contract**: After review is done and PR is created - -## Phase Advancement Workflow - -1. Complete work for current phase (spawn tasks, wait, merge) -2. Check status: `makima supervisor status` -3. Ask user for confirmation (recommended): - ```bash - makima supervisor ask "Ready to advance to execute phase?" --choices "Yes,Not yet" - ``` -4. Advance: `makima supervisor advance-phase execute` -5. Continue with next phase work - -**DO NOT forget to advance phases!** The user needs to see the contract progressing. +## MULTI-PHASE PLANS -## Key Points +When the plan document contains multiple implementation phases (Phase 1, Phase 2, etc.): -1. **Create a makima branch first** - use `branch "makima/{name}"` for the contract's work -2. **spawn returns immediately** - the task runs in the background -3. **wait blocks until complete** - you MUST call this to know when a task finishes -4. **Never fire-and-forget** - always wait for each task before moving on -5. **Merge to your makima branch** - use `merge <task_id> --to "makima/{name}"` to collect completed work -6. **Create PR when done** - use `pr "makima/{name}" --title "..."` -7. **Ask when unsure** - use `ask` to get user feedback on decisions +1. **Read the plan** to identify ALL phases +2. **Execute phases SEQUENTIALLY** - complete Phase 1 before Phase 2 +3. **Track your progress** - keep track of which phases are done +4. **Confirm between phases** - use `ask` to confirm before proceeding +5. The system will auto-create PR when ALL phases are complete -## Standard Workflow +## IMPORTANT NOTES +- DO NOT call advance-phase - the system does this automatically +- DO NOT manage git operations (branch, merge, pr) - the system handles this +- Focus ONLY on spawning tasks and responding to users +- You share a worktree with all tasks - changes are visible immediately +- If you need user input, use `makima supervisor ask` +- When all work is complete, use `makima supervisor complete` to finish -1. `branch "makima/{name}"` - Create branch (e.g., "makima/add-auth") -2. For each piece of work: - - `spawn` - Create task - - `wait` - Block until complete - - `merge --to "makima/{name}"` - Merge to branch -3. `ask "Ready to create PR?"` - Get user approval -4. `pr "makima/{name}" --title "..."` - Create PR +## WHEN TASKS COMPLETE -## Important Reminders +When a task completes: +1. Check the result with `makima supervisor diff <task_id>` +2. If more work needed, spawn another task +3. The system automatically commits changes -- **ONLY YOU can spawn tasks** - regular tasks cannot create children -- **NEVER implement anything yourself** - always spawn tasks -- **ALWAYS create a makima branch** - use `makima/{name}` naming convention -- **ASK for feedback** when you need clarification or approval -- Tasks run independently - you just coordinate -- You will be resumed if interrupted - your conversation is preserved -- Create checkpoints before major transitions -- **Mark contract complete** when PR is created by updating status - -## MANDATORY ACTIONS - -### [ACTION REQUIRED] Messages -When you receive an `[ACTION REQUIRED]` message from the system: -1. Read the directive carefully -2. Execute the specified command IMMEDIATELY -3. Do NOT ignore or defer these actions -4. These messages indicate conditions have been met and action is needed NOW - -### Phase Completion Checklist -After all tasks are "done" and merged, you MUST take the following actions: - -**If in execute phase:** -1. Create PR immediately: `makima supervisor pr "makima/{name}" --title "..."` -2. After PR created: - - Simple contract: Mark complete with `makima supervisor complete` - - Specification contract: Advance to review with `makima supervisor advance-phase review` - -**Never leave a contract hanging** - when work is done, create the PR and complete/advance. - ---- +When ALL work is complete: +- Use `makima supervisor complete` to mark the contract done +- The system will auto-create PR (for remote repos) "###; @@ -5308,20 +5036,19 @@ impl TaskManagerInner { } } _ = heartbeat_interval.tick(), if heartbeat_enabled => { - // Create periodic heartbeat commit to preserve work-in-progress - match self.create_heartbeat_commit(task_id, &working_dir).await { - Ok((sha, pushed)) => { - let status = if pushed { "pushed" } else { "local only" }; + // Create periodic ephemeral patch to preserve work-in-progress + match self.create_ephemeral_patch(task_id, &working_dir).await { + Ok(files_count) => { let msg = DaemonMessage::task_output( task_id, - format!("[Heartbeat] WIP checkpoint {} ({})\n", &sha[..8], status), + format!("[Heartbeat] Patch saved ({} files)\n", files_count), false, ); let _ = ws_tx.send(msg).await; } Err(e) => { - // No changes to commit or git error - this is fine, just log at debug level - tracing::debug!(task_id = %task_id, error = %e, "Heartbeat commit skipped"); + // No changes to patch or error - this is fine, just log at debug level + tracing::debug!(task_id = %task_id, error = %e, "Heartbeat patch skipped"); } } } @@ -5907,24 +5634,28 @@ impl TaskManagerInner { } } - /// Create a heartbeat commit with all uncommitted changes (WIP checkpoint). - /// Returns (commit SHA, push succeeded) on success, or an error message if nothing to commit. - /// Also creates a patch and sends it to the server for recovery purposes. - async fn create_heartbeat_commit( + /// Create an ephemeral patch of uncommitted changes and send to the server. + /// This does NOT create git commits or push - patches are stored in PostgreSQL only. + /// Returns the number of files changed on success, or an error message if nothing to patch. + async fn create_ephemeral_patch( &self, task_id: Uuid, worktree_path: &std::path::Path, - ) -> Result<(String, bool), String> { - // 1. Get parent SHA BEFORE committing (for patch creation) - let parent_sha_output = tokio::process::Command::new("git") + ) -> Result<i32, String> { + // 1. Get current HEAD SHA (base for the patch) + let base_sha_output = tokio::process::Command::new("git") .current_dir(worktree_path) .args(["rev-parse", "HEAD"]) .output() - .await; - let parent_sha = parent_sha_output - .ok() - .filter(|o| o.status.success()) - .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()); + .await + .map_err(|e| format!("Failed to run git rev-parse: {}", e))?; + + if !base_sha_output.status.success() { + let stderr = String::from_utf8_lossy(&base_sha_output.stderr); + return Err(format!("git rev-parse failed: {}", stderr)); + } + + let base_sha = String::from_utf8_lossy(&base_sha_output.stdout).trim().to_string(); // 2. Check for uncommitted changes using git status --porcelain let status_output = tokio::process::Command::new("git") @@ -5941,10 +5672,13 @@ impl TaskManagerInner { let status = String::from_utf8_lossy(&status_output.stdout); if status.trim().is_empty() { - return Err("No changes to commit".into()); + return Err("No changes to patch".into()); } - // 3. Stage all changes + // Count files with changes + let files_count = status.lines().count() as i32; + + // 3. Stage all changes (required for diff to include untracked files) let add_output = tokio::process::Command::new("git") .current_dir(worktree_path) .args(["add", "-A"]) @@ -5957,137 +5691,79 @@ impl TaskManagerInner { return Err(format!("git add failed: {}", stderr)); } - // 4. Create WIP commit with timestamp - let timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC"); - let commit_msg = format!("[WIP] Heartbeat checkpoint - {}", timestamp); - - let commit_output = tokio::process::Command::new("git") - .current_dir(worktree_path) - .args(["commit", "-m", &commit_msg]) - .output() - .await - .map_err(|e| format!("Failed to run git commit: {}", e))?; - - if !commit_output.status.success() { - let stderr = String::from_utf8_lossy(&commit_output.stderr); - return Err(format!("git commit failed: {}", stderr)); + // 4. Create patch (diff of staged changes against HEAD) + if !self.checkpoint_patches.enabled { + // Reset staged changes and return + let _ = tokio::process::Command::new("git") + .current_dir(worktree_path) + .args(["reset", "HEAD"]) + .output() + .await; + return Err("Checkpoint patches disabled".into()); } - // 5. Get the commit SHA - let sha_output = tokio::process::Command::new("git") - .current_dir(worktree_path) - .args(["rev-parse", "HEAD"]) - .output() - .await - .map_err(|e| format!("Failed to run git rev-parse: {}", e))?; + match storage::create_patch(worktree_path, &base_sha).await { + Ok((compressed_patch, patch_files_count)) => { + // Reset staged changes (we don't want to commit) + let _ = tokio::process::Command::new("git") + .current_dir(worktree_path) + .args(["reset", "HEAD"]) + .output() + .await; - if !sha_output.status.success() { - let stderr = String::from_utf8_lossy(&sha_output.stderr); - return Err(format!("git rev-parse failed: {}", stderr)); - } + // Check size limit + if compressed_patch.len() > self.checkpoint_patches.max_patch_size_bytes { + tracing::warn!( + task_id = %task_id, + patch_size = compressed_patch.len(), + max_size = self.checkpoint_patches.max_patch_size_bytes, + "Patch exceeds size limit" + ); + return Err("Patch exceeds size limit".into()); + } - let sha = String::from_utf8_lossy(&sha_output.stdout).trim().to_string(); - tracing::info!(task_id = %task_id, sha = %sha, "Created heartbeat commit"); + // Encode as base64 for JSON transport + let patch_data = base64::engine::general_purpose::STANDARD.encode(&compressed_patch); - // 6. Get current branch name - let branch_output = tokio::process::Command::new("git") - .current_dir(worktree_path) - .args(["branch", "--show-current"]) - .output() - .await; - let branch_name = branch_output - .ok() - .filter(|o| o.status.success()) - .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) - .unwrap_or_else(|| "unknown".to_string()); - - // 7. Push to remote (best effort - don't fail if push fails) - // Use -u origin HEAD to set upstream if not already set (new branches won't have upstream) - let push_output = tokio::process::Command::new("git") - .current_dir(worktree_path) - .args(["push", "-u", "origin", "HEAD"]) - .output() - .await; + tracing::debug!( + task_id = %task_id, + base_sha = %base_sha, + patch_size = compressed_patch.len(), + files_count = patch_files_count, + "Created ephemeral patch" + ); - let pushed = match push_output { - Ok(output) if output.status.success() => { - tracing::info!(task_id = %task_id, sha = %sha, "Pushed heartbeat commit to remote"); - true - } - Ok(output) => { - let stderr = String::from_utf8_lossy(&output.stderr); - tracing::warn!(task_id = %task_id, sha = %sha, error = %stderr, "Failed to push heartbeat commit (commit saved locally)"); - false + // Send CheckpointCreated message to server (patch-only, no commit) + let timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC"); + let msg = DaemonMessage::CheckpointCreated { + task_id, + success: true, + commit_sha: None, // No git commit + branch_name: None, + checkpoint_number: None, // Server will assign + files_changed: None, // Detailed file info not tracked for ephemeral patches + lines_added: None, + lines_removed: None, + error: None, + message: format!("Ephemeral patch - {}", timestamp), + patch_data: Some(patch_data), + patch_base_sha: Some(base_sha), + patch_files_count: Some(patch_files_count as i32), + }; + let _ = self.ws_tx.send(msg).await; + + Ok(files_count) } Err(e) => { - tracing::warn!(task_id = %task_id, sha = %sha, error = %e, "Failed to run git push (commit saved locally)"); - false - } - }; - - // 8. Create patch and send CheckpointCreated message to server - let mut patch_data: Option<String> = None; - let mut patch_base_sha: Option<String> = None; - let mut patch_files_count: Option<i32> = None; - - if self.checkpoint_patches.enabled { - if let Some(ref base_sha) = parent_sha { - match storage::create_patch(worktree_path, base_sha).await { - Ok((compressed_patch, files_count)) => { - // Check size limit - if compressed_patch.len() <= self.checkpoint_patches.max_patch_size_bytes { - // Encode as base64 for JSON transport - patch_data = Some(base64::engine::general_purpose::STANDARD.encode(&compressed_patch)); - patch_base_sha = Some(base_sha.clone()); - patch_files_count = Some(files_count as i32); - tracing::debug!( - task_id = %task_id, - sha = %sha, - patch_size = compressed_patch.len(), - files_count = files_count, - "Created checkpoint patch" - ); - } else { - tracing::warn!( - task_id = %task_id, - sha = %sha, - patch_size = compressed_patch.len(), - max_size = self.checkpoint_patches.max_patch_size_bytes, - "Patch exceeds size limit, not including in checkpoint" - ); - } - } - Err(e) => { - tracing::warn!( - task_id = %task_id, - sha = %sha, - error = %e, - "Failed to create patch for heartbeat commit" - ); - } - } + // Reset staged changes + let _ = tokio::process::Command::new("git") + .current_dir(worktree_path) + .args(["reset", "HEAD"]) + .output() + .await; + Err(format!("Failed to create patch: {}", e)) } } - - // Send CheckpointCreated message to server (so it stores the checkpoint and patch) - let msg = DaemonMessage::CheckpointCreated { - task_id, - success: true, - commit_sha: Some(sha.clone()), - branch_name: Some(branch_name), - checkpoint_number: None, // Server will assign - files_changed: None, // Could get from git diff --name-status if needed - lines_added: None, - lines_removed: None, - error: None, - message: commit_msg, - patch_data, - patch_base_sha, - patch_files_count, - }; - let _ = self.ws_tx.send(msg).await; - - Ok((sha, pushed)) } } |
