ai-dispatch 5.9.2

Multi-AI CLI team orchestrator
## v2.0 — New capabilities: prompt templates, multi-repo, webhooks, benchmarking

[[task]]
name = "prompt-templates"
agent = "codex"
skills = ["implementer"]
prompt = """
Add built-in prompt template support via `aid run codex --template bug-fix "description"`.

GOAL: Templates are markdown files in ~/.aid/templates/ that wrap user prompts with structured methodology. They complement skills (which inject methodology sections) by providing full prompt scaffolding.

IMPLEMENTATION:

1. Create src/templates.rs (extend or replace the existing minimal templates.rs):

   pub fn list_templates() -> Vec<String> — scan ~/.aid/templates/ for .md files, return stems
   pub fn load_template(name: &str) -> Result<String> — read template file content
   pub fn apply_template(template_content: &str, user_prompt: &str) -> String — replace {{prompt}} placeholder with user prompt

2. In src/main.rs Commands::Run, add:
   #[arg(long)]
   template: Option<String>,

3. In src/cmd/run.rs RunArgs, add: pub template: Option<String>
   In run(), after resolving skills but before dispatch, if template is set:
   - Load the template via templates::load_template()
   - Apply it: effective_prompt = templates::apply_template(&template_content, &args.prompt)
   - Use effective_prompt as the prompt sent to the agent

4. In src/main.rs Commands::Config, add a Templates variant (like Skills and Agents):
   aid config templates — list available templates

5. Ship 3 default templates (create in a new directory default-templates/ for reference):
   - bug-fix.md: structured reproduction → root cause → fix → verify workflow
   - feature.md: understand context → implement → test → document workflow
   - refactor.md: map dependencies → make change → verify behavior preserved workflow

   Each template should have {{prompt}} as the placeholder for the user's actual task description.

6. Add test for apply_template replacing {{prompt}}.

CONSTRAINTS:
- Create/modify: src/templates.rs, src/main.rs, src/cmd/run.rs, src/cmd/config.rs
- Create: default-templates/bug-fix.md, default-templates/feature.md, default-templates/refactor.md
- Keep templates.rs under 60 lines
- Do NOT reformat existing code
- cargo check and cargo test must pass
"""
dir = "."
worktree = "feat/prompt-templates"
verify = "auto"

[[task]]
name = "webhook-notifications"
agent = "codex"
skills = ["implementer"]
prompt = """
Add webhook notification support for task completion events.

GOAL: Configure webhooks in config.toml that fire on task completion, enabling Slack/Discord/custom notifications.

IMPLEMENTATION:

1. In src/config.rs, add webhook config:
   #[derive(Debug, Clone, Deserialize)]
   pub struct WebhookConfig {
       pub name: String,
       pub url: String,
       #[serde(default)]
       pub on_done: bool,     // fire on task success
       #[serde(default)]
       pub on_failed: bool,   // fire on task failure
       #[serde(default)]
       pub headers: Vec<(String, String)>,  // custom headers
   }

   Add to AidConfig:
   #[serde(default)]
   pub webhooks: Vec<WebhookConfig>,

   Config.toml example:
   [[webhook]]
   name = "slack-notify"
   url = "https://hooks.slack.com/..."
   on_done = true
   on_failed = true

2. Create src/webhook.rs (~60 lines):
   pub async fn fire_webhooks(config: &AidConfig, task: &Task, status: &str) {
       for webhook in &config.webhooks {
           if (status == "done" && webhook.on_done) || (status == "failed" && webhook.on_failed) {
               send_webhook(webhook, task).await;
           }
       }
   }

   async fn send_webhook(webhook: &WebhookConfig, task: &Task) {
       // Use std::process::Command to call curl (avoid adding reqwest dependency)
       // POST JSON body: { "task_id": "...", "agent": "...", "status": "...", "prompt": "...", "duration_ms": ... }
       // Fire and forget — log errors but don't block
   }

3. In src/background.rs run_task(), after recording completion, call fire_webhooks().
   Also in src/cmd/run.rs after foreground task completes, call fire_webhooks().

4. Add test for webhook config parsing.

CONSTRAINTS:
- Create: src/webhook.rs (under 80 lines)
- Modify: src/config.rs, src/background.rs, src/cmd/run.rs, src/main.rs (add mod webhook)
- Do NOT add reqwest or any HTTP client dependency — use curl via Command
- Do NOT reformat existing code
- cargo check and cargo test must pass
"""
dir = "."
worktree = "feat/webhooks"
verify = "auto"

