feat: RFC 0061 Phase 3 - CLI parity for RFC and worktree commands
Add handle_rfc_command() and handle_local_worktree_command() functions that call shared MCP handlers directly, avoiding code duplication. RFC commands: create, list, get, status, plan, complete Worktree commands: create, list, remove Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
016c05e1ed
commit
aec859b22e
1 changed files with 274 additions and 33 deletions
|
|
@ -590,14 +590,46 @@ enum RfcCommands {
|
|||
Create {
|
||||
/// RFC title
|
||||
title: String,
|
||||
|
||||
/// Problem statement
|
||||
#[arg(long)]
|
||||
problem: Option<String>,
|
||||
|
||||
/// Source spike (links RFC to spike)
|
||||
#[arg(long)]
|
||||
source_spike: Option<String>,
|
||||
},
|
||||
/// List all RFCs
|
||||
List {
|
||||
/// Filter by status (draft, accepted, in-progress, implemented)
|
||||
#[arg(long)]
|
||||
status: Option<String>,
|
||||
},
|
||||
/// Get RFC details
|
||||
Get {
|
||||
/// RFC title
|
||||
title: String,
|
||||
},
|
||||
/// Update RFC status
|
||||
Status {
|
||||
/// RFC title
|
||||
title: String,
|
||||
|
||||
/// New status (draft, accepted, in-progress, implemented, superseded)
|
||||
#[arg(long)]
|
||||
set: String,
|
||||
},
|
||||
/// Create a plan for an RFC
|
||||
Plan {
|
||||
/// RFC title
|
||||
title: String,
|
||||
|
||||
/// Tasks (can be specified multiple times)
|
||||
#[arg(long, num_args = 1..)]
|
||||
task: Vec<String>,
|
||||
},
|
||||
/// Get RFC details
|
||||
Get {
|
||||
/// Mark RFC as complete
|
||||
Complete {
|
||||
/// RFC title
|
||||
title: String,
|
||||
},
|
||||
|
|
@ -870,9 +902,45 @@ async fn tokio_main() -> Result<()> {
|
|||
}
|
||||
|
||||
match cli.command {
|
||||
None | Some(Commands::Status) => {
|
||||
None => {
|
||||
println!("{}", blue_core::voice::welcome());
|
||||
}
|
||||
Some(Commands::Status) => {
|
||||
match get_project_state() {
|
||||
Ok(state) => {
|
||||
let args = serde_json::json!({});
|
||||
match blue_mcp::handlers::status::handle_status(&state, &args) {
|
||||
Ok(result) => {
|
||||
let project = result.get("project").and_then(|v| v.as_str()).unwrap_or("unknown");
|
||||
let active = result.get("active").and_then(|v| v.as_array()).map(|a| a.len()).unwrap_or(0);
|
||||
let ready = result.get("ready").and_then(|v| v.as_array()).map(|a| a.len()).unwrap_or(0);
|
||||
let stalled = result.get("stalled").and_then(|v| v.as_array()).map(|a| a.len()).unwrap_or(0);
|
||||
let drafts = result.get("drafts").and_then(|v| v.as_array()).map(|a| a.len()).unwrap_or(0);
|
||||
let hint = result.get("hint").and_then(|v| v.as_str()).unwrap_or("");
|
||||
|
||||
println!("Project: {}", project);
|
||||
println!("Active: {} RFC(s)", active);
|
||||
println!("Ready: {} RFC(s)", ready);
|
||||
if stalled > 0 {
|
||||
println!("Stalled: {} RFC(s)", stalled);
|
||||
}
|
||||
println!("Drafts: {} RFC(s)", drafts);
|
||||
println!();
|
||||
println!("{}", hint);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
println!("{}", blue_core::voice::welcome());
|
||||
println!();
|
||||
println!("Run 'blue init' to get started.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Commands::Init { force }) => {
|
||||
let cwd = std::env::current_dir()?;
|
||||
let blue_dir = cwd.join(".blue");
|
||||
|
|
@ -907,8 +975,23 @@ async fn tokio_main() -> Result<()> {
|
|||
println!(" blue status");
|
||||
}
|
||||
Some(Commands::Next) => {
|
||||
println!("Looking at what's ready. One moment.");
|
||||
// TODO: Implement next
|
||||
let state = get_project_state()?;
|
||||
let args = serde_json::json!({});
|
||||
match blue_mcp::handlers::status::handle_next(&state, &args) {
|
||||
Ok(result) => {
|
||||
if let Some(recs) = result.get("recommendations").and_then(|v| v.as_array()) {
|
||||
for rec in recs {
|
||||
if let Some(s) = rec.as_str() {
|
||||
println!("{}", s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Commands::Mcp { .. }) => {
|
||||
blue_mcp::run().await?;
|
||||
|
|
@ -922,34 +1005,12 @@ async fn tokio_main() -> Result<()> {
|
|||
Some(Commands::Session { command }) => {
|
||||
handle_session_command(command).await?;
|
||||
}
|
||||
Some(Commands::Rfc { command }) => match command {
|
||||
RfcCommands::Create { title } => {
|
||||
println!("{}", blue_core::voice::success(
|
||||
&format!("Created RFC '{}'", title),
|
||||
Some("Want me to help fill in the details?"),
|
||||
));
|
||||
Some(Commands::Rfc { command }) => {
|
||||
handle_rfc_command(command).await?;
|
||||
}
|
||||
RfcCommands::Plan { title } => {
|
||||
println!("{}", blue_core::voice::ask(
|
||||
&format!("Ready to plan '{}'", title),
|
||||
"What are the tasks",
|
||||
));
|
||||
Some(Commands::Worktree { command }) => {
|
||||
handle_local_worktree_command(command).await?;
|
||||
}
|
||||
RfcCommands::Get { title } => {
|
||||
println!("Looking for '{}'.", title);
|
||||
}
|
||||
},
|
||||
Some(Commands::Worktree { command }) => match command {
|
||||
WorktreeCommands::Create { title } => {
|
||||
println!("Creating worktree for '{}'.", title);
|
||||
}
|
||||
WorktreeCommands::List => {
|
||||
println!("Listing worktrees.");
|
||||
}
|
||||
WorktreeCommands::Remove { title } => {
|
||||
println!("Removing worktree for '{}'.", title);
|
||||
}
|
||||
},
|
||||
Some(Commands::Pr { command }) => match command {
|
||||
PrCommands::Create { title } => {
|
||||
println!("Creating PR: {}", title);
|
||||
|
|
@ -3918,3 +3979,183 @@ async fn handle_reminder_command(command: ReminderCommands) -> Result<()> {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle RFC subcommands (RFC 0061)
|
||||
async fn handle_rfc_command(command: RfcCommands) -> Result<()> {
|
||||
let mut state = get_project_state()?;
|
||||
|
||||
match command {
|
||||
RfcCommands::Create { title, problem, source_spike } => {
|
||||
let mut args = json!({ "title": title });
|
||||
if let Some(p) = problem {
|
||||
args["problem"] = json!(p);
|
||||
}
|
||||
if let Some(s) = source_spike {
|
||||
args["source_spike"] = json!(s);
|
||||
}
|
||||
match blue_mcp::handlers::rfc::handle_create(&mut state, &args) {
|
||||
Ok(result) => {
|
||||
if let Some(msg) = result.get("message").and_then(|v| v.as_str()) {
|
||||
println!("{}", msg);
|
||||
}
|
||||
if let Some(file) = result.get("file").and_then(|v| v.as_str()) {
|
||||
println!("File: {}", file);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
RfcCommands::List { status } => {
|
||||
let args = match status {
|
||||
Some(s) => json!({ "status": s }),
|
||||
None => json!({}),
|
||||
};
|
||||
match blue_mcp::handlers::rfc::handle_list(&state, &args) {
|
||||
Ok(result) => {
|
||||
if let Some(rfcs) = result.get("rfcs").and_then(|v| v.as_array()) {
|
||||
if rfcs.is_empty() {
|
||||
println!("No RFCs found.");
|
||||
} else {
|
||||
for r in rfcs {
|
||||
let number = r.get("number").and_then(|v| v.as_i64()).unwrap_or(0);
|
||||
let title = r.get("title").and_then(|v| v.as_str()).unwrap_or("?");
|
||||
let status = r.get("status").and_then(|v| v.as_str()).unwrap_or("?");
|
||||
println!(" {:04} {} [{}]", number, title, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
RfcCommands::Get { title } => {
|
||||
let args = json!({ "title": title });
|
||||
match blue_mcp::handlers::rfc::handle_get(&state, &args) {
|
||||
Ok(result) => {
|
||||
println!("{}", serde_json::to_string_pretty(&result)?);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
RfcCommands::Status { title, set } => {
|
||||
let args = json!({ "title": title, "status": set });
|
||||
match blue_mcp::handlers::rfc::handle_update_status(&state, &args) {
|
||||
Ok(result) => {
|
||||
if let Some(msg) = result.get("message").and_then(|v| v.as_str()) {
|
||||
println!("{}", msg);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
RfcCommands::Plan { title, task } => {
|
||||
let args = json!({ "title": title, "tasks": task });
|
||||
match blue_mcp::handlers::rfc::handle_plan(&state, &args) {
|
||||
Ok(result) => {
|
||||
if let Some(msg) = result.get("message").and_then(|v| v.as_str()) {
|
||||
println!("{}", msg);
|
||||
}
|
||||
if let Some(file) = result.get("plan_file").and_then(|v| v.as_str()) {
|
||||
println!("Plan file: {}", file);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
RfcCommands::Complete { title } => {
|
||||
let args = json!({ "title": title });
|
||||
match blue_mcp::handlers::rfc::handle_complete(&state, &args) {
|
||||
Ok(result) => {
|
||||
if let Some(msg) = result.get("message").and_then(|v| v.as_str()) {
|
||||
println!("{}", msg);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle local worktree subcommands (RFC 0061)
|
||||
async fn handle_local_worktree_command(command: WorktreeCommands) -> Result<()> {
|
||||
let state = get_project_state()?;
|
||||
|
||||
match command {
|
||||
WorktreeCommands::Create { title } => {
|
||||
let args = json!({ "rfc_title": title });
|
||||
match blue_mcp::handlers::worktree::handle_create(&state, &args) {
|
||||
Ok(result) => {
|
||||
if let Some(msg) = result.get("message").and_then(|v| v.as_str()) {
|
||||
println!("{}", msg);
|
||||
}
|
||||
if let Some(path) = result.get("worktree_path").and_then(|v| v.as_str()) {
|
||||
println!("Worktree: {}", path);
|
||||
}
|
||||
if let Some(branch) = result.get("branch").and_then(|v| v.as_str()) {
|
||||
println!("Branch: {}", branch);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
WorktreeCommands::List => {
|
||||
match blue_mcp::handlers::worktree::handle_list(&state) {
|
||||
Ok(result) => {
|
||||
if let Some(worktrees) = result.get("worktrees").and_then(|v| v.as_array()) {
|
||||
if worktrees.is_empty() {
|
||||
println!("No worktrees found.");
|
||||
} else {
|
||||
for w in worktrees {
|
||||
let name = w.get("name").and_then(|v| v.as_str()).unwrap_or("?");
|
||||
let branch = w.get("branch").and_then(|v| v.as_str()).unwrap_or("?");
|
||||
let path = w.get("path").and_then(|v| v.as_str()).unwrap_or("?");
|
||||
println!(" {} ({}) -> {}", name, branch, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
WorktreeCommands::Remove { title } => {
|
||||
let args = json!({ "name": title });
|
||||
match blue_mcp::handlers::worktree::handle_remove(&state, &args) {
|
||||
Ok(result) => {
|
||||
if let Some(msg) = result.get("message").and_then(|v| v.as_str()) {
|
||||
println!("{}", msg);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue