Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package amake for openSUSE:Factory checked 
in at 2026-06-23 17:41:18
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/amake (Old)
 and      /work/SRC/openSUSE:Factory/.amake.new.1956 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "amake"

Tue Jun 23 17:41:18 2026 rev:3 rq:1361280 version:0.5.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/amake/amake.changes      2026-05-12 
19:30:59.465836579 +0200
+++ /work/SRC/openSUSE:Factory/.amake.new.1956/amake.changes    2026-06-23 
17:44:17.877477383 +0200
@@ -1,0 +2,10 @@
+Tue Jun 23 06:56:09 UTC 2026 - Alessio Biancalana <[email protected]>
+
+- Update to version 0.5.0:
+  * chore: update project version to 0.5.0 (#22)
+  * build(deps): bump actions/checkout from 6 to 7 (#21)
+  * build(deps): bump assert_cmd from 2.2.1 to 2.2.2 (#19)
+  * feat: implement model selection (#20)
+  * feat: add `pi` adapter
+
+-------------------------------------------------------------------

Old:
----
  amake-0.4.0.tar.zst

New:
----
  amake-0.5.0.tar.zst

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ amake.spec ++++++
--- /var/tmp/diff_new_pack.nOydQ9/_old  2026-06-23 17:44:20.101554893 +0200
+++ /var/tmp/diff_new_pack.nOydQ9/_new  2026-06-23 17:44:20.105555032 +0200
@@ -16,7 +16,7 @@
 #
 
 Name:           amake
-Version:        0.4.0
+Version:        0.5.0
 Release:        0
 Summary:        A task runner for AI CLI tools
 # TODO: Run `cargo lock2rpmprovides --spdx` after vendoring to get the

++++++ _service ++++++
--- /var/tmp/diff_new_pack.nOydQ9/_old  2026-06-23 17:44:20.157556843 +0200
+++ /var/tmp/diff_new_pack.nOydQ9/_new  2026-06-23 17:44:20.161556984 +0200
@@ -2,7 +2,7 @@
   <service name="obs_scm" mode="manual">
     <param name="url">https://github.com/dottorblaster/amake.git</param>
     <param name="scm">git</param>
-    <param name="revision">v0.4.0</param>
+    <param name="revision">v0.5.0</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(\d+\.\d+\.\d+)</param>
     <param name="versionrewrite-replacement">\1</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.nOydQ9/_old  2026-06-23 17:44:20.185557820 +0200
+++ /var/tmp/diff_new_pack.nOydQ9/_new  2026-06-23 17:44:20.189557959 +0200
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param 
name="url">https://github.com/dottorblaster/amake.git</param>
-              <param 
name="changesrevision">3e06dac50819379550a00d6f6967413298df5ad4</param></service></servicedata>
+              <param 
name="changesrevision">f3aa5caf1cfa242c6d0f911e12388afb1cca6ece</param></service></servicedata>
 (No newline at EOF)
 

++++++ amake-0.4.0.tar.zst -> amake-0.5.0.tar.zst ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amake-0.4.0/.github/workflows/ci.yml 
new/amake-0.5.0/.github/workflows/ci.yml
--- old/amake-0.4.0/.github/workflows/ci.yml    2026-05-08 15:34:05.000000000 
+0200
+++ new/amake-0.5.0/.github/workflows/ci.yml    2026-06-22 17:36:13.000000000 
+0200
@@ -13,7 +13,7 @@
   build-and-test:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v6
+      - uses: actions/checkout@v7
 
       - uses: dtolnay/rust-toolchain@stable
         with:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amake-0.4.0/Cargo.lock new/amake-0.5.0/Cargo.lock
--- old/amake-0.4.0/Cargo.lock  2026-05-08 15:34:05.000000000 +0200
+++ new/amake-0.5.0/Cargo.lock  2026-06-22 17:36:13.000000000 +0200
@@ -92,9 +92,9 @@
 
 [[package]]
 name = "assert_cmd"
-version = "2.2.1"
+version = "2.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "39bae1d3fa576f7c6519514180a72559268dd7d1fe104070956cb687bc6673bd"
+checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6"
 dependencies = [
  "anstyle",
  "bstr",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amake-0.4.0/Cargo.toml new/amake-0.5.0/Cargo.toml
--- old/amake-0.4.0/Cargo.toml  2026-05-08 15:34:05.000000000 +0200
+++ new/amake-0.5.0/Cargo.toml  2026-06-22 17:36:13.000000000 +0200
@@ -1,6 +1,6 @@
 [package]
 name = "amake"
-version = "0.3.0"
+version = "0.5.0"
 edition = "2024"
 license = "Apache-2.0"
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amake-0.4.0/README.md new/amake-0.5.0/README.md
--- old/amake-0.4.0/README.md   2026-05-08 15:34:05.000000000 +0200
+++ new/amake-0.5.0/README.md   2026-06-22 17:36:13.000000000 +0200
@@ -59,11 +59,14 @@
 tool = "aider"                          # override the default tool
 prompt = "Refactor error handling to use thiserror."
 files = ["src/main.rs", "src/error.rs"] # context files passed to the tool
+model = "gpt-4"                         # model to use (passed as --model to 
the tool)
 auto_approve = true                     # tool-specific "don't ask" flag
-extra_args = ["--model", "gpt-4"]       # passed verbatim to the tool CLI
+extra_args = ["--verbose"]              # passed verbatim to the tool CLI 
(last-occurrence wins for any duplicate flag)
 timeout = 300                           # seconds; kill the child if it runs 
longer (optional)
 ```
 
+`model` is just a first-class alias for the tool's own `--model` flag — useful 
for tools like `claude-code` where the flag spelling is the same, and easier to 
read than `extra_args = ["--model", "gpt-4"]`. It can be set on a task or under 
`[defaults]`. If both a task and `extra_args` set `--model`, the value in 
`extra_args` comes later in argv and wins (every tool in the table uses 
last-occurrence-wins for duplicate flags).
+
 ### Timeout and retry
 
 AI CLIs hang and flake. `timeout` (in seconds) caps how long a single attempt 
may run; `[tasks.<name>.retry]` re-runs failed attempts with backoff:
@@ -146,13 +149,15 @@
 aider
 claude-code
 copilot
+pi
 ```
 
-| Adapter | Binary | Auto-approve | Notes |
-|---|---|---|---|
-| `claude-code` | `claude` | `--dangerously-skip-permissions` | Prompt via 
`--print` |
-| `aider` | `aider` | `--yes` | Prompt via `--message` |
-| `copilot` | `gh` | (none) | Runs `gh copilot suggest -t shell` |
+| Adapter | Binary | Auto-approve | Model | Notes |
+|---|---|---|---|---|
+| `claude-code` | `claude` | `--dangerously-skip-permissions` | `--model 
<name>` | Prompt via `--print` |
+| `aider` | `aider` | `--yes` | `--model <name>` | Prompt via `--message` |
+| `copilot` | `gh` | (none) | `--model <name>` | Runs `gh copilot suggest -t 
shell` |
+| `pi` | `pi` | (none) | `--model <name>` | Prompt via `-p`; context files via 
inline `@path` |
 
 If the tool name doesn't match a built-in, amake treats it as a bare binary 
and passes `extra_args` + prompt as a positional arg. Good enough for most 
things:
 
@@ -263,7 +268,7 @@
 prompt = "Refactor error handling to use thiserror."
 files = ["src/main.rs", "src/error.rs"]
 auto_approve = true
-extra_args = ["--model", "gpt-4"]
+model = "gpt-4"
 ```
 
 ```
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amake-0.4.0/src/adapter/aider.rs 
new/amake-0.5.0/src/adapter/aider.rs
--- old/amake-0.4.0/src/adapter/aider.rs        2026-05-08 15:34:05.000000000 
+0200
+++ new/amake-0.5.0/src/adapter/aider.rs        2026-06-22 17:36:13.000000000 
+0200
@@ -43,6 +43,10 @@
             cmd.arg("--file").arg(file);
         }
 
+        if let Some(model) = &task.model {
+            cmd.arg("--model").arg(model);
+        }
+
         cmd.args(&task.extra_args);
 
         apply_workdir(&mut cmd, sandboxed, workdir);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amake-0.4.0/src/adapter/claude_code.rs 
new/amake-0.5.0/src/adapter/claude_code.rs
--- old/amake-0.4.0/src/adapter/claude_code.rs  2026-05-08 15:34:05.000000000 
+0200
+++ new/amake-0.5.0/src/adapter/claude_code.rs  2026-06-22 17:36:13.000000000 
+0200
@@ -43,6 +43,10 @@
             cmd.arg("--file").arg(file);
         }
 
+        if let Some(model) = &task.model {
+            cmd.arg("--model").arg(model);
+        }
+
         cmd.args(&task.extra_args);
         cmd.arg(&task.prompt);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amake-0.4.0/src/adapter/copilot.rs 
new/amake-0.5.0/src/adapter/copilot.rs
--- old/amake-0.4.0/src/adapter/copilot.rs      2026-05-08 15:34:05.000000000 
+0200
+++ new/amake-0.5.0/src/adapter/copilot.rs      2026-06-22 17:36:13.000000000 
+0200
@@ -40,6 +40,9 @@
         }
 
         cmd.args(["copilot", "suggest", "-t", "shell"]);
+        if let Some(model) = &task.model {
+            cmd.arg("--model").arg(model);
+        }
         cmd.arg(&task.prompt);
         cmd.args(&task.extra_args);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amake-0.4.0/src/adapter/generic.rs 
new/amake-0.5.0/src/adapter/generic.rs
--- old/amake-0.4.0/src/adapter/generic.rs      2026-05-08 15:34:05.000000000 
+0200
+++ new/amake-0.5.0/src/adapter/generic.rs      2026-06-22 17:36:13.000000000 
+0200
@@ -50,6 +50,10 @@
             );
         }
 
