summaryrefslogtreecommitdiff
path: root/makima/src/bin/makima.rs
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-05 23:42:48 +0000
committersoryu <soryu@soryu.co>2026-02-05 23:42:48 +0000
commit88a4f15ce1310f8ee8693835be14aa5280233f17 (patch)
tree5c1a0417e02071d2198d13478ffa85533b19f891 /makima/src/bin/makima.rs
parentf1a50b80f3969d150bd1c31edde0aff05369157e (diff)
downloadsoryu-88a4f15ce1310f8ee8693835be14aa5280233f17.tar.gz
soryu-88a4f15ce1310f8ee8693835be14aa5280233f17.zip
Add directive-first chain system redesign
Redesigns the chain system with a directive-first architecture where Directive is the top-level entity (the "why/what") and Chains are generated execution plans (the "how") that can be dynamically modified. Backend: - Add database migration for directive system tables - Add Directive, DirectiveChain, ChainStep, DirectiveEvent models - Add DirectiveVerifier and DirectiveApproval models - Add orchestration module with engine, planner, and verifier - Add comprehensive API handlers for directives - Add daemon CLI commands for directive management - Add directive skill documentation - Integrate contract completion with directive engine - Add SSE endpoint for real-time directive events Frontend: - Add directives route with split-view layout - Add 6-tab detail view (Overview, Chain, Events, Evaluations, Approvals, Verifiers) - Add React Flow DAG visualization for chain steps - Add SSE subscription hook for real-time event updates - Add useDirectives and useDirectiveEventSubscription hooks - Add directive types and API functions Fixes: - Fix test failures in ws/protocol, task_output, completion_gate, patch - Fix word boundary matching in looks_like_task() - Fix parse_last() to find actual last completion gate - Fix create_export_patch when merge-base equals HEAD - Clean up clippy warnings in new code Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'makima/src/bin/makima.rs')
-rw-r--r--makima/src/bin/makima.rs151
1 files changed, 150 insertions, 1 deletions
diff --git a/makima/src/bin/makima.rs b/makima/src/bin/makima.rs
index f9c981f..822b21f 100644
--- a/makima/src/bin/makima.rs
+++ b/makima/src/bin/makima.rs
@@ -7,7 +7,7 @@ use std::sync::Arc;
use makima::daemon::api::{ApiClient, CreateContractRequest};
use makima::daemon::cli::{
Cli, CliConfig, Commands, ConfigCommand, ContractCommand, ChainCommand,
- SupervisorCommand, ViewArgs,
+ DirectiveCommand, SupervisorCommand, ViewArgs,
};
use makima::daemon::tui::{self, Action, App, ListItem, ViewType, TuiWsClient, WsEvent, OutputLine, OutputMessageType, WsConnectionState, RepositorySuggestion};
use makima::daemon::config::{DaemonConfig, RepoEntry};
@@ -32,6 +32,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Commands::View(args) => run_view(args).await,
Commands::Config(cmd) => run_config(cmd).await,
Commands::Chain(cmd) => run_chain(cmd).await,
+ Commands::Directive(cmd) => run_directive(cmd).await,
}
}
@@ -1021,6 +1022,154 @@ async fn run_chain(
Ok(())
}
+/// Run directive commands.
+async fn run_directive(
+ cmd: DirectiveCommand,
+) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
+ match cmd {
+ DirectiveCommand::Create(args) => {
+ let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
+ let result = client
+ .create_directive(&args.goal, args.repository.as_deref(), &args.autonomy)
+ .await?;
+ println!("{}", serde_json::to_string(&result.0)?);
+ }
+ DirectiveCommand::Status(args) => {
+ let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
+ let result = client.get_directive(args.directive_id).await?;
+ println!("{}", serde_json::to_string(&result.0)?);
+ }
+ DirectiveCommand::List(args) => {
+ let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
+ let result = client
+ .list_directives(args.status.as_deref(), args.limit)
+ .await?;
+ println!("{}", serde_json::to_string(&result.0)?);
+ }
+ DirectiveCommand::Steps(args) => {
+ let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
+ let result = client.get_directive_chain(args.directive_id).await?;
+ println!("{}", serde_json::to_string(&result.0)?);
+ }
+ DirectiveCommand::Graph(args) => {
+ let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
+ let result = client.get_directive_graph(args.directive_id).await?;
+
+ if args.with_status {
+ // Enhanced ASCII visualization with status
+ if let Some(nodes) = result.0.get("nodes").and_then(|v| v.as_array()) {
+ let mut by_depth: std::collections::HashMap<i32, Vec<(&str, &str)>> =
+ std::collections::HashMap::new();
+
+ for node in nodes {
+ let name = node.get("name").and_then(|v| v.as_str()).unwrap_or("?");
+ let status = node
+ .get("status")
+ .and_then(|v| v.as_str())
+ .unwrap_or("pending");
+ let depth = node.get("depth").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
+ by_depth.entry(depth).or_default().push((name, status));
+ }
+
+ let directive_name = result
+ .0
+ .get("name")
+ .and_then(|v| v.as_str())
+ .unwrap_or("Directive");
+ println!("Directive: {}", directive_name);
+ println!();
+
+ let max_depth = by_depth.keys().max().copied().unwrap_or(0);
+ for depth in 0..=max_depth {
+ if let Some(steps) = by_depth.get(&depth) {
+ let indent = " ".repeat(depth as usize);
+ for (name, status) in steps {
+ let status_icon = match *status {
+ "passed" | "completed" => "\u{2713}",
+ "running" | "evaluating" => "\u{21bb}",
+ "failed" | "blocked" => "\u{2717}",
+ "rework" => "\u{21ba}",
+ "skipped" => "\u{2212}",
+ "ready" => "\u{25b7}",
+ _ => "\u{25cb}",
+ };
+ println!("{}[{}] {} {}", indent, name, status_icon, status);
+ }
+ if depth < max_depth {
+ println!("{} |", indent);
+ println!("{} v", indent);
+ }
+ }
+ }
+ }
+ } else {
+ println!("{}", serde_json::to_string_pretty(&result.0)?);
+ }
+ }
+ DirectiveCommand::Events(args) => {
+ let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
+ let result = client
+ .list_directive_events(args.directive_id, args.limit)
+ .await?;
+ println!("{}", serde_json::to_string(&result.0)?);
+ }
+ DirectiveCommand::Approve(args) => {
+ let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
+ let result = client
+ .approve_directive_request(
+ args.directive_id,
+ args.approval_id,
+ args.response.as_deref(),
+ )
+ .await?;
+ println!("{}", serde_json::to_string(&result.0)?);
+ }
+ DirectiveCommand::Deny(args) => {
+ let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
+ let result = client
+ .deny_directive_request(
+ args.directive_id,
+ args.approval_id,
+ args.reason.as_deref(),
+ )
+ .await?;
+ println!("{}", serde_json::to_string(&result.0)?);
+ }
+ DirectiveCommand::Start(args) => {
+ let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
+ eprintln!("Starting directive {}...", args.directive_id);
+ let result = client.start_directive(args.directive_id).await?;
+ println!("{}", serde_json::to_string(&result.0)?);
+ }
+ DirectiveCommand::Pause(args) => {
+ let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
+ eprintln!("Pausing directive {}...", args.directive_id);
+ let result = client.pause_directive(args.directive_id).await?;
+ println!("{}", serde_json::to_string(&result.0)?);
+ }
+ DirectiveCommand::Resume(args) => {
+ let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
+ eprintln!("Resuming directive {}...", args.directive_id);
+ let result = client.resume_directive(args.directive_id).await?;
+ println!("{}", serde_json::to_string(&result.0)?);
+ }
+ DirectiveCommand::Stop(args) => {
+ let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
+ eprintln!("Stopping directive {}...", args.directive_id);
+ let result = client.stop_directive(args.directive_id).await?;
+ println!("{}", serde_json::to_string(&result.0)?);
+ }
+ DirectiveCommand::Archive(args) => {
+ let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
+ eprintln!("Archiving directive {}...", args.directive_id);
+ let result = client.archive_directive(args.directive_id).await?;
+ println!("{}", serde_json::to_string(&result.0)?);
+ }
+ }
+
+ Ok(())
+}
+
/// Load contracts from API
async fn load_contracts(client: &ApiClient) -> Result<Vec<ListItem>, Box<dyn std::error::Error + Send + Sync>> {
let result = client.list_contracts().await?;