summaryrefslogtreecommitdiff
path: root/makima/src/daemon
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/daemon')
-rw-r--r--makima/src/daemon/api/directive.rs162
-rw-r--r--makima/src/daemon/api/mod.rs1
-rw-r--r--makima/src/daemon/chain/parser.rs7
-rw-r--r--makima/src/daemon/chain/runner.rs12
-rw-r--r--makima/src/daemon/cli/directive.rs186
-rw-r--r--makima/src/daemon/cli/mod.rs56
-rw-r--r--makima/src/daemon/db/local.rs4
-rw-r--r--makima/src/daemon/process/claude_protocol.rs2
-rw-r--r--makima/src/daemon/skills/directive.md303
-rw-r--r--makima/src/daemon/skills/mod.rs6
-rw-r--r--makima/src/daemon/storage/patch.rs25
-rw-r--r--makima/src/daemon/task/completion_gate.rs21
-rw-r--r--makima/src/daemon/task/state.rs4
-rw-r--r--makima/src/daemon/temp.rs2
-rw-r--r--makima/src/daemon/worktree/manager.rs3
-rw-r--r--makima/src/daemon/ws/protocol.rs6
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 {