[[task]]
name = "agent-benchmark"
agent = "codex"
skills = ["implementer"]
prompt = """
Add `aid benchmark` command to dispatch the same task to multiple agents and compare results.

GOAL: `aid benchmark "Fix the truncate bug" --agents codex,opencode --dir .` dispatches the task to both agents in parallel worktrees, waits for completion, and produces a comparison report.

IMPLEMENTATION:

1. In src/main.rs Commands enum, add:
   /// Benchmark a task across multiple agents
   Benchmark {
       /// Task prompt
       prompt: String,
       /// Comma-separated agents to compare
       #[arg(long)]
       agents: String,
       /// Working directory
       #[arg(short, long)]
       dir: Option<String>,
       /// Verify command
       #[arg(long, num_args = 0..=1, default_missing_value = "auto")]
       verify: Option<String>,
   },

2. Create src/cmd/benchmark.rs (~100 lines):

   pub async fn run(store: Arc<Store>, prompt: String, agents: String, dir: Option<String>, verify: Option<String>) -> Result<()> {
       let agent_list: Vec<&str> = agents.split(',').map(|s| s.trim()).collect();
       let mut task_ids = Vec::new();

       // Dispatch to each agent in parallel with unique worktree branches
       for agent_name in &agent_list {
           let branch = format!("bench/{agent_name}");
           let run_args = RunArgs { agent_name, prompt, dir, worktree: Some(branch), verify, bg: true, ... };
           let task_id = cmd::run::run(store.clone(), run_args).await?;
           task_ids.push((agent_name.to_string(), task_id));
       }

       // Wait for all tasks to complete
       println!("Waiting for {} agents...", agent_list.len());
       // Poll store until all tasks have terminal status (Done/Failed)
       loop {
           let all_done = task_ids.iter().all(|(_, id)| {
               store.get_task(id.as_str()).ok().flatten()
                   .map(|t| t.status.is_terminal()).unwrap_or(false)
           });
           if all_done { break; }
           tokio::time::sleep(Duration::from_secs(3)).await;
       }

       // Print comparison report
       println!("\n=== Benchmark Results ===");
       for (agent, task_id) in &task_ids {
           let task = store.get_task(task_id.as_str())?.unwrap();
           let duration = task.duration_ms.map(|ms| format!("{}s", ms/1000)).unwrap_or("-".into());
           let tokens = task.tokens.map(|t| t.to_string()).unwrap_or("-".into());
           let cost = cost::format_cost(task.cost_usd);
           println!("{:<12} {:<8} {:<10} {:<10} {:<8}", agent, task.status.label(), duration, tokens, cost);
       }
   }

3. Wire in main.rs match block.
4. Add mod benchmark to src/cmd/mod.rs.

CONSTRAINTS:
- Create: src/cmd/benchmark.rs (under 120 lines)
- Modify: src/main.rs, src/cmd/mod.rs
- Do NOT reformat existing code
- cargo check and cargo test must pass
"""
dir = "."
worktree = "feat/benchmark"
verify = "auto"

[[task]]
name = "multi-repo-dispatch"
agent = "codex"
skills = ["implementer"]
prompt = """
Add multi-repo task dispatch: `aid run codex "task" --repo /path/to/other-project`.

GOAL: Allow dispatching tasks to repos other than the current directory.
This is different from --dir (which sets the agent's working directory within the current repo).
--repo specifies a completely different git repository, and aid creates the worktree there.

IMPLEMENTATION:

1. In src/main.rs Commands::Run, add:
   /// Target repository path (defaults to current dir's git root)
   #[arg(long)]
   repo: Option<String>,

2. In src/cmd/run.rs RunArgs, add: pub repo: Option<String>

3. In run(), when --repo is set:
   - Use repo path as the base for worktree creation (instead of current dir)
   - Set the agent's --dir to the worktree path within that repo
   - Store the repo path in the task record for reference

4. In src/types.rs Task, add:
   pub repo_path: Option<String>,

5. In src/store.rs, add repo_path column to tasks table (in migrate()):
   ALTER TABLE tasks ADD COLUMN repo_path TEXT;

6. Show repo_path in `aid board` when it's set (add a column or show in task detail).

7. In src/cmd/run.rs where worktree is created, if repo is set:
   - Change to use the repo path as the git root for worktree::create_worktree()
   - The worktree will be created as a subdirectory of the repo's .git/worktrees

CONSTRAINTS:
- Modify: src/main.rs, src/cmd/run.rs, src/types.rs, src/store.rs, src/board.rs
- ~30 lines of new logic
- Do NOT reformat existing code
- Add test for migration adding repo_path column
- cargo check and cargo test must pass
"""
dir = "."
worktree = "feat/multi-repo"
verify = "auto"