+        if let Some(model) = &task.model {
+            cmd.arg("--model").arg(model);
+        }
+
         cmd.args(&task.extra_args);
         cmd.arg(&task.prompt);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amake-0.4.0/src/adapter/mod.rs 
new/amake-0.5.0/src/adapter/mod.rs
--- old/amake-0.4.0/src/adapter/mod.rs  2026-05-08 15:34:05.000000000 +0200
+++ new/amake-0.5.0/src/adapter/mod.rs  2026-06-22 17:36:13.000000000 +0200
@@ -2,6 +2,7 @@
 mod claude_code;
 mod copilot;
 mod generic;
+mod pi;
 mod sandbox;
 
 use crate::config::Task;
@@ -39,6 +40,7 @@
         );
         adapters.insert("aider".into(), Box::new(aider::AiderAdapter));
         adapters.insert("copilot".into(), Box::new(copilot::CopilotAdapter));
+        adapters.insert("pi".into(), Box::new(pi::PiAdapter));
         Self { adapters }
     }
 
@@ -93,6 +95,7 @@
             retry: None,
             idle_warn: None,
             idle_kill: None,
+            model: None,
         }
     }
 
@@ -176,6 +179,68 @@
         assert_eq!(args, &["copilot", "suggest", "-t", "shell", "Suggest"]);
     }
 
