From 88a4f15ce1310f8ee8693835be14aa5280233f17 Mon Sep 17 00:00:00 2001 From: soryu Date: Thu, 5 Feb 2026 23:42:48 +0000 Subject: 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 --- makima/src/bin/makima.rs | 151 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 1 deletion(-) (limited to 'makima/src/bin/makima.rs') 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> { 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> { + 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> = + 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, Box> { let result = client.list_contracts().await?; -- cgit v1.2.3