diff options
Diffstat (limited to 'makima/src/daemon')
| -rw-r--r-- | makima/src/daemon/api/directive.rs | 162 | ||||
| -rw-r--r-- | makima/src/daemon/api/mod.rs | 1 | ||||
| -rw-r--r-- | makima/src/daemon/chain/parser.rs | 7 | ||||
| -rw-r--r-- | makima/src/daemon/chain/runner.rs | 12 | ||||
| -rw-r--r-- | makima/src/daemon/cli/directive.rs | 186 | ||||
| -rw-r--r-- | makima/src/daemon/cli/mod.rs | 56 | ||||
| -rw-r--r-- | makima/src/daemon/db/local.rs | 4 | ||||
| -rw-r--r-- | makima/src/daemon/process/claude_protocol.rs | 2 | ||||
| -rw-r--r-- | makima/src/daemon/skills/directive.md | 303 | ||||
| -rw-r--r-- | makima/src/daemon/skills/mod.rs | 6 | ||||
| -rw-r--r-- | makima/src/daemon/storage/patch.rs | 25 | ||||
| -rw-r--r-- | makima/src/daemon/task/completion_gate.rs | 21 | ||||
| -rw-r--r-- | makima/src/daemon/task/state.rs | 4 | ||||
| -rw-r--r-- | makima/src/daemon/temp.rs | 2 | ||||
| -rw-r--r-- | makima/src/daemon/worktree/manager.rs | 3 | ||||
| -rw-r--r-- | makima/src/daemon/ws/protocol.rs | 6 |
16 files changed, 771 insertions, 29 deletions
diff --git a/makima/src/daemon/api/directive.rs b/makima/src/daemon/api/directive.rs new file mode 100644 index 0000000..5281d21 --- /dev/null +++ b/makima/src/daemon/api/directive.rs @@ -0,0 +1,162 @@ +//! Directive API methods. + +use uuid::Uuid; + +use super::client::{ApiClient, ApiError}; +use super::supervisor::JsonValue; + +impl ApiClient { + /// Create a new directive. + pub async fn create_directive( + &self, + goal: &str, + repository_url: Option<&str>, + autonomy_level: &str, + ) -> Result<JsonValue, ApiError> { + #[derive(serde::Serialize)] + #[serde(rename_all = "camelCase")] + struct CreateRequest<'a> { + goal: &'a str, + repository_url: Option<&'a str>, + autonomy_level: &'a str, + } + let req = CreateRequest { + goal, + repository_url, + autonomy_level, + }; + self.post("/api/v1/directives", &req).await + } + + /// List all directives for the authenticated user. + pub async fn list_directives( + &self, + status: Option<&str>, + limit: i32, + ) -> Result<JsonValue, ApiError> { + let mut params = Vec::new(); + if let Some(s) = status { + params.push(format!("status={}", s)); + } + params.push(format!("limit={}", limit)); + let query_string = format!("?{}", params.join("&")); + self.get(&format!("/api/v1/directives{}", query_string)) + .await + } + + /// Get a directive by ID (includes progress info). + pub async fn get_directive(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> { + self.get(&format!("/api/v1/directives/{}", directive_id)) + .await + } + + /// Archive a directive. + pub async fn archive_directive(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> { + self.delete_with_response(&format!("/api/v1/directives/{}", directive_id)) + .await + } + + /// Start a directive (plans and begins execution). + pub async fn start_directive(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> { + self.post_empty(&format!("/api/v1/directives/{}/start", directive_id)) + .await + } + + /// Pause a directive. + pub async fn pause_directive(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> { + self.post_empty(&format!("/api/v1/directives/{}/pause", directive_id)) + .await + } + + /// Resume a paused directive. + pub async fn resume_directive(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> { + self.post_empty(&format!("/api/v1/directives/{}/resume", directive_id)) + .await + } + + /// Stop a directive. + pub async fn stop_directive(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> { + self.post_empty(&format!("/api/v1/directives/{}/stop", directive_id)) + .await + } + + /// Get the current chain and steps for a directive. + pub async fn get_directive_chain(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> { + self.get(&format!("/api/v1/directives/{}/chain", directive_id)) + .await + } + + /// Get directive DAG structure for visualization. + pub async fn get_directive_graph(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> { + self.get(&format!("/api/v1/directives/{}/chain/graph", directive_id)) + .await + } + + /// List events for a directive. + pub async fn list_directive_events( + &self, + directive_id: Uuid, + limit: i32, + ) -> Result<JsonValue, ApiError> { + self.get(&format!( + "/api/v1/directives/{}/events?limit={}", + directive_id, limit + )) + .await + } + + /// List pending approvals for a directive. + pub async fn list_directive_approvals( + &self, + directive_id: Uuid, + ) -> Result<JsonValue, ApiError> { + self.get(&format!("/api/v1/directives/{}/approvals", directive_id)) + .await + } + + /// Approve an approval request. + pub async fn approve_directive_request( + &self, + directive_id: Uuid, + approval_id: Uuid, + response: Option<&str>, + ) -> Result<JsonValue, ApiError> { + #[derive(serde::Serialize)] + #[serde(rename_all = "camelCase")] + struct ApprovalRequest<'a> { + response: Option<&'a str>, + } + let req = ApprovalRequest { response }; + self.post( + &format!( + "/api/v1/directives/{}/approvals/{}/approve", + directive_id, approval_id + ), + &req, + ) + .await + } + + /// Deny an approval request. + pub async fn deny_directive_request( + &self, + directive_id: Uuid, + approval_id: Uuid, + response: Option<&str>, + ) -> Result<JsonValue, ApiError> { + #[derive(serde::Serialize)] + #[serde(rename_all = "camelCase")] + struct ApprovalRequest<'a> { + response: Option<&'a str>, + } + let req = ApprovalRequest { response }; + self.post( + &format!( + "/api/v1/directives/{}/approvals/{}/deny", + directive_id, approval_id + ), + &req, + ) + .await + } +} diff --git a/makima/src/daemon/api/mod.rs b/makima/src/daemon/api/mod.rs index 7868907..f1f52d0 100644 --- a/makima/src/daemon/api/mod.rs +++ b/makima/src/daemon/api/mod.rs @@ -3,6 +3,7 @@ pub mod chain; pub mod client; pub mod contract; +pub mod directive; pub mod supervisor; pub use client::ApiClient; diff --git a/makima/src/daemon/chain/parser.rs b/makima/src/daemon/chain/parser.rs index 3851d1f..b32d0f2 100644 --- a/makima/src/daemon/chain/parser.rs +++ b/makima/src/daemon/chain/parser.rs @@ -395,7 +395,9 @@ contracts: fn test_repo_alias() { let yaml = r#" name: Repo Chain -repo: https://github.com/user/project +repositories: + - name: main + repository_url: https://github.com/user/project contracts: - name: Phase1 tasks: @@ -403,8 +405,9 @@ contracts: plan: "Work on repo" "#; let chain = parse_chain_yaml(yaml).unwrap(); + assert_eq!(chain.repositories.len(), 1); assert_eq!( - chain.repository_url, + chain.repositories[0].repository_url, Some("https://github.com/user/project".to_string()) ); } diff --git a/makima/src/daemon/chain/runner.rs b/makima/src/daemon/chain/runner.rs index dfbcfa7..1814581 100644 --- a/makima/src/daemon/chain/runner.rs +++ b/makima/src/daemon/chain/runner.rs @@ -37,8 +37,10 @@ pub enum RunnerError { /// Chain runner for creating and managing chains. pub struct ChainRunner { /// Base API URL + #[allow(dead_code)] api_url: String, /// API key for authentication + #[allow(dead_code)] api_key: String, } @@ -116,6 +118,7 @@ impl ChainRunner { CreateChainRequest { name: chain.name.clone(), description: chain.description.clone(), + repository_url: None, // Legacy field, repositories take precedence repositories: if repositories.is_empty() { None } else { @@ -242,7 +245,9 @@ mod tests { let yaml = r#" name: Test Chain description: A test chain -repo: https://github.com/test/repo +repositories: + - name: main + repository_url: https://github.com/test/repo contracts: - name: Research type: simple @@ -270,8 +275,11 @@ loop: assert_eq!(request.name, "Test Chain"); assert_eq!(request.description, Some("A test chain".to_string())); + // Repositories are now in a separate array + let repos = request.repositories.unwrap(); + assert_eq!(repos.len(), 1); assert_eq!( - request.repository_url, + repos[0].repository_url, Some("https://github.com/test/repo".to_string()) ); assert_eq!(request.loop_enabled, Some(true)); diff --git a/makima/src/daemon/cli/directive.rs b/makima/src/daemon/cli/directive.rs new file mode 100644 index 0000000..a2bb34b --- /dev/null +++ b/makima/src/daemon/cli/directive.rs @@ -0,0 +1,186 @@ +//! Directive CLI commands for autonomous goal-driven orchestration. +//! +//! Directives are top-level goals that the system works toward. Each directive +//! generates a chain of steps that are executed autonomously with configurable +//! guardrails. + +use clap::Args; +use uuid::Uuid; + +/// Common arguments for directive commands requiring API access. +#[derive(Args, Debug, Clone)] +pub struct DirectiveArgs { + /// API URL + #[arg(long, env = "MAKIMA_API_URL", default_value = "https://api.makima.jp", global = true)] + pub api_url: String, + + /// API key for authentication + #[arg(long, env = "MAKIMA_API_KEY", global = true)] + pub api_key: String, +} + +/// Arguments for the `create` command. +#[derive(Args, Debug)] +pub struct CreateArgs { + #[command(flatten)] + pub common: DirectiveArgs, + + /// The goal for the directive + #[arg(short, long)] + pub goal: String, + + /// Repository URL (optional) + #[arg(short, long)] + pub repository: Option<String>, + + /// Autonomy level: full_auto, guardrails, or manual + #[arg(short, long, default_value = "guardrails")] + pub autonomy: String, +} + +/// Arguments for the `status` command. +#[derive(Args, Debug)] +pub struct StatusArgs { + #[command(flatten)] + pub common: DirectiveArgs, + + /// Directive ID + pub directive_id: Uuid, +} + +/// Arguments for the `list` command. +#[derive(Args, Debug)] +pub struct ListArgs { + #[command(flatten)] + pub common: DirectiveArgs, + + /// Filter by status (draft, planning, active, paused, completed, archived, failed) + #[arg(long)] + pub status: Option<String>, + + /// Limit number of results + #[arg(long, default_value = "50")] + pub limit: i32, +} + +/// Arguments for the `steps` command. +#[derive(Args, Debug)] +pub struct StepsArgs { + #[command(flatten)] + pub common: DirectiveArgs, + + /// Directive ID + pub directive_id: Uuid, +} + +/// Arguments for the `events` command. +#[derive(Args, Debug)] +pub struct EventsArgs { + #[command(flatten)] + pub common: DirectiveArgs, + + /// Directive ID + pub directive_id: Uuid, + + /// Limit number of events + #[arg(long, default_value = "50")] + pub limit: i32, +} + +/// Arguments for the `approve` command. +#[derive(Args, Debug)] +pub struct ApproveArgs { + #[command(flatten)] + pub common: DirectiveArgs, + + /// Directive ID + pub directive_id: Uuid, + + /// Approval ID + pub approval_id: Uuid, + + /// Response message (optional) + #[arg(short, long)] + pub response: Option<String>, +} + +/// Arguments for the `deny` command. +#[derive(Args, Debug)] +pub struct DenyArgs { + #[command(flatten)] + pub common: DirectiveArgs, + + /// Directive ID + pub directive_id: Uuid, + + /// Approval ID + pub approval_id: Uuid, + + /// Reason for denial (optional) + #[arg(short, long)] + pub reason: Option<String>, +} + +/// Arguments for the `start` command. +#[derive(Args, Debug)] +pub struct StartArgs { + #[command(flatten)] + pub common: DirectiveArgs, + + /// Directive ID + pub directive_id: Uuid, +} + +/// Arguments for the `pause` command. +#[derive(Args, Debug)] +pub struct PauseArgs { + #[command(flatten)] + pub common: DirectiveArgs, + + /// Directive ID + pub directive_id: Uuid, +} + +/// Arguments for the `resume` command. +#[derive(Args, Debug)] +pub struct ResumeArgs { + #[command(flatten)] + pub common: DirectiveArgs, + + /// Directive ID + pub directive_id: Uuid, +} + +/// Arguments for the `stop` command. +#[derive(Args, Debug)] +pub struct StopArgs { + #[command(flatten)] + pub common: DirectiveArgs, + + /// Directive ID + pub directive_id: Uuid, +} + +/// Arguments for the `archive` command. +#[derive(Args, Debug)] +pub struct ArchiveArgs { + #[command(flatten)] + pub common: DirectiveArgs, + + /// Directive ID + pub directive_id: Uuid, +} + +/// Arguments for the `graph` command (ASCII DAG visualization). +#[derive(Args, Debug)] +pub struct GraphArgs { + #[command(flatten)] + pub common: DirectiveArgs, + + /// Directive ID + pub directive_id: Uuid, + + /// Show step status in nodes + #[arg(long)] + pub with_status: bool, +} diff --git a/makima/src/daemon/cli/mod.rs b/makima/src/daemon/cli/mod.rs index 035a784..91ef87c 100644 --- a/makima/src/daemon/cli/mod.rs +++ b/makima/src/daemon/cli/mod.rs @@ -4,6 +4,7 @@ pub mod chain; pub mod config; pub mod contract; pub mod daemon; +pub mod directive; pub mod server; pub mod supervisor; pub mod view; @@ -14,6 +15,7 @@ pub use chain::ChainArgs; pub use config::CliConfig; pub use contract::ContractArgs; pub use daemon::DaemonArgs; +pub use directive::DirectiveArgs; pub use server::ServerArgs; pub use supervisor::SupervisorArgs; pub use view::ViewArgs; @@ -68,6 +70,14 @@ pub enum Commands { /// in parallel when no dependencies exist. #[command(subcommand)] Chain(ChainCommand), + + /// Directive commands for autonomous goal-driven orchestration + /// + /// Directives are top-level goals that generate chains of steps executed + /// autonomously with configurable guardrails. Steps spawn contracts with + /// supervisors and are verified with programmatic and LLM evaluation. + #[command(subcommand)] + Directive(DirectiveCommand), } /// Config subcommands for CLI configuration. @@ -248,6 +258,52 @@ pub enum ChainCommand { Archive(chain::ArchiveArgs), } +/// Directive subcommands for autonomous goal-driven orchestration. +#[derive(Subcommand, Debug)] +pub enum DirectiveCommand { + /// Create a new directive from a goal + Create(directive::CreateArgs), + + /// Get directive status and progress + Status(directive::StatusArgs), + + /// List all directives + List(directive::ListArgs), + + /// List steps in the directive's chain + Steps(directive::StepsArgs), + + /// Display ASCII DAG visualization + /// + /// Shows the directive's chain structure as an ASCII graph with + /// steps as nodes and dependencies as edges. + Graph(directive::GraphArgs), + + /// Show recent events for a directive + Events(directive::EventsArgs), + + /// Approve a pending approval request + Approve(directive::ApproveArgs), + + /// Deny a pending approval request + Deny(directive::DenyArgs), + + /// Start a directive (generates chain and begins execution) + Start(directive::StartArgs), + + /// Pause a running directive + Pause(directive::PauseArgs), + + /// Resume a paused directive + Resume(directive::ResumeArgs), + + /// Stop a directive + Stop(directive::StopArgs), + + /// Archive a directive + Archive(directive::ArchiveArgs), +} + impl Cli { /// Parse command-line arguments pub fn parse_args() -> Self { diff --git a/makima/src/daemon/db/local.rs b/makima/src/daemon/db/local.rs index f3ed45a..5b4ca5b 100644 --- a/makima/src/daemon/db/local.rs +++ b/makima/src/daemon/db/local.rs @@ -336,7 +336,9 @@ impl LocalDb { #[cfg(test)] mod tests { - use crate::daemon::*; + use super::*; + use chrono::Utc; + use uuid::Uuid; #[test] fn test_open_memory() { diff --git a/makima/src/daemon/process/claude_protocol.rs b/makima/src/daemon/process/claude_protocol.rs index 96e5377..930152b 100644 --- a/makima/src/daemon/process/claude_protocol.rs +++ b/makima/src/daemon/process/claude_protocol.rs @@ -45,7 +45,7 @@ impl ClaudeInputMessage { #[cfg(test)] mod tests { - use crate::daemon::*; + use super::*; #[test] fn test_user_message_serialization() { diff --git a/makima/src/daemon/skills/directive.md b/makima/src/daemon/skills/directive.md new file mode 100644 index 0000000..97e8e20 --- /dev/null +++ b/makima/src/daemon/skills/directive.md @@ -0,0 +1,303 @@ +--- +name: makima-directive +description: Directive orchestration tools for autonomous goal-driven execution. Use when working with directives, chains, steps, verifiers, and approvals. +--- + +# Directive Orchestration Tools + +Directives are top-level goals that drive autonomous execution with configurable guardrails. Each directive generates a chain of steps that spawn contracts with supervisors, verified by programmatic checks and LLM evaluation. + +## Architecture + +``` +Directive (goal + requirements + acceptance criteria) + | + +-- Chain (generated DAG execution plan) + | +-- Step 1 (pending -> ready -> running -> evaluating -> passed) + | | +-- Contract (spawned when step reaches 'ready') + | | +-- Supervisor Task + | +-- Step 2 (depends_on: [Step 1]) + | +-- Step 3 (depends_on: [Step 1], parallel with Step 2) + | + +-- Verifiers (test runner, linter, build, type checker) + +-- Evaluations (programmatic + LLM composite scores) + +-- Events (audit stream) + +-- Approvals (human-in-the-loop gates) +``` + +## Status Flow + +### Directive Status +- `draft` - Created but not started +- `planning` - Generating chain from requirements +- `active` - Executing steps +- `paused` - Temporarily stopped +- `completed` - All steps passed +- `archived` - No longer active +- `failed` - Execution failed + +### Step Status +- `pending` - Waiting for dependencies +- `ready` - Dependencies met, ready to start +- `running` - Contract executing +- `evaluating` - Running verifiers +- `passed` - Evaluation succeeded +- `failed` - Evaluation failed, exceeded retries +- `rework` - Sent back for corrections +- `skipped` - Manually skipped +- `blocked` - Blocked by failed dependency + +## Autonomy Levels + +- `full_auto` - No approval gates, automatic progression +- `guardrails` - Request approval for yellow/red confidence scores +- `manual` - Request approval for all step completions + +## Confidence Scoring + +Each step evaluation produces a composite confidence score: + +1. **Programmatic verifiers** run first (tests, lint, build) + - Weight: 1.0 each + - If any required verifier fails: automatic RED + +2. **LLM evaluation** runs second + - Weight: 2.0 + - Evaluates against acceptance criteria + +3. **Composite score** computed from weighted average + - GREEN: >= configured threshold (default 0.8) + - YELLOW: >= yellow threshold (default 0.5) + - RED: below yellow threshold + +## CLI Commands + +```bash +# Create a new directive +makima directive create --goal "Add OAuth2 authentication" --repository https://github.com/org/repo + +# List directives +makima directive list [--status active] + +# Get directive status with progress +makima directive status <directive-id> + +# Start execution (generates chain and begins) +makima directive start <directive-id> + +# View chain steps +makima directive steps <directive-id> + +# View DAG visualization +makima directive graph <directive-id> --with-status + +# View recent events +makima directive events <directive-id> --limit 20 + +# Approve a pending request +makima directive approve <directive-id> <approval-id> [--response "Looks good"] + +# Deny a pending request +makima directive deny <directive-id> <approval-id> [--reason "Need more testing"] + +# Lifecycle commands +makima directive pause <directive-id> +makima directive resume <directive-id> +makima directive stop <directive-id> +makima directive archive <directive-id> +``` + +## API Endpoints + +### Directive CRUD +``` +POST /api/v1/directives # Create from goal +GET /api/v1/directives # List +GET /api/v1/directives/:id # Get with progress +PUT /api/v1/directives/:id # Update +DELETE /api/v1/directives/:id # Archive +``` + +### Lifecycle +``` +POST /api/v1/directives/:id/start # Plan + execute +POST /api/v1/directives/:id/pause # Pause +POST /api/v1/directives/:id/resume # Resume +POST /api/v1/directives/:id/stop # Stop +``` + +### Chain & Steps +``` +GET /api/v1/directives/:id/chain # Current chain + steps +GET /api/v1/directives/:id/chain/graph # DAG for visualization +POST /api/v1/directives/:id/chain/replan # Force regeneration +POST /api/v1/directives/:id/chain/steps # Add step +PUT /api/v1/directives/:id/chain/steps/:sid # Modify step +DELETE /api/v1/directives/:id/chain/steps/:sid # Remove step +``` + +### Step Operations +``` +GET /api/v1/directives/:id/steps/:sid # Step detail +POST /api/v1/directives/:id/steps/:sid/evaluate # Force re-evaluation +POST /api/v1/directives/:id/steps/:sid/skip # Skip step +POST /api/v1/directives/:id/steps/:sid/rework # Manual rework +``` + +### Monitoring +``` +GET /api/v1/directives/:id/evaluations # List evaluations +GET /api/v1/directives/:id/events # Event log (polling) +GET /api/v1/directives/:id/events/stream # Event stream (SSE) +``` + +### Verifiers +``` +GET /api/v1/directives/:id/verifiers # List verifiers +POST /api/v1/directives/:id/verifiers # Add verifier +PUT /api/v1/directives/:id/verifiers/:vid # Update verifier +POST /api/v1/directives/:id/verifiers/auto-detect # Auto-detect +``` + +### Approvals +``` +GET /api/v1/directives/:id/approvals # Pending approvals +POST /api/v1/directives/:id/approvals/:aid/approve # Approve +POST /api/v1/directives/:id/approvals/:aid/deny # Deny +``` + +## Creating a Directive + +### Request +```json +POST /api/v1/directives +{ + "goal": "Implement user authentication with OAuth2", + "repositoryUrl": "https://github.com/org/repo", + "autonomyLevel": "guardrails", + "confidenceThresholdGreen": 0.8, + "confidenceThresholdYellow": 0.5, + "maxReworkCycles": 3, + "maxTotalCostUsd": 100.0, + "maxWallTimeMinutes": 480 +} +``` + +### Response +```json +{ + "id": "uuid", + "title": "Implement user authentication with OAuth2", + "goal": "Implement user authentication with OAuth2", + "status": "draft", + "autonomyLevel": "guardrails", + "createdAt": "2026-02-05T12:00:00Z" +} +``` + +## Starting a Directive + +When you start a directive: +1. System generates requirements from the goal +2. Chain planner creates a DAG of steps +3. Root steps (no dependencies) transition to `ready` +4. Contracts spawn for ready steps with supervisors +5. Verifiers auto-detect from repository + +## Evaluation Flow + +When a contract completes: + +1. Step transitions to `evaluating` +2. **Programmatic verifiers** run (tests, lint, build) + - Each produces pass/fail + output +3. **LLM evaluation** runs + - Reviews code against acceptance criteria + - Provides feedback and score +4. **Composite score** computed +5. Based on confidence level and autonomy: + - GREEN: Step passes, downstream unblocks + - YELLOW (guardrails): Request approval + - RED: Initiate rework or request approval + +## Rework Flow + +When a step needs rework: + +1. Contract phase reset to editing +2. Supervisor receives rework instructions +3. Rework count incremented +4. If max reworks exceeded: escalate or fail + +## Event Types + +Events are logged for audit and monitoring: + +- `directive_created`, `directive_started`, `directive_paused`, `directive_completed` +- `chain_generated`, `chain_regenerated` +- `step_ready`, `step_started`, `step_evaluating`, `step_passed`, `step_failed` +- `rework_initiated`, `rework_completed` +- `approval_requested`, `approval_granted`, `approval_denied` +- `verifier_run`, `evaluation_completed` +- `circuit_breaker_triggered` + +## Verifier Configuration + +Verifiers can be auto-detected or manually configured: + +```json +POST /api/v1/directives/:id/verifiers +{ + "name": "Test Runner", + "verifierType": "test_runner", + "command": "npm test", + "workingDirectory": ".", + "timeoutSeconds": 300, + "weight": 1.0, + "required": true, + "enabled": true +} +``` + +### Auto-Detection + +The system detects verifiers from: +- `package.json` - npm test, npm run lint, npm run build +- `Cargo.toml` - cargo test, cargo clippy, cargo build +- `pyproject.toml` - pytest, ruff, mypy + +## Circuit Breakers + +Directives have built-in circuit breakers: + +- `maxTotalCostUsd` - Stop if cumulative cost exceeds limit +- `maxWallTimeMinutes` - Stop if elapsed time exceeds limit +- `maxReworkCycles` - Fail step after N rework attempts +- `maxChainRegenerations` - Fail if chain regenerated too many times + +## Example Workflow + +```bash +# 1. Create a directive +makima directive create \ + --goal "Add dark mode to the application" \ + --repository https://github.com/myorg/myapp \ + --autonomy guardrails + +# Returns directive ID: 123e4567-e89b-12d3-a456-426614174000 + +# 2. Start execution +makima directive start 123e4567-e89b-12d3-a456-426614174000 + +# 3. Monitor progress +makima directive status 123e4567-e89b-12d3-a456-426614174000 + +# 4. View the execution graph +makima directive graph 123e4567-e89b-12d3-a456-426614174000 --with-status + +# 5. Watch events +makima directive events 123e4567-e89b-12d3-a456-426614174000 + +# 6. If approval needed, approve or deny +makima directive approve 123e4567-e89b-12d3-a456-426614174000 <approval-id> +``` diff --git a/makima/src/daemon/skills/mod.rs b/makima/src/daemon/skills/mod.rs index 3b0c0dc..dafa9ec 100644 --- a/makima/src/daemon/skills/mod.rs +++ b/makima/src/daemon/skills/mod.rs @@ -9,12 +9,16 @@ pub const SUPERVISOR_SKILL: &str = include_str!("supervisor.md"); /// Contract skill content - task-contract interaction commands pub const CONTRACT_SKILL: &str = include_str!("contract.md"); -/// Chain skill content - multi-contract orchestration commands +/// Chain skill content - multi-contract orchestration commands (legacy) pub const CHAIN_SKILL: &str = include_str!("chain.md"); +/// Directive skill content - autonomous goal-driven orchestration +pub const DIRECTIVE_SKILL: &str = include_str!("directive.md"); + /// All skills as (name, content) pairs for installation pub const ALL_SKILLS: &[(&str, &str)] = &[ ("makima-supervisor", SUPERVISOR_SKILL), ("makima-contract", CONTRACT_SKILL), ("makima-chain", CHAIN_SKILL), + ("makima-directive", DIRECTIVE_SKILL), ]; diff --git a/makima/src/daemon/storage/patch.rs b/makima/src/daemon/storage/patch.rs index 0da4eda..b374d15 100644 --- a/makima/src/daemon/storage/patch.rs +++ b/makima/src/daemon/storage/patch.rs @@ -227,6 +227,16 @@ pub async fn create_export_patch( None }; + // Get current HEAD SHA for comparison + let head_sha = Command::new("git") + .current_dir(worktree_path) + .args(["rev-parse", "HEAD"]) + .output() + .await + .ok() + .filter(|o| o.status.success()) + .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()); + // If we couldn't find upstream, try common default branches let base = if base.is_none() { let default_branches = ["origin/main", "origin/master", "main", "master"]; @@ -241,14 +251,23 @@ pub async fn create_export_patch( if let Ok(output) = merge_base { if output.status.success() { - found_base = Some(String::from_utf8_lossy(&output.stdout).trim().to_string()); - break; + let mb_sha = String::from_utf8_lossy(&output.stdout).trim().to_string(); + // Skip if merge-base equals HEAD (would result in empty diff) + if head_sha.as_ref() != Some(&mb_sha) { + found_base = Some(mb_sha); + break; + } } } } found_base } else { - base + // Also check upstream base + if base.as_ref() == head_sha.as_ref() { + None + } else { + base + } }; // If still nothing, get the first commit or use HEAD~1 diff --git a/makima/src/daemon/task/completion_gate.rs b/makima/src/daemon/task/completion_gate.rs index 69b7c6a..40a6466 100644 --- a/makima/src/daemon/task/completion_gate.rs +++ b/makima/src/daemon/task/completion_gate.rs @@ -5,7 +5,7 @@ //! development framework. //! //! Format: -//! ``` +//! ```text //! <COMPLETION_GATE> //! ready: true|false //! reason: "explanation of completion status" @@ -133,19 +133,18 @@ impl CompletionGate { /// This is useful when Claude produces multiple completion gates during /// a long-running task, and we want to use the final status. pub fn parse_last(text: &str) -> Option<Self> { + let start_tag = "<COMPLETION_GATE>"; let end_tag = "</COMPLETION_GATE>"; - let mut last_gate = None; - let mut search_start = 0; - while let Some(end_idx) = text[search_start..].find(end_tag) { - let absolute_end = search_start + end_idx + end_tag.len(); - if let Some(gate) = Self::parse(&text[..absolute_end]) { - last_gate = Some(gate); - } - search_start = absolute_end; - } + // Find the last occurrence of the start tag + let start_idx = text.rfind(start_tag)?; + let remaining = &text[start_idx..]; + + // Find the end tag after the last start tag + let end_idx = remaining.find(end_tag)?; - last_gate + // Parse just this last gate + Self::parse(&remaining[..end_idx + end_tag.len()]) } } diff --git a/makima/src/daemon/task/state.rs b/makima/src/daemon/task/state.rs index 7b59b62..fe73de1 100644 --- a/makima/src/daemon/task/state.rs +++ b/makima/src/daemon/task/state.rs @@ -124,9 +124,7 @@ impl Default for TaskState { #[cfg(test)] mod tests { - #[allow(unused_imports)] - use crate::daemon::*; - use super::TaskState; + use super::*; #[test] fn test_valid_transitions() { diff --git a/makima/src/daemon/temp.rs b/makima/src/daemon/temp.rs index 42d4a28..015b21b 100644 --- a/makima/src/daemon/temp.rs +++ b/makima/src/daemon/temp.rs @@ -214,7 +214,7 @@ impl Default for TempManager { #[cfg(test)] mod tests { - use crate::daemon::*; + use super::*; #[test] fn test_temp_manager_default_dir() { diff --git a/makima/src/daemon/worktree/manager.rs b/makima/src/daemon/worktree/manager.rs index 166e654..310627c 100644 --- a/makima/src/daemon/worktree/manager.rs +++ b/makima/src/daemon/worktree/manager.rs @@ -1949,7 +1949,8 @@ pub fn sanitize_name(name: &str) -> String { #[cfg(test)] mod tests { - use crate::daemon::*; + use super::*; + use uuid::Uuid; #[test] fn test_extract_repo_name() { diff --git a/makima/src/daemon/ws/protocol.rs b/makima/src/daemon/ws/protocol.rs index 5c88038..574e864 100644 --- a/makima/src/daemon/ws/protocol.rs +++ b/makima/src/daemon/ws/protocol.rs @@ -907,7 +907,7 @@ impl DaemonMessage { #[cfg(test)] mod tests { - use crate::daemon::*; + use super::*; #[test] fn test_daemon_message_serialization() { @@ -920,7 +920,7 @@ mod tests { #[test] fn test_daemon_command_deserialization() { - let json = r#"{"type":"spawnTask","taskId":"550e8400-e29b-41d4-a716-446655440000","plan":"Build the feature","repoUrl":"https://github.com/test/repo","baseBranch":"main","parentTaskId":null,"depth":0,"isOrchestrator":false}"#; + let json = r#"{"type":"spawnTask","taskId":"550e8400-e29b-41d4-a716-446655440000","taskName":"Build Feature","plan":"Build the feature","repoUrl":"https://github.com/test/repo","baseBranch":"main","parentTaskId":null,"depth":0,"isOrchestrator":false}"#; let cmd: DaemonCommand = serde_json::from_str(json).unwrap(); match cmd { DaemonCommand::SpawnTask { @@ -945,7 +945,7 @@ mod tests { #[test] fn test_orchestrator_spawn_deserialization() { - let json = r#"{"type":"spawnTask","taskId":"550e8400-e29b-41d4-a716-446655440000","plan":"Coordinate subtasks","repoUrl":"https://github.com/test/repo","baseBranch":"main","parentTaskId":null,"depth":0,"isOrchestrator":true}"#; + let json = r#"{"type":"spawnTask","taskId":"550e8400-e29b-41d4-a716-446655440000","taskName":"Coordinate","plan":"Coordinate subtasks","repoUrl":"https://github.com/test/repo","baseBranch":"main","parentTaskId":null,"depth":0,"isOrchestrator":true}"#; let cmd: DaemonCommand = serde_json::from_str(json).unwrap(); match cmd { DaemonCommand::SpawnTask { |