+    // -- pi adapter tests --
+
+    #[test]
+    fn pi_basic() {
+        let adapter = pi::PiAdapter;
+        let task = make_task("Hello");
+        let cmd = adapter.build_command(&task, None, false, None);
+        assert_eq!(cmd.get_program(), "pi");
+        let args = get_args(&cmd);
+        assert_eq!(args, &["-p", "Hello"]);
+    }
+
+    #[test]
+    fn pi_auto_approve() {
+        let adapter = pi::PiAdapter;
+        let task = make_task("Hello");
+        // auto_approve=true emits a warning but passes no extra flags
+        let cmd = adapter.build_command(&task, None, true, None);
+        let args = get_args(&cmd);
+        assert_eq!(args, &["-p", "Hello"]);
+    }
+
+    #[test]
+    fn pi_with_files() {
+        let adapter = pi::PiAdapter;
+        let mut task = make_task("Hello");
+        task.files = vec![PathBuf::from("a.rs"), PathBuf::from("b.rs")];
+        let cmd = adapter.build_command(&task, None, false, None);
+        let args = get_args(&cmd);
+        assert_eq!(args, &["-p", "@a.rs", "@b.rs", "Hello"]);
+    }
+
+    #[test]
+    fn pi_with_files_and_extra_args() {
+        let adapter = pi::PiAdapter;
+        let mut task = make_task("Hi");
+        task.files = vec![PathBuf::from("src/main.rs")];
+        task.extra_args = vec!["--model".into(), "foo".into()];
+        let cmd = adapter.build_command(&task, None, false, None);
+        let args = get_args(&cmd);
+        assert_eq!(args, &["-p", "@src/main.rs", "--model", "foo", "Hi"]);
+    }
+
+    #[test]
+    fn pi_sandbox_falls_back() {
+        let adapter = pi::PiAdapter;
+        let task = make_task("Fix");
+        let sandbox = SandboxConfig::default();
+        let cmd = adapter.build_command(&task, None, true, Some(&sandbox));
+        // pi has no clampdown agent, so it falls back to running directly
+        assert_eq!(cmd.get_program(), "pi");
+    }
+
+    #[test]
+    fn pi_workdir_set_when_not_sandboxed() {
+        let adapter = pi::PiAdapter;
+        let task = make_task("Hello");
+        let workdir = PathBuf::from("/my/project");
+        let cmd = adapter.build_command(&task, Some(&workdir), false, None);
+        assert_eq!(cmd.get_current_dir(), Some(Path::new("/my/project")));
+    }
+
     #[test]
     fn generic_passthrough() {
         let adapter = GenericPassthrough::new("mytool");
@@ -244,6 +309,7 @@
         assert!(names.contains(&"claude-code"));
         assert!(names.contains(&"aider"));
         assert!(names.contains(&"copilot"));
+        assert!(names.contains(&"pi"));
     }
 
     #[test]
