pub mod check;
pub mod init;
pub mod issue;
pub mod milestone;
pub mod pr;
pub mod sat;
use crate::daemon::client::DaemonClient;
use crate::daemon::protocol::Method;
pub async fn status(client: &mut DaemonClient) -> anyhow::Result<()> {
let response = client.call(Method::Status).await?;
if let Some(err) = DaemonClient::extract_error(&response) {
anyhow::bail!("{err}");
}
let result = DaemonClient::extract_result(&response)
.ok_or_else(|| anyhow::anyhow!("no result in response"))?;
let counts = result
.get("counts")
.ok_or_else(|| anyhow::anyhow!("no counts"))?;
let total = result.get("total").and_then(|v| v.as_u64()).unwrap_or(0);
println!("Daemon Status");
println!("=============");
println!(
"Running: {}",
counts.get("running").and_then(|v| v.as_u64()).unwrap_or(0)
);
println!(
"Pending: {}",
counts.get("pending").and_then(|v| v.as_u64()).unwrap_or(0)
);
println!(
"Completed: {}",
counts
.get("completed")
.and_then(|v| v.as_u64())
.unwrap_or(0)
);
println!(
"Failed: {}",
counts.get("failed").and_then(|v| v.as_u64()).unwrap_or(0)
);
println!(
"Cancelled: {}",
counts
.get("cancelled")
.and_then(|v| v.as_u64())
.unwrap_or(0)
);
println!(
"Awaiting: {}",
counts
.get("awaiting_approval")
.and_then(|v| v.as_u64())
.unwrap_or(0)
);
println!("Total: {total}");
Ok(())
}
pub async fn run_list(
client: &mut DaemonClient,
status_filter: Option<&str>,
milestone: Option<&str>,
) -> anyhow::Result<()> {
let mut params = serde_json::Map::new();
if let Some(s) = status_filter {
params.insert("status".into(), serde_json::Value::String(s.into()));
}
if let Some(m) = milestone {
params.insert("milestone".into(), serde_json::Value::String(m.into()));
}
let response = client
.send(Method::RunList, serde_json::Value::Object(params))
.await?;
if let Some(err) = DaemonClient::extract_error(&response) {
anyhow::bail!("{err}");
}
let result =
DaemonClient::extract_result(&response).ok_or_else(|| anyhow::anyhow!("no result"))?;
let runs = result.get("runs").and_then(|v| v.as_array());
match runs {
Some(arr) if !arr.is_empty() => {
println!(
"{:<36} {:<18} {:<20} {:<24}",
"ID", "Status", "Milestone", "Updated"
);
println!("{}", "-".repeat(100));
for run in arr {
let id = run.get("id").and_then(|v| v.as_str()).unwrap_or("?");
let status = run.get("status").and_then(|v| v.as_str()).unwrap_or("?");
let milestone = run.get("milestone").and_then(|v| v.as_str()).unwrap_or("-");
let updated = run
.get("updated_at")
.and_then(|v| v.as_str())
.unwrap_or("-");
println!("{id:<36} {status:<18} {milestone:<20} {updated:<24}");
}
}
_ => {
println!("No runs found.");
}
}
Ok(())
}
pub async fn run_show(client: &mut DaemonClient, id: &str) -> anyhow::Result<()> {
let params = serde_json::json!({ "id": id });
let response = client.send(Method::RunShow, params).await?;
if let Some(err) = DaemonClient::extract_error(&response) {
if err.contains("not found") {
println!("Run not found: {id}");
return Ok(());
}
anyhow::bail!("{err}");
}
let result =
DaemonClient::extract_result(&response).ok_or_else(|| anyhow::anyhow!("no result"))?;
let run = result
.get("run")
.ok_or_else(|| anyhow::anyhow!("no run in result"))?;
if std::env::var("DARQ_JSON").is_ok() {
println!("{}", serde_json::to_string_pretty(run)?);
return Ok(());
}
let id_short = run.get("id").and_then(|v| v.as_str()).unwrap_or("?");
let status = run.get("status").and_then(|v| v.as_str()).unwrap_or("?");
let issue = run
.get("issue_number")
.and_then(|v| v.as_u64())
.unwrap_or(0);
let repo = run.get("repo").and_then(|v| v.as_str()).unwrap_or("?");
let sat_score = run.get("sat_score").and_then(|v| v.as_f64());
println!(
"Run: {} Issue: #{} Repo: {}",
&id_short[..8.min(id_short.len())],
issue,
repo
);
println!("Status: {}", status);
if let Some(score) = sat_score {
println!("SAT Score: {:.1}", score);
}
if let Some(artifacts) = run.get("artifacts").and_then(|v| v.as_array())
&& !artifacts.is_empty()
{
let kinds: Vec<&str> = artifacts
.iter()
.filter_map(|a| a.get("kind").and_then(|v| v.as_str()))
.collect();
println!("Artifacts: {}", kinds.join(", "));
}
if let Some(patterns) = run.get("patterns_used").and_then(|v| v.as_array())
&& !patterns.is_empty()
{
println!("Patterns injected: {}", patterns.len());
}
if let Some(error) = run.get("error_message").and_then(|v| v.as_str()) {
println!();
println!("Error: {}", error);
if error.contains("contract violation") {
println!(" This is a contract violation — a workflow's precondition was not met.");
println!(" Check that the previous workflow completed successfully.");
}
}
if let Some(created) = run.get("created_at").and_then(|v| v.as_str()) {
println!();
println!("Created: {}", &created[..19.min(created.len())]);
}
if let Some(completed) = run.get("completed_at").and_then(|v| v.as_str()) {
println!("Completed: {}", &completed[..19.min(completed.len())]);
}
Ok(())
}
pub async fn run_approve(client: &mut DaemonClient, id: &str) -> anyhow::Result<()> {
let params = serde_json::json!({ "id": id });
let response = client.send(Method::RunApprove, params).await?;
if let Some(err) = DaemonClient::extract_error(&response) {
anyhow::bail!("{err}");
}
let result =
DaemonClient::extract_result(&response).ok_or_else(|| anyhow::anyhow!("no result"))?;
let run = result.get("run").ok_or_else(|| anyhow::anyhow!("no run"))?;
let run_id = run.get("id").and_then(|v| v.as_str()).unwrap_or(id);
let run_status = run.get("status").and_then(|v| v.as_str()).unwrap_or("?");
println!("Approved run {run_id} — status: {run_status}");
Ok(())
}
pub async fn run_cancel(
client: &mut DaemonClient,
id: &str,
reason: Option<String>,
) -> anyhow::Result<()> {
let mut params = serde_json::json!({ "id": id });
if let Some(r) = reason {
params["reason"] = serde_json::Value::String(r);
}
let response = client.send(Method::RunCancel, params).await?;
if let Some(err) = DaemonClient::extract_error(&response) {
anyhow::bail!("{err}");
}
let result =
DaemonClient::extract_result(&response).ok_or_else(|| anyhow::anyhow!("no result"))?;
let run = result.get("run").ok_or_else(|| anyhow::anyhow!("no run"))?;
let run_id = run.get("id").and_then(|v| v.as_str()).unwrap_or(id);
let run_status = run.get("status").and_then(|v| v.as_str()).unwrap_or("?");
println!("Cancelled run {run_id} — status: {run_status}");
Ok(())
}
pub async fn run_workflow_chain(
client: &mut DaemonClient,
start: &str,
issue: u64,
max_steps: u32,
pause: bool,
) -> anyhow::Result<()> {
let params = serde_json::json!({
"start": start,
"issue": issue,
"max_steps": max_steps,
"pause_for_approval": pause,
});
let response = client.send(Method::WorkflowChain, params).await?;
if let Some(err) = DaemonClient::extract_error(&response) {
anyhow::bail!("{err}");
}
println!("Workflow queued. Waiting for completion...");
let mut events = DaemonClient::subscribe_events().await?;
let mut run_id: Option<String> = None;
while let Some(event) = events.recv().await {
if event.event != "run_event" {
continue;
}
let data = &event.data;
let event_type = data.get("type").and_then(|v| v.as_str()).unwrap_or("");
let ev_run_id = data.get("run_id").and_then(|v| v.as_str()).unwrap_or("");
if run_id.is_none() && event_type == "status_changed" {
let to = data.get("to").and_then(|v| v.as_str()).unwrap_or("");
if to == "running" {
run_id = Some(ev_run_id.to_string());
println!("Run started: {ev_run_id}");
}
}
if let Some(ref rid) = run_id {
if ev_run_id != *rid {
continue;
}
} else {
continue;
}
match event_type {
"status_changed" => {
let from = data.get("from").and_then(|v| v.as_str()).unwrap_or("?");
let to = data.get("to").and_then(|v| v.as_str()).unwrap_or("?");
println!(" [{from}] → [{to}]");
}
"artifact_added" => {
let kind = data.get("kind").and_then(|v| v.as_str()).unwrap_or("?");
println!(" artifact: {kind}");
}
"approval_recorded" => {
println!(" approved");
}
_ => {}
}
if event_type == "status_changed" {
let to = data.get("to").and_then(|v| v.as_str()).unwrap_or("");
if to == "completed" || to == "failed" || to == "cancelled" {
println!("\nWorkflow {to}.");
break;
}
if to == "awaiting_approval" {
println!("\nPaused for approval.");
println!("Run: {ev_run_id}");
println!("Approve: darq run approve {ev_run_id}");
break;
}
}
}
Ok(())
}
pub async fn stats(client: &mut DaemonClient) -> anyhow::Result<()> {
let response = client.call(Method::Stats).await?;
if let Some(err) = DaemonClient::extract_error(&response) {
anyhow::bail!("{err}");
}
let result = DaemonClient::extract_result(&response)
.ok_or_else(|| anyhow::anyhow!("no result in response"))?;
let total_runs = result
.get("total_runs")
.and_then(|v| v.as_u64())
.unwrap_or(0);
let completed = result
.get("completed")
.and_then(|v| v.as_u64())
.unwrap_or(0);
let failed = result.get("failed").and_then(|v| v.as_u64()).unwrap_or(0);
let first_pass_rate = result
.get("first_pass_rate")
.and_then(|v| v.as_f64())
.unwrap_or(0.0);
let avg_sat = result
.get("avg_sat_score")
.and_then(|v| v.as_f64())
.unwrap_or(0.0);
println!("Learning Analytics");
println!("==================");
println!("Total runs: {total_runs}");
println!("Completed: {completed}");
println!("Failed: {failed}");
println!("First-pass rate: {:.1}%", first_pass_rate * 100.0);
println!("Avg SAT score: {:.1}%", avg_sat * 100.0);
if let Some(patterns) = result
.get("pattern_effectiveness")
.and_then(|v| v.as_array())
&& !patterns.is_empty()
{
println!();
println!("Pattern Effectiveness");
println!("---------------------");
println!(
"{:<30} {:>10} {:>10} {:>8}",
"Pattern", "Success", "Total", "Rate"
);
println!("{}", "-".repeat(62));
for p in patterns {
let pid = p.get("pattern_id").and_then(|v| v.as_str()).unwrap_or("?");
let ok = p.get("successes").and_then(|v| v.as_u64()).unwrap_or(0);
let total = p.get("total").and_then(|v| v.as_u64()).unwrap_or(0);
let rate = p.get("rate").and_then(|v| v.as_f64()).unwrap_or(0.0);
println!("{pid:<30} {ok:>10} {total:>10} {rate:>7.1}%");
}
}
Ok(())
}