summaryrefslogtreecommitdiff
path: root/makima/src/daemon/task
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/daemon/task')
-rw-r--r--makima/src/daemon/task/manager.rs658
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))
}
}