@@ -261,4 +327,230 @@
         let cmd = adapter.build_command(&task, Some(&workdir), false, None);
         assert_eq!(cmd.get_current_dir(), Some(Path::new("/my/project")));
     }
+
+    // -- model flag --
+
+    fn assert_contains_pair(args: &[&std::ffi::OsStr], key: &str, value: &str) 
{
+        let mut iter = args.iter();
+        let mut found = false;
+        while let Some(a) = iter.next() {
+            if a.to_string_lossy() == key
+                && iter
+                    .next()
+                    .map(|v| v.to_string_lossy() == value)
+                    .unwrap_or(false)
+            {
+                found = true;
+                break;
+            }
+        }
+        assert!(
+            found,
+            "expected {key} {value:?} in args, got {:?}",
+            args.iter()
+                .map(|a| a.to_string_lossy().into_owned())
+                .collect::<Vec<_>>()
+        );
+    }
+
+    fn assert_does_not_contain(args: &[&std::ffi::OsStr], key: &str) {
+        for a in args {
+            assert_ne!(
+                a.to_string_lossy(),
+                key,
+                "unexpected {key} in args: {:?}",
+                args
+            );
+        }
+    }
+
+    #[test]
+    fn claude_code_model_set() {
+        let adapter = claude_code::ClaudeCodeAdapter;
+        let mut task = make_task("Hello");
+        task.model = Some("sonnet".into());
+        let cmd = adapter.build_command(&task, None, false, None);
+        let args = get_args(&cmd);
+        assert_contains_pair(&args, "--model", "sonnet");
+    }
+
+    #[test]
+    fn claude_code_model_unset() {
+        let adapter = claude_code::ClaudeCodeAdapter;
+        let task = make_task("Hello");
+        let cmd = adapter.build_command(&task, None, false, None);
+        let args = get_args(&cmd);
+        assert_does_not_contain(&args, "--model");
+    }
+
+    #[test]
+    fn claude_code_model_before_extra_args() {
+        let adapter = claude_code::ClaudeCodeAdapter;
+        let mut task = make_task("Hello");
+        task.model = Some("sonnet".into());
+        task.extra_args = vec!["--model".into(), "opus".into()];
+        let cmd = adapter.build_command(&task, None, false, None);
+        let args = get_args(&cmd);
+        // The config's --model comes first; the extra_args --model comes 
later and wins.
+        let pos_sonnet = args
+            .iter()
+            .position(|a| a.to_string_lossy() == "--model")
+            .expect("first --model");
+        assert_eq!(args[pos_sonnet + 1], "sonnet");
+        assert_eq!(args[pos_sonnet + 2], "--model");
+        assert_eq!(args[pos_sonnet + 3], "opus");
+    }
+
+    #[test]
+    fn aider_model_set() {
+        let adapter = aider::AiderAdapter;
+        let mut task = make_task("Fix it");
+        task.model = Some("gpt-4".into());
+        let cmd = adapter.build_command(&task, None, true, None);
+        let args = get_args(&cmd);
+        assert_contains_pair(&args, "--model", "gpt-4");
+    }
+
+    #[test]
+    fn aider_model_unset() {
+        let adapter = aider::AiderAdapter;
+        let task = make_task("Fix it");
+        let cmd = adapter.build_command(&task, None, true, None);
+        let args = get_args(&cmd);
+        assert_does_not_contain(&args, "--model");
+    }
+
+    #[test]
+    fn aider_model_before_extra_args() {
+        let adapter = aider::AiderAdapter;
+        let mut task = make_task("Fix it");
+        task.model = Some("gpt-4".into());
+        task.extra_args = vec!["--model".into(), "opus".into()];
+        let cmd = adapter.build_command(&task, None, false, None);
+        let args = get_args(&cmd);
+        let pos = args
+            .iter()
+            .position(|a| a.to_string_lossy() == "--model")
+            .unwrap();
+        assert_eq!(args[pos + 1], "gpt-4");
+        assert_eq!(args[pos + 2], "--model");
+        assert_eq!(args[pos + 3], "opus");
+    }
+
+    #[test]
+    fn copilot_model_set() {
+        let adapter = copilot::CopilotAdapter;
+        let mut task = make_task("Suggest");
+        task.model = Some("gpt-4o".into());
+        let cmd = adapter.build_command(&task, None, false, None);
+        let args = get_args(&cmd);
+        assert_contains_pair(&args, "--model", "gpt-4o");
+    }
+
+    #[test]
+    fn copilot_model_unset() {
+        let adapter = copilot::CopilotAdapter;
+        let task = make_task("Suggest");
+        let cmd = adapter.build_command(&task, None, false, None);
+        let args = get_args(&cmd);
+        assert_does_not_contain(&args, "--model");
+    }
+
+    #[test]
+    fn copilot_model_before_extra_args() {
+        let adapter = copilot::CopilotAdapter;
+        let mut task = make_task("Suggest");
+        task.model = Some("gpt-4o".into());
+        task.extra_args = vec!["--model".into(), "haiku".into()];
+        let cmd = adapter.build_command(&task, None, false, None);
+        let args = get_args(&cmd);
+        // For copilot, the prompt sits between the config --model and the
+        // extra_args --model, so the extra-args value lands later in argv and
+        // wins. Find the LAST --model occurrence and confirm it carries 
"haiku".
+        let last_pos = args
+            .iter()
+            .rposition(|a| a.to_string_lossy() == "--model")
+            .unwrap();
+        assert_eq!(args[last_pos + 1], "haiku");
+        // The config value still appears earlier in argv.
+        let first_pos = args
+            .iter()
+            .position(|a| a.to_string_lossy() == "--model")
+            .unwrap();
+        assert_eq!(args[first_pos + 1], "gpt-4o");
+    }
+
+    #[test]
+    fn pi_model_set() {
+        let adapter = pi::PiAdapter;
+        let mut task = make_task("Hello");
+        task.model = Some("claude".into());
+        let cmd = adapter.build_command(&task, None, false, None);
+        let args = get_args(&cmd);
+        assert_contains_pair(&args, "--model", "claude");
+    }
+
+    #[test]
+    fn pi_model_unset() {
+        let adapter = pi::PiAdapter;
+        let task = make_task("Hello");
+        let cmd = adapter.build_command(&task, None, false, None);
+        let args = get_args(&cmd);
+        assert_does_not_contain(&args, "--model");
+    }
+
+    #[test]
+    fn pi_model_before_extra_args() {
+        let adapter = pi::PiAdapter;
+        let mut task = make_task("Hi");
+        task.files = vec![PathBuf::from("src/main.rs")];
+        task.model = Some("claude".into());
+        task.extra_args = vec!["--model".into(), "gpt-4".into()];
+        let cmd = adapter.build_command(&task, None, false, None);
+        let args = get_args(&cmd);
+        // --model from config comes after the @file tokens but before 
extra_args
+        let pos = args
+            .iter()
+            .position(|a| a.to_string_lossy() == "--model")
+            .unwrap();
+        assert_eq!(args[pos + 1], "claude");
+        assert_eq!(args[pos + 2], "--model");
+        assert_eq!(args[pos + 3], "gpt-4");
+    }
+
+    #[test]
+    fn generic_passthrough_model_set() {
+        let adapter = GenericPassthrough::new("mytool");
+        let mut task = make_task("Do stuff");
+        task.model = Some("sonnet".into());
+        let cmd = adapter.build_command(&task, None, false, None);
+        let args = get_args(&cmd);
+        assert_contains_pair(&args, "--model", "sonnet");
+    }
+
+    #[test]
+    fn generic_passthrough_model_unset() {
+        let adapter = GenericPassthrough::new("mytool");
+        let task = make_task("Do stuff");
+        let cmd = adapter.build_command(&task, None, false, None);
+        let args = get_args(&cmd);
+        assert_does_not_contain(&args, "--model");
+    }
+
+    #[test]
+    fn generic_passthrough_model_before_extra_args() {
+        let adapter = GenericPassthrough::new("mytool");
+        let mut task = make_task("Do stuff");
+        task.model = Some("sonnet".into());
+        task.extra_args = vec!["--model".into(), "opus".into()];
+        let cmd = adapter.build_command(&task, None, false, None);
+        let args = get_args(&cmd);
+        let pos = args
+            .iter()
+            .position(|a| a.to_string_lossy() == "--model")
+            .unwrap();
+        assert_eq!(args[pos + 1], "sonnet");
+        assert_eq!(args[pos + 2], "--model");
+        assert_eq!(args[pos + 3], "opus");
+    }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amake-0.4.0/src/adapter/pi.rs 
new/amake-0.5.0/src/adapter/pi.rs
--- old/amake-0.4.0/src/adapter/pi.rs   1970-01-01 01:00:00.000000000 +0100
+++ new/amake-0.5.0/src/adapter/pi.rs   2026-06-22 17:36:13.000000000 +0200
@@ -0,0 +1,60 @@
+use super::Adapter;
+use super::sandbox::{apply_workdir, start_sandboxed_command};
+use crate::config::Task;
+use crate::sandbox::SandboxConfig;
+use std::path::Path;
+use std::process::Command;
+
+pub struct PiAdapter;
+
+impl Adapter for PiAdapter {
+    fn name(&self) -> &str {
+        "pi"
+    }
+
+    fn clampdown_agent(&self) -> Option<&str> {
+        None
+    }
+
+    fn build_command(
+        &self,
+        task: &Task,
+        workdir: Option<&Path>,
+        auto_approve: bool,
+        sandbox: Option<&SandboxConfig>,
+    ) -> Command {
+        let mut cmd = Command::new("pi");
+
+        let sandboxed = start_sandboxed_command(
+            &mut cmd,
+            self.clampdown_agent(),
+            self.name(),
+            sandbox,
+            workdir,
+        );
+
+        if auto_approve {
+            eprintln!("warning: auto_approve is set for pi but no known flag 
exists — ignoring");
+        }
+
+        cmd.arg("-p");
+
+        // pi uses inline @<path> tokens in the prompt for context files.
+        // Each is passed as a separate argv entry so paths containing
+        // spaces are preserved by the kernel's argv boundary.
+        for file in &task.files {
+            cmd.arg(format!("@{}", file.display()));
+        }
+
+        if let Some(model) = &task.model {
+            cmd.arg("--model").arg(model);
+        }
+
+        cmd.args(&task.extra_args);
+        cmd.arg(&task.prompt);
+
+        apply_workdir(&mut cmd, sandboxed, workdir);
+
+        cmd
+    }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amake-0.4.0/src/config.rs 
new/amake-0.5.0/src/config.rs
--- old/amake-0.4.0/src/config.rs       2026-05-08 15:34:05.000000000 +0200
+++ new/amake-0.5.0/src/config.rs       2026-06-22 17:36:13.000000000 +0200
@@ -15,6 +15,7 @@
     pub retry: Option<RetryConfig>,
     pub idle_warn: Option<u64>,
     pub idle_kill: Option<u64>,
+    pub model: Option<String>,
 }
 
 #[derive(Debug, Deserialize, Clone, Copy, PartialEq, Eq, Default)]
@@ -75,6 +76,7 @@
     retry: Option<RetryConfig>,
     idle_warn: Option<u64>,
     idle_kill: Option<u64>,
+    model: Option<String>,
 }
 
 #[derive(Debug, Clone)]
