summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--makima/frontend/src/components/contracts/ContractList.tsx5
-rw-r--r--makima/frontend/src/hooks/useSpeakWebSocket.ts2
-rw-r--r--makima/frontend/src/lib/api.ts12
-rw-r--r--makima/frontend/src/routes/contracts.tsx78
-rw-r--r--makima/frontend/tsconfig.tsbuildinfo2
-rw-r--r--makima/src/bin/makima.rs4
-rw-r--r--makima/src/daemon/api/supervisor.rs9
-rw-r--r--makima/src/daemon/cli/supervisor.rs8
-rw-r--r--makima/src/daemon/task/manager.rs40
-rw-r--r--makima/src/daemon/ws/protocol.rs4
-rw-r--r--makima/src/server/handlers/mesh_supervisor.rs41
-rw-r--r--makima/src/server/state.rs4
12 files changed, 140 insertions, 69 deletions
diff --git a/makima/frontend/src/components/contracts/ContractList.tsx b/makima/frontend/src/components/contracts/ContractList.tsx
index 98f8ff6..532ab87 100644
--- a/makima/frontend/src/components/contracts/ContractList.tsx
+++ b/makima/frontend/src/components/contracts/ContractList.tsx
@@ -136,6 +136,11 @@ export function ContractList({
Local
</span>
)}
+ {contract.redTeamEnabled && (
+ <span className="px-1.5 py-0.5 font-mono text-[9px] uppercase text-cyan-400 border border-cyan-400/30 bg-cyan-400/10 shrink-0" title="Red Team monitoring enabled">
+ 🔍 Red Team
+ </span>
+ )}
</div>
<span
className={`text-[10px] font-mono uppercase shrink-0 ${
diff --git a/makima/frontend/src/hooks/useSpeakWebSocket.ts b/makima/frontend/src/hooks/useSpeakWebSocket.ts
index 3ef8851..d9fb826 100644
--- a/makima/frontend/src/hooks/useSpeakWebSocket.ts
+++ b/makima/frontend/src/hooks/useSpeakWebSocket.ts
@@ -22,7 +22,7 @@ export function useSpeakWebSocket() {
const wsRef = useRef<WebSocket | null>(null);
const audioContextRef = useRef<AudioContext | null>(null);
- const audioQueueRef = useRef<Float32Array[]>([]);
+ const audioQueueRef = useRef<Float32Array<ArrayBuffer>[]>([]);
const isPlayingRef = useRef(false);
const modelLoadingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const nextPlayTimeRef = useRef(0);
diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts
index ca04ce7..8838dbd 100644
--- a/makima/frontend/src/lib/api.ts
+++ b/makima/frontend/src/lib/api.ts
@@ -1636,6 +1636,8 @@ export interface ContractTypeTemplate {
defaultPhase: ContractPhase;
/** Whether this is a built-in type (always available) */
isBuiltin: boolean;
+ /** Optional mapping from phase ID to display name */
+ phaseNames?: Record<string, string>;
}
/** Response from list contract types endpoint */
@@ -1680,6 +1682,8 @@ export interface ContractSummary {
supervisorTaskId: string | null;
/** When true, tasks won't auto-push or create PRs - use patch files instead */
localOnly: boolean;
+ /** When true, a red team task monitors work output for quality */
+ redTeamEnabled: boolean;
fileCount: number;
taskCount: number;
repositoryCount: number;
@@ -1704,6 +1708,10 @@ export interface Contract {
phaseGuard: boolean;
/** When true, tasks won't auto-push or create PRs - use patch files instead */
localOnly: boolean;
+ /** When true, a red team task monitors work output for quality */
+ redTeamEnabled: boolean;
+ /** Custom criteria for the red team to evaluate */
+ redTeamPrompt: string | null;
version: number;
createdAt: string;
updatedAt: string;
@@ -1739,6 +1747,10 @@ export interface CreateContractRequest {
initialPhase?: ContractPhase;
/** When true, tasks won't auto-push or create PRs - use patch files instead */
localOnly?: boolean;
+ /** When true, spawn a red team task to monitor work output */
+ redTeamEnabled?: boolean;
+ /** Custom criteria for the red team to evaluate */
+ redTeamPrompt?: string;
}
export interface UpdateContractRequest {
diff --git a/makima/frontend/src/routes/contracts.tsx b/makima/frontend/src/routes/contracts.tsx
index aa62bd9..bb66215 100644
--- a/makima/frontend/src/routes/contracts.tsx
+++ b/makima/frontend/src/routes/contracts.tsx
@@ -93,6 +93,8 @@ function ContractsPageContent() {
const [contractTypes, setContractTypes] = useState<ContractTypeTemplate[]>([]);
const [contractTypesLoading, setContractTypesLoading] = useState(false);
const [localOnly, setLocalOnly] = useState(false);
+ const [redTeamEnabled, setRedTeamEnabled] = useState(false);
+ const [redTeamPrompt, setRedTeamPrompt] = useState("");
// Fetch contract types when modal opens - merges built-in types with user templates
useEffect(() => {
@@ -108,11 +110,12 @@ function ContractsPageContent() {
// Convert user templates to ContractTypeTemplate format, excluding built-ins
return templates
.filter((t: { isBuiltIn?: boolean }) => !t.isBuiltIn)
- .map((t: { id: string; name: string; description: string; phases: { id: string }[] }) => ({
+ .map((t: { id: string; name: string; description: string; phases: { id: string; name: string }[] }) => ({
id: t.id,
name: t.name,
description: t.description,
phases: t.phases.map((p: { id: string }) => p.id) as ContractPhase[],
+ phaseNames: Object.fromEntries(t.phases.map((p: { id: string; name: string }) => [p.id, p.name])),
defaultPhase: (t.phases[0]?.id || "execute") as ContractPhase,
isBuiltin: false,
}));
@@ -265,6 +268,8 @@ function ContractsPageContent() {
contractType: contractType,
initialPhase: initialPhase !== defaultPhaseForType ? initialPhase : undefined,
localOnly: localOnly || undefined,
+ redTeamEnabled: redTeamEnabled || undefined,
+ redTeamPrompt: redTeamEnabled && redTeamPrompt.trim() ? redTeamPrompt.trim() : undefined,
};
try {
@@ -340,6 +345,8 @@ function ContractsPageContent() {
setRepoUrl("");
setRepoPath("");
setLocalOnly(false);
+ setRedTeamEnabled(false);
+ setRedTeamPrompt("");
setCreateError(null);
}, []);
@@ -652,11 +659,17 @@ function ContractsPageContent() {
onChange={(e) => setInitialPhase(e.target.value as ContractPhase)}
className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]"
>
- {(contractTypes.find((t) => t.id === contractType)?.phases || []).map((phase) => (
- <option key={phase} value={phase}>
- {phase.charAt(0).toUpperCase() + phase.slice(1)}
- </option>
- ))}
+ {(() => {
+ const template = contractTypes.find((t) => t.id === contractType);
+ return (template?.phases || []).map((phase) => {
+ const displayName = template?.phaseNames?.[phase] || (phase.charAt(0).toUpperCase() + phase.slice(1));
+ return (
+ <option key={phase} value={phase}>
+ {displayName}
+ </option>
+ );
+ });
+ })()}
</select>
<p className="mt-1 font-mono text-xs text-[#8b949e]">
{contractType === "simple"
@@ -705,6 +718,59 @@ function ContractsPageContent() {
</p>
</div>
+ {/* Red Team Monitoring */}
+ <div className="border-t border-[rgba(117,170,252,0.2)] pt-4">
+ <div className="flex items-center gap-3">
+ <button
+ type="button"
+ onClick={() => setRedTeamEnabled(!redTeamEnabled)}
+ className={`w-5 h-5 flex items-center justify-center border transition-colors ${
+ redTeamEnabled
+ ? "bg-[#0f3c78] border-[#75aafc] text-[#dbe7ff]"
+ : "bg-[#0d1b2d] border-[#3f6fb3] text-transparent"
+ }`}
+ >
+ {redTeamEnabled && (
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="12"
+ height="12"
+ viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor"
+ strokeWidth="3"
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ >
+ <polyline points="20 6 9 17 4 12" />
+ </svg>
+ )}
+ </button>
+ <label
+ className="font-mono text-sm text-[#dbe7ff] cursor-pointer select-none"
+ onClick={() => setRedTeamEnabled(!redTeamEnabled)}
+ >
+ Enable Red Team Monitoring
+ </label>
+ </div>
+ <p className="font-mono text-xs text-[#8b949e] pl-8">
+ Spawns a parallel task to monitor work output for quality and compliance.
+ </p>
+ {redTeamEnabled && (
+ <div className="mt-3 pl-8">
+ <label className="block font-mono text-xs text-[#75aafc] uppercase mb-2">
+ Custom Review Criteria (Optional)
+ </label>
+ <textarea
+ value={redTeamPrompt}
+ onChange={(e) => setRedTeamPrompt(e.target.value)}
+ placeholder="e.g., 'Focus on security best practices' or 'Ensure all functions have tests'"
+ className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] text-sm font-mono h-20 resize-none focus:border-[#75aafc] focus:outline-none"
+ />
+ </div>
+ )}
+ </div>
+
{/* Repository Configuration */}
<div className="border-t border-[rgba(117,170,252,0.2)] pt-4">
<label className="block font-mono text-xs text-[#75aafc] uppercase mb-3">
diff --git a/makima/frontend/tsconfig.tsbuildinfo b/makima/frontend/tsconfig.tsbuildinfo
index b02179d..804859b 100644
--- a/makima/frontend/tsconfig.tsbuildinfo
+++ b/makima/frontend/tsconfig.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/autopilotpanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/mesh/worktreefilespanel.tsx","./src/components/templates/templateeditor.tsx","./src/components/workflow/phasecolumn.tsx","./src/components/workflow/workflowboard.tsx","./src/components/workflow/workflowcontractcard.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/settings.tsx","./src/routes/templates.tsx","./src/routes/workflow.tsx","./src/types/messages.ts","./src/types/templates.ts"],"version":"5.9.3"} \ No newline at end of file
+{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/autopilotpanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/mesh/worktreefilespanel.tsx","./src/components/templates/templateeditor.tsx","./src/components/workflow/phasecolumn.tsx","./src/components/workflow/workflowboard.tsx","./src/components/workflow/workflowcontractcard.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usespeakwebsocket.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/settings.tsx","./src/routes/speak.tsx","./src/routes/templates.tsx","./src/routes/workflow.tsx","./src/types/messages.ts","./src/types/templates.ts"],"version":"5.9.3"} \ No newline at end of file
diff --git a/makima/src/bin/makima.rs b/makima/src/bin/makima.rs
index 44fa590..8e83565 100644
--- a/makima/src/bin/makima.rs
+++ b/makima/src/bin/makima.rs
@@ -439,10 +439,10 @@ async fn run_supervisor(
}
SupervisorCommand::Pr(args) => {
let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
- eprintln!("Creating PR for task {}...", args.task_id);
+ eprintln!("Creating PR for branch {}...", args.branch);
let body = args.body.as_deref().unwrap_or("");
let result = client
- .supervisor_pr(args.task_id, &args.title, body, &args.base)
+ .supervisor_pr(&args.branch, &args.title, body)
.await?;
println!("{}", serde_json::to_string(&result.0)?);
}
diff --git a/makima/src/daemon/api/supervisor.rs b/makima/src/daemon/api/supervisor.rs
index 6b99de0..c841b21 100644
--- a/makima/src/daemon/api/supervisor.rs
+++ b/makima/src/daemon/api/supervisor.rs
@@ -54,10 +54,9 @@ pub struct MergeRequest {
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreatePrRequest {
- pub task_id: Uuid,
+ pub branch: String,
pub title: String,
pub body: String,
- pub base_branch: String,
}
#[derive(Serialize)]
@@ -165,16 +164,14 @@ impl ApiClient {
/// Create a pull request.
pub async fn supervisor_pr(
&self,
- task_id: Uuid,
+ branch: &str,
title: &str,
body: &str,
- base_branch: &str,
) -> Result<JsonValue, ApiError> {
let req = CreatePrRequest {
- task_id,
+ branch: branch.to_string(),
title: title.to_string(),
body: body.to_string(),
- base_branch: base_branch.to_string(),
};
self.post("/api/v1/mesh/supervisor/pr", &req).await
}
diff --git a/makima/src/daemon/cli/supervisor.rs b/makima/src/daemon/cli/supervisor.rs
index 09f61db..9ad7aef 100644
--- a/makima/src/daemon/cli/supervisor.rs
+++ b/makima/src/daemon/cli/supervisor.rs
@@ -128,9 +128,9 @@ pub struct PrArgs {
#[command(flatten)]
pub common: SupervisorArgs,
- /// Task ID to create PR for
+ /// Branch name to create PR from (e.g., "makima/feature-name")
#[arg(index = 1)]
- pub task_id: Uuid,
+ pub branch: String,
/// PR title
#[arg(long)]
@@ -139,10 +139,6 @@ pub struct PrArgs {
/// PR body/description
#[arg(long)]
pub body: Option<String>,
-
- /// Base branch (default: main)
- #[arg(long, default_value = "main")]
- pub base: String,
}
/// Arguments for diff command.
diff --git a/makima/src/daemon/task/manager.rs b/makima/src/daemon/task/manager.rs
index f0da860..8c5f8d7 100644
--- a/makima/src/daemon/task/manager.rs
+++ b/makima/src/daemon/task/manager.rs
@@ -669,7 +669,7 @@ 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" --base main
+makima supervisor pr "makima/user-authentication" --title "Add user authentication"
```
## Available Tools (via makima supervisor)
@@ -701,7 +701,7 @@ makima supervisor branch <branch_name> [--from <task_id|sha>]
makima supervisor merge <task_id> [--to <branch>] [--squash]
# Create a pull request
-makima supervisor pr <task_id> --title "Title" [--body "Body"] [--base main]
+makima supervisor pr <branch> --title "Title" [--body "Body"]
# View a task's diff
makima supervisor diff <task_id>
@@ -838,7 +838,7 @@ Common deliverable IDs by phase:
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 "..." --base main`
+6. **Create PR when done** - use `pr "makima/{name}" --title "..."`
7. **Ask when unsure** - use `ask` to get user feedback on decisions
## Standard Workflow
@@ -849,7 +849,7 @@ Common deliverable IDs by phase:
- `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 "..." --base main` - Create PR
+4. `pr "makima/{name}" --title "..."` - Create PR
## Important Reminders
@@ -875,7 +875,7 @@ When you receive an `[ACTION REQUIRED]` message from the system:
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 "..." --base main`
+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`
@@ -2016,14 +2016,16 @@ impl TaskManager {
title,
body,
base_branch,
+ branch,
} => {
tracing::info!(
task_id = %task_id,
title = %title,
base_branch = %base_branch,
+ branch = %branch,
"Creating pull request"
);
- self.handle_create_pr(task_id, title, body, base_branch).await?;
+ self.handle_create_pr(task_id, title, body, base_branch, branch).await?;
}
DaemonCommand::GetTaskDiff {
task_id,
@@ -3135,6 +3137,7 @@ impl TaskManager {
title: String,
body: Option<String>,
base_branch: String,
+ branch: String,
) -> Result<(), DaemonError> {
// Get worktree path - this works even for completed tasks by scanning worktrees directory
let worktree_path = match self.get_task_worktree_path(task_id).await {
@@ -3153,30 +3156,19 @@ impl TaskManager {
}
};
- // Get base_branch from in-memory tasks if available (for fallback)
- let task_base_branch = {
- let tasks = self.tasks.read().await;
- tasks.get(&task_id).and_then(|t| t.base_branch.clone())
- };
-
- // Use task's base_branch if the provided one is the default "main" and task has a detected one
- let effective_base_branch = if base_branch == "main" {
- task_base_branch.unwrap_or(base_branch)
- } else {
- base_branch
- };
-
tracing::info!(
task_id = %task_id,
- effective_base_branch = %effective_base_branch,
+ base_branch = %base_branch,
+ branch = %branch,
worktree_path = %worktree_path.display(),
- "Creating PR with effective base branch"
+ "Creating PR"
);
- // Push the current branch first
+ // Push the branch to origin
+ let push_refspec = format!("HEAD:refs/heads/{}", branch);
let push_result = tokio::process::Command::new("git")
.current_dir(&worktree_path)
- .args(["push", "-u", "origin", "HEAD"])
+ .args(["push", "-u", "origin", &push_refspec])
.output()
.await;
@@ -3195,7 +3187,7 @@ impl TaskManager {
// Create PR using gh CLI
let mut pr_cmd = tokio::process::Command::new("gh");
pr_cmd.current_dir(&worktree_path);
- pr_cmd.args(["pr", "create", "--title", &title, "--base", &effective_base_branch]);
+ pr_cmd.args(["pr", "create", "--title", &title, "--base", &base_branch, "--head", &branch]);
if let Some(ref body_text) = body {
pr_cmd.args(["--body", body_text]);
diff --git a/makima/src/daemon/ws/protocol.rs b/makima/src/daemon/ws/protocol.rs
index bd13975..e971798 100644
--- a/makima/src/daemon/ws/protocol.rs
+++ b/makima/src/daemon/ws/protocol.rs
@@ -693,9 +693,11 @@ pub enum DaemonCommand {
task_id: Uuid,
title: String,
body: Option<String>,
- /// Base branch for the PR (default: main).
+ /// Base branch for the PR.
#[serde(rename = "baseBranch")]
base_branch: String,
+ /// Source branch name to push and create PR from.
+ branch: String,
},
/// Get the diff for a task's changes.
diff --git a/makima/src/server/handlers/mesh_supervisor.rs b/makima/src/server/handlers/mesh_supervisor.rs
index 016367f..a0a3a96 100644
--- a/makima/src/server/handlers/mesh_supervisor.rs
+++ b/makima/src/server/handlers/mesh_supervisor.rs
@@ -1267,15 +1267,9 @@ pub struct MergeTaskResponse {
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreatePRRequest {
- pub task_id: Uuid,
+ pub branch: String,
pub title: String,
pub body: Option<String>,
- #[serde(default = "default_base_branch")]
- pub base_branch: String,
-}
-
-fn default_base_branch() -> String {
- "main".to_string()
}
/// Response for PR creation.
@@ -1513,48 +1507,53 @@ pub async fn create_pr(
headers: HeaderMap,
Json(request): Json<CreatePRRequest>,
) -> impl IntoResponse {
- let (_supervisor_id, owner_id) = match verify_supervisor_auth(&state, &headers, None).await {
+ let (supervisor_id, _owner_id) = match verify_supervisor_auth(&state, &headers, None).await {
Ok(ids) => ids,
Err(e) => return e.into_response(),
};
let pool = state.db_pool.as_ref().unwrap();
- // Get the target task
- let task = match repository::get_task_for_owner(pool, request.task_id, owner_id).await {
+ // Get the supervisor's own task to find daemon and base_branch
+ let task = match repository::get_task(pool, supervisor_id).await {
Ok(Some(t)) => t,
Ok(None) => {
return (
StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Task not found")),
+ Json(ApiError::new("NOT_FOUND", "Supervisor task not found")),
).into_response();
}
Err(e) => {
- tracing::error!(error = %e, "Failed to get task");
+ tracing::error!(error = %e, "Failed to get supervisor task");
return (
StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", "Failed to get task")),
+ Json(ApiError::new("DB_ERROR", "Failed to get supervisor task")),
).into_response();
}
};
- // Get daemon running the task
+ // Get daemon running the supervisor
let Some(daemon_id) = task.daemon_id else {
return (
StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("NO_DAEMON", "Task has no assigned daemon")),
+ Json(ApiError::new("NO_DAEMON", "Supervisor has no assigned daemon")),
).into_response();
};
+ // Use base_branch from the task's repository config, falling back to "main"
+ let base_branch = task.base_branch.unwrap_or_else(|| "main".to_string());
+
// Subscribe to PR results BEFORE sending the command
let mut rx = state.pr_results.subscribe();
- // Send CreatePR command to daemon
+ // Send CreatePR command to daemon using the supervisor's task ID
+ // (the branch is in the supervisor's worktree)
let cmd = DaemonCommand::CreatePR {
- task_id: request.task_id,
+ task_id: supervisor_id,
title: request.title.clone(),
body: request.body.clone(),
- base_branch: request.base_branch.clone(),
+ base_branch,
+ branch: request.branch.clone(),
};
if let Err(e) = state.send_daemon_command(daemon_id, cmd).await {
@@ -1571,7 +1570,7 @@ pub async fn create_pr(
loop {
match rx.recv().await {
Ok(notification) => {
- if notification.task_id == request.task_id {
+ if notification.task_id == supervisor_id {
return Some(notification);
}
// Not our task, keep waiting
@@ -1594,7 +1593,7 @@ pub async fn create_pr(
(
status,
Json(CreatePRResponse {
- task_id: request.task_id,
+ task_id: supervisor_id,
success: notification.success,
message: notification.message,
pr_url: notification.pr_url,
@@ -1607,7 +1606,7 @@ pub async fn create_pr(
(
StatusCode::GATEWAY_TIMEOUT,
Json(CreatePRResponse {
- task_id: request.task_id,
+ task_id: supervisor_id,
success: false,
message: "PR creation timed out waiting for daemon response".to_string(),
pr_url: None,
diff --git a/makima/src/server/state.rs b/makima/src/server/state.rs
index bf8f6f2..041b101 100644
--- a/makima/src/server/state.rs
+++ b/makima/src/server/state.rs
@@ -461,9 +461,11 @@ pub enum DaemonCommand {
task_id: Uuid,
title: String,
body: Option<String>,
- /// Base branch for the PR (default: main)
+ /// Base branch for the PR
#[serde(rename = "baseBranch")]
base_branch: String,
+ /// Source branch name to push and create PR from
+ branch: String,
},
/// Get the diff for a task's changes