a73x

c3f85a05

Merge branch 'worktree-agent-aaae5596'

a73x   2026-03-21 16:21


diff --git a/src/cli.rs b/src/cli.rs
index 89ab8a3..9c1a270 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -157,6 +157,11 @@ pub enum IssueCmd {
        /// Issue ID (prefix match)
        id: String,
    },
    /// Delete an issue (removes the local collab ref)
    Delete {
        /// Issue ID (prefix match)
        id: String,
    },
}

#[derive(Subcommand)]
@@ -275,4 +280,9 @@ pub enum PatchCmd {
        #[arg(short, long)]
        reason: Option<String>,
    },
    /// Delete a patch (removes the local collab ref)
    Delete {
        /// Patch ID (prefix match)
        id: String,
    },
}
diff --git a/src/issue.rs b/src/issue.rs
index cee4408..3636152 100644
--- a/src/issue.rs
+++ b/src/issue.rs
@@ -267,6 +267,12 @@ pub fn close(
    Ok(())
}

pub fn delete(repo: &Repository, id_prefix: &str) -> Result<String, crate::error::Error> {
    let (ref_name, id) = state::resolve_issue_ref(repo, id_prefix)?;
    repo.find_reference(&ref_name)?.delete()?;
    Ok(id)
}

pub fn reopen(repo: &Repository, id_prefix: &str) -> Result<(), crate::error::Error> {
    let sk = signing::load_signing_key(&signing::signing_key_dir()?)?;
    let (ref_name, _id) = state::resolve_issue_ref(repo, id_prefix)?;
diff --git a/src/lib.rs b/src/lib.rs
index ce146e9..a773cd3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -138,6 +138,11 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> {
                println!("Issue reopened.");
                Ok(())
            }
            IssueCmd::Delete { id } => {
                let full_id = issue::delete(repo, &id)?;
                println!("Deleted issue {:.8}", full_id);
                Ok(())
            }
        },
        Commands::Patch(cmd) => match cmd {
            PatchCmd::Create {
@@ -315,6 +320,11 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> {
                println!("Patch closed.");
                Ok(())
            }
            PatchCmd::Delete { id } => {
                let full_id = patch::delete(repo, &id)?;
                println!("Deleted patch {:.8}", full_id);
                Ok(())
            }
        },
        Commands::Status => {
            let project_status = status::compute(repo)?;
diff --git a/src/patch.rs b/src/patch.rs
index f09a353..e756238 100644
--- a/src/patch.rs
+++ b/src/patch.rs
@@ -339,6 +339,12 @@ pub fn generate_diff(repo: &Repository, patch: &state::PatchState) -> Result<Str
    Ok(output)
}

pub fn delete(repo: &Repository, id_prefix: &str) -> Result<String, crate::error::Error> {
    let (ref_name, id) = state::resolve_patch_ref(repo, id_prefix)?;
    repo.find_reference(&ref_name)?.delete()?;
    Ok(id)
}

pub fn close(
    repo: &Repository,
    id_prefix: &str,
diff --git a/tests/cli_test.rs b/tests/cli_test.rs
index 497ed3f..2ea1c65 100644
--- a/tests/cli_test.rs
+++ b/tests/cli_test.rs
@@ -864,3 +864,77 @@ fn test_init_key_creates_key_files() {
    );
    assert!(stdout.contains("Signing key generated"));
}

// ===========================================================================
// Issue delete
// ===========================================================================

#[test]
fn test_issue_delete_removes_from_list() {
    let repo = TestRepo::new("Alice", "alice@example.com");
    let id = repo.issue_open("Bug to delete");

    // Verify it shows up
    let out = repo.run_ok(&["issue", "list"]);
    assert!(out.contains("Bug to delete"));

    // Delete it
    let out = repo.run_ok(&["issue", "delete", &id]);
    assert!(out.contains("Deleted issue"));

    // Verify it's gone
    let out = repo.run_ok(&["issue", "list", "--all"]);
    assert!(!out.contains("Bug to delete"));
}

#[test]
fn test_issue_delete_nonexistent_errors() {
    let repo = TestRepo::new("Alice", "alice@example.com");
    let err = repo.run_err(&["issue", "delete", "deadbeef"]);
    assert!(!err.is_empty());
}

#[test]
fn test_issue_delete_then_show_errors() {
    let repo = TestRepo::new("Alice", "alice@example.com");
    let id = repo.issue_open("Ephemeral");
    repo.run_ok(&["issue", "delete", &id]);
    repo.run_err(&["issue", "show", &id]);
}

// ===========================================================================
// Patch delete
// ===========================================================================

#[test]
fn test_patch_delete_removes_from_list() {
    let repo = TestRepo::new("Alice", "alice@example.com");
    let id = repo.patch_create("Patch to delete");

    // Verify it shows up
    let out = repo.run_ok(&["patch", "list"]);
    assert!(out.contains("Patch to delete"));

    // Delete it
    let out = repo.run_ok(&["patch", "delete", &id]);
    assert!(out.contains("Deleted patch"));

    // Verify it's gone
    let out = repo.run_ok(&["patch", "list", "--all"]);
    assert!(!out.contains("Patch to delete"));
}

#[test]
fn test_patch_delete_nonexistent_errors() {
    let repo = TestRepo::new("Alice", "alice@example.com");
    let err = repo.run_err(&["patch", "delete", "deadbeef"]);
    assert!(!err.is_empty());
}

#[test]
fn test_patch_delete_then_show_errors() {
    let repo = TestRepo::new("Alice", "alice@example.com");
    let id = repo.patch_create("Ephemeral patch");
    repo.run_ok(&["patch", "delete", &id]);
    repo.run_err(&["patch", "show", &id]);
}