@@ -92,6 +94,7 @@
     pub retry: Option<RetryConfig>,
     pub idle_warn: Option<u64>,
     pub idle_kill: Option<u64>,
+    pub model: Option<String>,
 }
 
 impl From<RawTask> for Task {
@@ -116,6 +119,7 @@
             retry: raw.retry,
             idle_warn: raw.idle_warn,
             idle_kill: raw.idle_kill,
+            model: raw.model,
         }
     }
 }
@@ -196,6 +200,13 @@
             .ok_or_else(|| Error::NoTool(task_name.into()))
     }
 
+    pub fn effective_model(&self, task: &Task) -> Option<String> {
+        task.model
+            .as_ref()
+            .or(self.defaults.model.as_ref())
+            .cloned()
+    }
+
     pub fn effective_workdir(&self, task: &Task) -> Option<PathBuf> {
         task.workdir
             .as_ref()
@@ -672,4 +683,58 @@
         assert_eq!(effective.attempts, 5);
         assert_eq!(effective.backoff, BackoffStrategy::Exponential);
     }
+
+    #[test]
+    fn parses_model_field() {
+        let toml = r#"
+[tasks.t]
+prompt = "x"
+model = "sonnet"
+"#;
+        let cfg = Config::from_str(toml, Path::new("Amakefile")).unwrap();
+        assert_eq!(cfg.tasks["t"].model.as_deref(), Some("sonnet"));
+    }
+
+    #[test]
+    fn effective_model_inherits_default() {
+        let toml = r#"
+[defaults]
+model = "opus"
+
+[tasks.t]
+prompt = "x"
+"#;
+        let cfg = Config::from_str(toml, Path::new("Amakefile")).unwrap();
+        assert_eq!(
+            cfg.effective_model(&cfg.tasks["t"]).as_deref(),
+            Some("opus")
+        );
+    }
+
+    #[test]
+    fn effective_model_task_overrides_default() {
+        let toml = r#"
+[defaults]
+model = "opus"
+
+[tasks.t]
+prompt = "x"
+model = "sonnet"
+"#;
+        let cfg = Config::from_str(toml, Path::new("Amakefile")).unwrap();
+        assert_eq!(
+            cfg.effective_model(&cfg.tasks["t"]).as_deref(),
+            Some("sonnet")
+        );
+    }
+
+    #[test]
+    fn effective_model_none_when_unset() {
+        let toml = r#"
+[tasks.t]
+prompt = "x"
+"#;
+        let cfg = Config::from_str(toml, Path::new("Amakefile")).unwrap();
+        assert!(cfg.effective_model(&cfg.tasks["t"]).is_none());
+    }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amake-0.4.0/src/runner.rs 
new/amake-0.5.0/src/runner.rs
--- old/amake-0.4.0/src/runner.rs       2026-05-08 15:34:05.000000000 +0200
+++ new/amake-0.5.0/src/runner.rs       2026-06-22 17:36:13.000000000 +0200
@@ -163,6 +163,7 @@
 
         let mut rendered_task = task.clone();
         rendered_task.prompt = rendered_prompt;
