summaryrefslogtreecommitdiff
path: root/makima/src/daemon
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-10 14:50:07 +0000
committersoryu <soryu@soryu.co>2026-02-10 14:50:07 +0000
commit15b6e5fba161a194fe5427d7d29b0c4286423260 (patch)
treefdd7bde229150cbb56d37714c23c2dc9db902f28 /makima/src/daemon
parent526edf672aae73c3670ab6141253bf92f1fbfe8c (diff)
downloadsoryu-15b6e5fba161a194fe5427d7d29b0c4286423260.tar.gz
soryu-15b6e5fba161a194fe5427d7d29b0c4286423260.zip
Add auto-PR creation for remote repos in directives
Diffstat (limited to 'makima/src/daemon')
-rw-r--r--makima/src/daemon/api/directive.rs20
-rw-r--r--makima/src/daemon/cli/directive.rs15
-rw-r--r--makima/src/daemon/cli/mod.rs3
-rw-r--r--makima/src/daemon/task/manager.rs45
-rw-r--r--makima/src/daemon/worktree/manager.rs92
5 files changed, 161 insertions, 14 deletions
diff --git a/makima/src/daemon/api/directive.rs b/makima/src/daemon/api/directive.rs
index cd21692..5886766 100644
--- a/makima/src/daemon/api/directive.rs
+++ b/makima/src/daemon/api/directive.rs
@@ -134,4 +134,24 @@ impl ApiClient {
let req = UpdateGoalRequest { goal: goal.to_string() };
self.put(&format!("/api/v1/directives/{}/goal", directive_id), &req).await
}
+
+ /// Update directive metadata (PR URL, PR branch, etc.)
+ pub async fn directive_update(
+ &self,
+ directive_id: Uuid,
+ pr_url: Option<String>,
+ pr_branch: Option<String>,
+ ) -> Result<JsonValue, ApiError> {
+ let req = UpdateDirectiveMetadataRequest { pr_url, pr_branch };
+ self.put(&format!("/api/v1/directives/{}", directive_id), &req).await
+ }
+}
+
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct UpdateDirectiveMetadataRequest {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub pr_url: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub pr_branch: Option<String>,
}
diff --git a/makima/src/daemon/cli/directive.rs b/makima/src/daemon/cli/directive.rs
index cd94a56..2e6ac1d 100644
--- a/makima/src/daemon/cli/directive.rs
+++ b/makima/src/daemon/cli/directive.rs
@@ -110,3 +110,18 @@ pub struct BatchAddStepsArgs {
#[arg(long)]
pub json: String,
}
+
+/// Arguments for update command.
+#[derive(Args, Debug)]
+pub struct UpdateArgs {
+ #[command(flatten)]
+ pub common: DirectiveArgs,
+
+ /// PR URL to store on the directive
+ #[arg(long)]
+ pub pr_url: Option<String>,
+
+ /// PR branch name to store on the directive
+ #[arg(long)]
+ pub pr_branch: Option<String>,
+}
diff --git a/makima/src/daemon/cli/mod.rs b/makima/src/daemon/cli/mod.rs
index 98923d9..bcaaa70 100644
--- a/makima/src/daemon/cli/mod.rs
+++ b/makima/src/daemon/cli/mod.rs
@@ -246,6 +246,9 @@ pub enum DirectiveCommand {
/// Batch add multiple steps from JSON
BatchAddSteps(directive::BatchAddStepsArgs),
+
+ /// Update directive metadata (PR URL, etc.)
+ Update(directive::UpdateArgs),
}
impl Cli {
diff --git a/makima/src/daemon/task/manager.rs b/makima/src/daemon/task/manager.rs
index a24f527..22b41d9 100644
--- a/makima/src/daemon/task/manager.rs
+++ b/makima/src/daemon/task/manager.rs
@@ -5587,8 +5587,8 @@ impl TaskManagerInner {
let target_repo = match target_repo_path {
Some(path) => Some(crate::daemon::worktree::expand_tilde(path)),
None => {
- if action == "pr" {
- // For PR action, check if worktree has an origin remote we can use directly
+ if action == "pr" || action == "branch" {
+ // For PR/branch action without target_repo, use origin directly
None
} else {
tracing::warn!(task_id = %task_id, "No target_repo_path configured, skipping completion action");
@@ -5633,19 +5633,36 @@ impl TaskManagerInner {
match action {
"branch" => {
- let target_repo = target_repo.ok_or_else(|| "No target_repo_path configured for branch action".to_string())?;
- // Just push the branch to target repo
- self.worktree_manager
- .push_to_target_repo(worktree_path, &target_repo, &branch_name, task_name)
- .await
- .map_err(|e| e.to_string())?;
+ match target_repo {
+ Some(target_repo) => {
+ // Push branch to local target repo
+ self.worktree_manager
+ .push_to_target_repo(worktree_path, &target_repo, &branch_name, task_name)
+ .await
+ .map_err(|e| e.to_string())?;
- let msg = DaemonMessage::task_output(
- task_id,
- format!("Branch '{}' pushed to {}\n", branch_name, target_repo.display()),
- false,
- );
- let _ = self.ws_tx.send(msg).await;
+ let msg = DaemonMessage::task_output(
+ task_id,
+ format!("Branch '{}' pushed to {}\n", branch_name, target_repo.display()),
+ false,
+ );
+ let _ = self.ws_tx.send(msg).await;
+ }
+ None => {
+ // Push branch to origin (GitHub)
+ self.worktree_manager
+ .push_branch_to_origin(worktree_path, &branch_name, task_name)
+ .await
+ .map_err(|e| e.to_string())?;
+
+ let msg = DaemonMessage::task_output(
+ task_id,
+ format!("Branch '{}' pushed to origin\n", branch_name),
+ false,
+ );
+ let _ = self.ws_tx.send(msg).await;
+ }
+ }
Ok(None)
}
"merge" => {
diff --git a/makima/src/daemon/worktree/manager.rs b/makima/src/daemon/worktree/manager.rs
index 310627c..20c93b1 100644
--- a/makima/src/daemon/worktree/manager.rs
+++ b/makima/src/daemon/worktree/manager.rs
@@ -1417,6 +1417,98 @@ impl WorktreeManager {
Ok(())
}
+ /// Push a worktree branch to origin (the upstream GitHub remote).
+ /// Simpler than push_to_target_repo — just pushes to origin.
+ pub async fn push_branch_to_origin(
+ &self,
+ worktree_path: &Path,
+ branch_name: &str,
+ task_name: &str,
+ ) -> Result<(), WorktreeError> {
+ tracing::info!(
+ worktree = %worktree_path.display(),
+ branch = %branch_name,
+ "Pushing branch to origin"
+ );
+
+ // Stage all changes
+ let output = Command::new("git")
+ .args(["add", "-A"])
+ .current_dir(worktree_path)
+ .output()
+ .await?;
+
+ if !output.status.success() {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ return Err(WorktreeError::GitCommand(format!(
+ "Failed to stage changes: {}",
+ stderr
+ )));
+ }
+
+ // Check if there are staged changes to commit
+ let output = Command::new("git")
+ .args(["diff", "--cached", "--quiet"])
+ .current_dir(worktree_path)
+ .output()
+ .await?;
+
+ // Exit code 1 means there are staged changes
+ if !output.status.success() {
+ tracing::info!("Committing staged changes before push to origin");
+
+ let commit_message = format!("feat: {}", task_name);
+ let output = Command::new("git")
+ .args(["commit", "-m", &commit_message])
+ .current_dir(worktree_path)
+ .output()
+ .await?;
+
+ if !output.status.success() {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ return Err(WorktreeError::GitCommand(format!(
+ "Failed to commit changes: {}",
+ stderr
+ )));
+ }
+ }
+
+ // Ensure there are commits to push
+ let output = Command::new("git")
+ .args(["log", "--oneline", "-1"])
+ .current_dir(worktree_path)
+ .output()
+ .await?;
+
+ if !output.status.success() {
+ return Err(WorktreeError::GitCommand(
+ "No commits in worktree".to_string(),
+ ));
+ }
+
+ // Push to origin
+ let output = Command::new("git")
+ .args(["push", "-u", "origin", &format!("HEAD:{}", branch_name)])
+ .current_dir(worktree_path)
+ .output()
+ .await?;
+
+ if !output.status.success() {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ return Err(WorktreeError::GitCommand(format!(
+ "Failed to push to origin: {}",
+ stderr
+ )));
+ }
+
+ tracing::info!(
+ branch = %branch_name,
+ "Branch pushed to origin successfully"
+ );
+
+ Ok(())
+ }
+
/// Merge a branch into the target branch in the target repository.
///
/// This pushes the branch first (if needed), then performs a merge in the target repo.