+        rendered_task.model = config.effective_model(task);
 
         let resolved = registry.resolve_or_generic(&tool);
         let adapter = resolved.adapter();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amake-0.4.0/tests/cli.rs new/amake-0.5.0/tests/cli.rs
--- old/amake-0.4.0/tests/cli.rs        2026-05-08 15:34:05.000000000 +0200
+++ new/amake-0.5.0/tests/cli.rs        2026-06-22 17:36:13.000000000 +0200
@@ -40,7 +40,8 @@
     amake().arg("adapters").assert().success().stdout(
         predicate::str::contains("claude-code")
             .and(predicate::str::contains("aider"))
-            .and(predicate::str::contains("copilot")),
+            .and(predicate::str::contains("copilot"))
+            .and(predicate::str::contains("pi")),
     );
 }
 
@@ -1190,3 +1191,170 @@
         "two attempts of ~1s idle-kill plus 1s backoff should fit well under 
10s"
     );
 }
+
+// ── Model flag ──
+
+#[test]
+fn dry_run_emits_model_flag() {
+    let dir = TempDir::new().unwrap();
+    setup_amakefile(
+        &dir,
+        r#"
+[tasks.greet]
+tool = "claude-code"
+prompt = "Hello"
+model = "sonnet"
+"#,
+    );
+
+    amake()
+        .args([
+            "run",
+            "--dry-run",
+            "-f",
+            dir.path().join("Amakefile").to_str().unwrap(),
+            "greet",
+        ])
+        .assert()
+        .success()
+        .stdout(
+            predicate::str::contains("[greet]")
+                .and(predicate::str::contains("claude"))
+                .and(predicate::str::contains("--model"))
+                .and(predicate::str::contains("sonnet")),
+        );
+}
+
+#[test]
+fn dry_run_inherits_default_model() {
+    let dir = TempDir::new().unwrap();
+    setup_amakefile(
+        &dir,
+        r#"
+[defaults]
+tool = "claude-code"
+model = "opus"
+
+[tasks.greet]
+prompt = "Hello"
+"#,
+    );
+
+    amake()
+        .args([
+            "run",
+            "--dry-run",
+            "-f",
+            dir.path().join("Amakefile").to_str().unwrap(),
+            "greet",
+        ])
+        .assert()
+        .success()
+        
.stdout(predicate::str::contains("--model").and(predicate::str::contains("opus")));
+}
+
+#[test]
+fn dry_run_task_model_overrides_default() {
+    let dir = TempDir::new().unwrap();
+    setup_amakefile(
+        &dir,
+        r#"
+[defaults]
+tool = "claude-code"
+model = "opus"
+
+[tasks.greet]
+prompt = "Hello"
+model = "sonnet"
+"#,
+    );
+
+    amake()
+        .args([
+            "run",
+            "--dry-run",
+            "-f",
+            dir.path().join("Amakefile").to_str().unwrap(),
+            "greet",
+        ])
+        .assert()
+        .success()
+        
.stdout(predicate::str::contains("sonnet").and(predicate::str::contains("opus").not()));
+}
+
+#[test]
+fn extra_args_model_overrides_config_model() {
+    let dir = TempDir::new().unwrap();
+    setup_amakefile(
+        &dir,
+        r#"
+[tasks.greet]
+tool = "claude-code"
+prompt = "Hello"
+model = "sonnet"
+extra_args = ["--model", "opus"]
+"#,
+    );
+
+    let output = amake()
+        .args([
+            "run",
+            "--dry-run",
+            "-f",
+            dir.path().join("Amakefile").to_str().unwrap(),
+            "greet",
+        ])
+        .assert()
+        .success()
+        .get_output()
+        .stdout
+        .clone();
+    let stdout = String::from_utf8(output).unwrap();
+    // The extra_args --model should appear later in the rendered command
+    // and therefore "win" against the config's --model.
+    let sonnet_pos = stdout
+        .find("sonnet")
+        .expect("expected 'sonnet' to appear in dry-run output");
+    let opus_pos = stdout
+        .rfind("opus")
+        .expect("expected 'opus' to appear in dry-run output");
+    let last_model_pos = stdout
+        .rfind("--model")
+        .expect("expected '--model' to appear");
+    // The last --model must be the extra_args one carrying "opus".
+    let after_last = &stdout[last_model_pos..];
+    assert!(
+        after_last.contains("opus"),
+        "expected last --model to carry 'opus', got tail: {after_last:?}"
+    );
+    // 'sonnet' must appear before 'opus' in the rendered command.
+    assert!(
+        sonnet_pos < opus_pos,
+        "expected 'sonnet' to appear before 'opus', got:\n{stdout}"
+    );
+}
+
+#[test]
+fn dry_run_no_model_when_unset() {
+    let dir = TempDir::new().unwrap();
+    setup_amakefile(
+        &dir,
+        r#"
+[tasks.greet]
+tool = "claude-code"
+prompt = "Hello"
+"#,
+    );
+
+    amake()
+        .args([
+            "run",
+            "--dry-run",
+            "-f",
+            dir.path().join("Amakefile").to_str().unwrap(),
+            "greet",
+        ])
+        .assert()
+        .success()
+        .stdout(predicate::str::contains("--model").not());
+}

++++++ amake.obsinfo ++++++
--- /var/tmp/diff_new_pack.nOydQ9/_old  2026-06-23 17:44:20.313562281 +0200
+++ /var/tmp/diff_new_pack.nOydQ9/_new  2026-06-23 17:44:20.317562420 +0200
@@ -1,5 +1,5 @@
 name: amake
-version: 0.4.0
-mtime: 1778247245
-commit: 3e06dac50819379550a00d6f6967413298df5ad4
+version: 0.5.0
+mtime: 1782142573
+commit: f3aa5caf1cfa242c6d0f911e12388afb1cca6ece
 

++++++ vendor.tar.zst ++++++
/work/SRC/openSUSE:Factory/amake/vendor.tar.zst 
/work/SRC/openSUSE:Factory/.amake.new.1956/vendor.tar.zst differ: char 7, line 1

Reply via email to