summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-08 21:07:30 +0000
committersoryu <soryu@soryu.co>2026-02-08 21:07:30 +0000
commit3662b334dfd68cfdf00ed44ae88927c2e1b2aabe (patch)
treebff5ae1e189fb8bcc0211d97dab3b9acb4257038
parent87fa8c4af66745bd30bc84b6c5ef657dd4dec002 (diff)
downloadsoryu-3662b334dfd68cfdf00ed44ae88927c2e1b2aabe.tar.gz
soryu-3662b334dfd68cfdf00ed44ae88927c2e1b2aabe.zip
Remove directive mechanism
-rw-r--r--makima/frontend/src/components/NavStrip.tsx1
-rw-r--r--makima/frontend/src/components/directives/DirectiveContractsTab.tsx148
-rw-r--r--makima/frontend/src/components/directives/DirectiveDetail.tsx425
-rw-r--r--makima/frontend/src/components/directives/DirectiveList.tsx143
-rw-r--r--makima/frontend/src/components/directives/StepDiagram.tsx313
-rw-r--r--makima/frontend/src/hooks/useDirectives.ts127
-rw-r--r--makima/frontend/src/lib/api.ts213
-rw-r--r--makima/frontend/src/main.tsx17
-rw-r--r--makima/frontend/src/routes/directives.tsx403
-rw-r--r--makima/migrations/20260209000000_remove_directive_system.sql18
-rw-r--r--makima/src/bin/makima.rs93
-rw-r--r--makima/src/daemon/api/directive.rs106
-rw-r--r--makima/src/daemon/api/mod.rs1
-rw-r--r--makima/src/daemon/cli/directive.rs60
-rw-r--r--makima/src/daemon/cli/mod.rs40
-rw-r--r--makima/src/daemon/skills/directive.md88
-rw-r--r--makima/src/daemon/skills/mod.rs4
-rw-r--r--makima/src/db/models.rs333
-rw-r--r--makima/src/db/repository.rs888
-rw-r--r--makima/src/orchestration/directive.rs1685
-rw-r--r--makima/src/orchestration/mod.rs2
-rw-r--r--makima/src/server/handlers/contracts.rs18
-rw-r--r--makima/src/server/handlers/directives.rs785
-rw-r--r--makima/src/server/handlers/mesh_daemon.rs10
-rw-r--r--makima/src/server/handlers/mod.rs1
-rw-r--r--makima/src/server/mod.rs19
-rw-r--r--makima/src/server/openapi.rs44
27 files changed, 35 insertions, 5950 deletions
diff --git a/makima/frontend/src/components/NavStrip.tsx b/makima/frontend/src/components/NavStrip.tsx
index f7e67db..fb95c7f 100644
--- a/makima/frontend/src/components/NavStrip.tsx
+++ b/makima/frontend/src/components/NavStrip.tsx
@@ -11,7 +11,6 @@ interface NavLink {
const NAV_LINKS: NavLink[] = [
{ label: "Listen", href: "/listen" },
{ label: "Contracts", href: "/contracts", requiresAuth: true },
- { label: "Directives", href: "/directives", requiresAuth: true },
{ label: "Board", href: "/workflow", requiresAuth: true },
{ label: "Mesh", href: "/mesh", requiresAuth: true },
{ label: "History", href: "/history", requiresAuth: true },
diff --git a/makima/frontend/src/components/directives/DirectiveContractsTab.tsx b/makima/frontend/src/components/directives/DirectiveContractsTab.tsx
deleted file mode 100644
index 28da117..0000000
--- a/makima/frontend/src/components/directives/DirectiveContractsTab.tsx
+++ /dev/null
@@ -1,148 +0,0 @@
-import { useNavigate } from "react-router";
-import type {
- DirectiveWithChains,
- StepContractSummary,
- ContractPhase,
-} from "../../lib/api";
-import { PhaseProgressBarCompact } from "../contracts/PhaseProgressBar";
-
-interface DirectiveContractsTabProps {
- directive: DirectiveWithChains;
-}
-
-const statusColors: Record<string, string> = {
- active: "text-green-400",
- completed: "text-blue-400",
- archived: "text-[#555]",
-};
-
-function ContractCard({
- summary,
- label,
-}: {
- summary: StepContractSummary;
- label: string;
-}) {
- const navigate = useNavigate();
-
- const progressPct =
- summary.taskCount > 0
- ? Math.round((summary.tasksDone / summary.taskCount) * 100)
- : 0;
-
- return (
- <div
- className="border border-dashed border-[rgba(117,170,252,0.25)] bg-[rgba(117,170,252,0.03)] p-3 cursor-pointer hover:bg-[rgba(117,170,252,0.06)] transition-colors"
- onClick={() => navigate(`/contracts/${summary.id}`)}
- >
- <div className="flex items-center justify-between mb-1.5">
- <div className="flex items-center gap-2 min-w-0">
- <span className="font-mono text-[11px] text-[#dbe7ff] truncate">
- {summary.name}
- </span>
- <span className="font-mono text-[9px] text-[#7788aa] uppercase shrink-0">
- {summary.contractType}
- </span>
- </div>
- <div className="flex items-center gap-2 shrink-0">
- <span
- className={`font-mono text-[9px] uppercase ${statusColors[summary.status] || "text-[#888]"}`}
- >
- {summary.status}
- </span>
- <span className="font-mono text-[9px] text-[#75aafc]">&rarr;</span>
- </div>
- </div>
-
- <div className="flex items-center gap-2 mb-1.5">
- <span className="font-mono text-[9px] text-[#7788aa] uppercase shrink-0">
- {label}
- </span>
- <PhaseProgressBarCompact
- currentPhase={summary.phase as ContractPhase}
- />
- </div>
-
- {/* Task progress bar */}
- <div className="flex items-center gap-2">
- <div className="flex-1 h-1 bg-[rgba(117,170,252,0.1)] rounded-full overflow-hidden">
- <div
- className="h-full bg-[#3f6fb3] rounded-full transition-all"
- style={{ width: `${progressPct}%` }}
- />
- </div>
- <span className="font-mono text-[9px] text-[#7788aa] shrink-0">
- {summary.tasksDone}/{summary.taskCount} tasks
- </span>
- </div>
- </div>
- );
-}
-
-export function DirectiveContractsTab({
- directive,
-}: DirectiveContractsTabProps) {
- // Collect all contract summaries
- const contracts: { summary: StepContractSummary; label: string }[] = [];
-
- if (directive.orchestratorContractSummary) {
- contracts.push({
- summary: directive.orchestratorContractSummary,
- label: "Planning",
- });
- }
-
- for (const chain of directive.chains) {
- for (const step of chain.steps) {
- if (step.contractSummary) {
- contracts.push({
- summary: step.contractSummary,
- label: step.name,
- });
- }
- // Show monitoring/evaluation contracts
- if (step.monitoringContractId) {
- contracts.push({
- summary: {
- id: step.monitoringContractId,
- name: `${step.name} - Evaluation`,
- contractType: "monitoring",
- status: step.status === "evaluating" ? "active" : "completed",
- phase: "plan",
- taskCount: 1,
- tasksDone: step.status === "evaluating" ? 0 : 1,
- tasksRunning: step.status === "evaluating" ? 1 : 0,
- tasksFailed: 0,
- },
- label: `${step.name} eval`,
- });
- }
- }
- }
-
- if (contracts.length === 0) {
- return (
- <div className="text-center py-8">
- <p className="font-mono text-xs text-[#7788aa]">
- {directive.status === "draft"
- ? "No contracts yet. Start the directive to begin planning."
- : directive.status === "planning"
- ? "Planning in progress... contracts will appear when steps are created."
- : "No contracts associated with this directive."}
- </p>
- </div>
- );
- }
-
- return (
- <div className="space-y-2">
- {contracts.map((c) => (
- <ContractCard
- key={c.summary.id}
- summary={c.summary}
- label={c.label}
- />
- ))}
- </div>
- );
-}
diff --git a/makima/frontend/src/components/directives/DirectiveDetail.tsx b/makima/frontend/src/components/directives/DirectiveDetail.tsx
deleted file mode 100644
index 6bdf5aa..0000000
--- a/makima/frontend/src/components/directives/DirectiveDetail.tsx
+++ /dev/null
@@ -1,425 +0,0 @@
-import { useState, useEffect, useRef } from "react";
-import { useNavigate } from "react-router";
-import type {
- DirectiveWithChains,
- DirectiveStatus,
- ContractPhase,
-} from "../../lib/api";
-import { getDirective } from "../../lib/api";
-import { PhaseProgressBarCompact } from "../contracts/PhaseProgressBar";
-import { StepDiagram } from "./StepDiagram";
-import { DirectiveContractsTab } from "./DirectiveContractsTab";
-
-interface DirectiveDetailProps {
- directive: DirectiveWithChains;
- onBack: () => void;
- onDelete?: (id: string) => void;
- onStart?: (id: string) => void;
- onRefresh?: (updated: DirectiveWithChains) => void;
-}
-
-type Tab = "overview" | "chain" | "contracts";
-
-const statusColors: Record<DirectiveStatus, string> = {
- draft: "text-[#888]",
- planning: "text-yellow-400",
- active: "text-green-400",
- paused: "text-orange-400",
- completed: "text-blue-400",
- archived: "text-[#555]",
- failed: "text-red-400",
-};
-
-function JsonSection({
- label,
- data,
-}: {
- label: string;
- data: unknown[] | unknown;
-}) {
- const items = Array.isArray(data) ? data : [];
- if (items.length === 0) return null;
-
- return (
- <div>
- <h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-1">
- {label}
- </h4>
- <div className="font-mono text-xs text-[#9bb8d8] bg-[rgba(0,0,0,0.2)] p-2 max-h-32 overflow-y-auto">
- {items.map((item, i) => (
- <div key={i} className="mb-0.5">
- {typeof item === "string" ? item : JSON.stringify(item)}
- </div>
- ))}
- </div>
- </div>
- );
-}
-
-export function DirectiveDetail({
- directive,
- onBack,
- onDelete,
- onStart,
- onRefresh,
-}: DirectiveDetailProps) {
- const navigate = useNavigate();
- const [activeTab, setActiveTab] = useState<Tab>("overview");
-
- // Auto-poll when directive is in an active state
- const isLive =
- directive.status === "planning" || directive.status === "active";
- const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
-
- useEffect(() => {
- if (!isLive) {
- if (intervalRef.current) {
- clearInterval(intervalRef.current);
- intervalRef.current = null;
- }
- return;
- }
-
- intervalRef.current = setInterval(async () => {
- try {
- const updated = await getDirective(directive.id);
- if (updated && onRefresh) {
- onRefresh(updated);
- }
- } catch {
- // Ignore poll errors
- }
- }, 5000);
-
- return () => {
- if (intervalRef.current) {
- clearInterval(intervalRef.current);
- intervalRef.current = null;
- }
- };
- }, [isLive, directive.id, onRefresh]);
-
- // Count total steps and completed steps across all chains
- const totalSteps = directive.chains.reduce(
- (sum, c) => sum + c.totalSteps,
- 0
- );
- const completedSteps = directive.chains.reduce(
- (sum, c) => sum + c.completedSteps,
- 0
- );
-
- // Count contracts
- const contractCount =
- (directive.orchestratorContractSummary ? 1 : 0) +
- directive.chains.reduce(
- (sum, c) =>
- sum + c.steps.filter((s) => s.contractSummary != null).length,
- 0
- );
-
- const tabs: { key: Tab; label: string; count?: number }[] = [
- { key: "overview", label: "Overview" },
- { key: "chain", label: "Chain", count: totalSteps },
- { key: "contracts", label: "Contracts", count: contractCount },
- ];
-
- return (
- <div className="panel h-full flex flex-col">
- {/* Header */}
- <div className="p-4 border-b border-dashed border-[rgba(117,170,252,0.35)]">
- <div className="flex items-center justify-between mb-3">
- <button
- onClick={onBack}
- className="font-mono text-xs text-[#75aafc] hover:text-[#9bc3ff] transition-colors"
- >
- &larr; Back to list
- </button>
- <div className="flex items-center gap-2">
- {onStart && directive.status === "draft" && (
- <button
- onClick={() => onStart(directive.id)}
- className="px-3 py-1.5 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase"
- >
- Start
- </button>
- )}
- {onDelete && (
- <button
- onClick={() => onDelete(directive.id)}
- className="px-3 py-1.5 font-mono text-xs text-red-400 border border-red-400/30 hover:border-red-400/50 transition-colors uppercase"
- >
- Delete
- </button>
- )}
- </div>
- </div>
- <div className="flex items-center gap-3 mb-2">
- <h2 className="font-mono text-lg text-[#dbe7ff]">
- {directive.title}
- </h2>
- <span
- className={`font-mono text-xs uppercase ${
- statusColors[directive.status as DirectiveStatus] || "text-[#888]"
- }`}
- >
- {directive.status}
- </span>
- {isLive && (
- <span className="font-mono text-[9px] text-yellow-400/60 animate-pulse">
- polling
- </span>
- )}
- <span className="font-mono text-[10px] text-[#7788aa]">
- v{directive.version}
- </span>
- </div>
- </div>
-
- {/* Tabs */}
- <div className="flex border-b border-[rgba(117,170,252,0.2)]">
- {tabs.map((tab) => (
- <button
- key={tab.key}
- onClick={() => setActiveTab(tab.key)}
- className={`
- px-4 py-2 font-mono text-xs uppercase tracking-wider transition-colors
- ${
- activeTab === tab.key
- ? "text-[#dbe7ff] border-b-2 border-[#75aafc]"
- : "text-[#555] hover:text-[#9bc3ff]"
- }
- `}
- >
- {tab.label}
- {tab.count != null && tab.count > 0 && (
- <span className="ml-1 text-[10px] text-[#7788aa]">
- ({tab.count})
- </span>
- )}
- </button>
- ))}
- </div>
-
- {/* Tab content */}
- <div className="flex-1 overflow-y-auto p-4">
- {activeTab === "overview" && (
- <div className="space-y-4">
- {/* Orchestrator contract link */}
- {directive.orchestratorContractId && (
- <div className="flex items-center gap-2 p-2 border border-dashed border-[rgba(117,170,252,0.2)] bg-[rgba(117,170,252,0.03)]">
- <span className="font-mono text-[10px] text-[#7788aa] uppercase">
- Planning Contract
- </span>
- {directive.orchestratorContractSummary && (
- <PhaseProgressBarCompact
- currentPhase={
- directive.orchestratorContractSummary
- .phase as ContractPhase
- }
- />
- )}
- <button
- onClick={() =>
- navigate(
- `/contracts/${directive.orchestratorContractId}`
- )
- }
- className="font-mono text-[11px] text-[#75aafc] hover:text-white transition-colors"
- >
- {directive.orchestratorContractSummary?.name ||
- directive.orchestratorContractId.slice(0, 8) + "..."}{" "}
- &rarr;
- </button>
- {directive.status === "planning" && (
- <span className="font-mono text-[9px] text-yellow-400 animate-pulse">
- planning in progress
- </span>
- )}
- </div>
- )}
-
- {/* Goal */}
- <div>
- <h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-1">
- Goal
- </h4>
- <p className="font-mono text-xs text-[#9bb8d8] whitespace-pre-wrap">
- {directive.goal}
- </p>
- </div>
-
- {/* Config grid */}
- <div className="grid grid-cols-3 gap-2">
- <div>
- <span className="font-mono text-[10px] text-[#7788aa] uppercase">
- Autonomy
- </span>
- <div className="font-mono text-xs text-[#dbe7ff]">
- {directive.autonomyLevel}
- </div>
- </div>
- <div>
- <span className="font-mono text-[10px] text-[#7788aa] uppercase">
- Chains
- </span>
- <div className="font-mono text-xs text-[#dbe7ff]">
- {directive.chainGenerationCount} generated
- </div>
- </div>
- <div>
- <span className="font-mono text-[10px] text-[#7788aa] uppercase">
- Cost
- </span>
- <div className="font-mono text-xs text-[#dbe7ff]">
- ${directive.totalCostUsd.toFixed(2)}
- </div>
- </div>
- {directive.repositoryUrl && (
- <div className="col-span-3">
- <span className="font-mono text-[10px] text-[#7788aa] uppercase">
- Repository
- </span>
- <div className="font-mono text-xs text-[#dbe7ff] truncate">
- {directive.repositoryUrl}
- </div>
- </div>
- )}
- </div>
-
- {/* Stat cards */}
- <div className="grid grid-cols-3 gap-2">
- <div className="border border-dashed border-[rgba(117,170,252,0.2)] p-2 text-center">
- <div className="font-mono text-lg text-[#dbe7ff]">
- {totalSteps}
- </div>
- <div className="font-mono text-[9px] text-[#7788aa] uppercase">
- Total Steps
- </div>
- </div>
- <div className="border border-dashed border-[rgba(117,170,252,0.2)] p-2 text-center">
- <div className="font-mono text-lg text-green-400">
- {completedSteps}
- </div>
- <div className="font-mono text-[9px] text-[#7788aa] uppercase">
- Completed
- </div>
- </div>
- <div className="border border-dashed border-[rgba(117,170,252,0.2)] p-2 text-center">
- <div className="font-mono text-lg text-[#dbe7ff]">
- ${directive.totalCostUsd.toFixed(2)}
- </div>
- <div className="font-mono text-[9px] text-[#7788aa] uppercase">
- Cost
- </div>
- </div>
- </div>
-
- {/* Structured sections */}
- <JsonSection label="Requirements" data={directive.requirements} />
- <JsonSection
- label="Acceptance Criteria"
- data={directive.acceptanceCriteria}
- />
- <JsonSection label="Constraints" data={directive.constraints} />
- <JsonSection
- label="External Dependencies"
- data={directive.externalDependencies}
- />
-
- {/* Metadata */}
- <div>
- <h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-1">
- Metadata
- </h4>
- <div className="grid grid-cols-2 gap-1 font-mono text-[10px]">
- <span className="text-[#7788aa]">Created</span>
- <span className="text-[#9bb8d8]">
- {new Date(directive.createdAt).toLocaleString()}
- </span>
- <span className="text-[#7788aa]">Updated</span>
- <span className="text-[#9bb8d8]">
- {new Date(directive.updatedAt).toLocaleString()}
- </span>
- {directive.startedAt && (
- <>
- <span className="text-[#7788aa]">Started</span>
- <span className="text-[#9bb8d8]">
- {new Date(directive.startedAt).toLocaleString()}
- </span>
- </>
- )}
- {directive.completedAt && (
- <>
- <span className="text-[#7788aa]">Completed</span>
- <span className="text-[#9bb8d8]">
- {new Date(directive.completedAt).toLocaleString()}
- </span>
- </>
- )}
- <span className="text-[#7788aa]">Version</span>
- <span className="text-[#9bb8d8]">{directive.version}</span>
- </div>
- </div>
- </div>
- )}
-
- {activeTab === "chain" && (
- <div className="space-y-4">
- {directive.chains.length === 0 ? (
- <div className="flex flex-col items-center justify-center py-12">
- <div className="font-mono text-[40px] text-[#333] mb-3">
- {directive.status === "planning" ? "\u2699" : "\u25CB"}
- </div>
- <p className="font-mono text-xs text-[#7788aa] text-center max-w-[300px]">
- {directive.status === "planning"
- ? "Planning in progress... the chain will appear when the planner submits a plan."
- : directive.status === "draft"
- ? "No chains yet. Start the directive to begin planning."
- : "No chains created for this directive."}
- </p>
- </div>
- ) : (
- <>
- {/* Chain metadata header */}
- {directive.chains.map((chain) => (
- <div key={chain.id} className="space-y-3">
- <div className="flex items-center gap-3 pb-2 border-b border-dashed border-[rgba(117,170,252,0.15)]">
- <span className="font-mono text-xs text-[#dbe7ff]">
- {chain.name}
- </span>
- <span className="font-mono text-[10px] text-[#7788aa] uppercase">
- gen {chain.generation}
- </span>
- <span className={`font-mono text-[10px] uppercase ${
- chain.status === "completed" ? "text-green-400" :
- chain.status === "failed" ? "text-red-400" :
- chain.status === "running" ? "text-yellow-400" :
- "text-[#7788aa]"
- }`}>
- {chain.status}
- </span>
- <span className="font-mono text-[10px] text-[#7788aa] ml-auto">
- {chain.completedSteps}/{chain.totalSteps} steps
- {chain.failedSteps > 0 && (
- <span className="text-red-400 ml-1">
- ({chain.failedSteps} failed)
- </span>
- )}
- </span>
- </div>
- <StepDiagram steps={chain.steps} />
- </div>
- ))}
- </>
- )}
- </div>
- )}
-
- {activeTab === "contracts" && (
- <DirectiveContractsTab directive={directive} />
- )}
- </div>
- </div>
- );
-}
diff --git a/makima/frontend/src/components/directives/DirectiveList.tsx b/makima/frontend/src/components/directives/DirectiveList.tsx
deleted file mode 100644
index 5afa36e..0000000
--- a/makima/frontend/src/components/directives/DirectiveList.tsx
+++ /dev/null
@@ -1,143 +0,0 @@
-import { useState } from "react";
-import type { DirectiveSummary, DirectiveStatus } from "../../lib/api";
-
-interface DirectiveListProps {
- directives: DirectiveSummary[];
- loading: boolean;
- onSelect: (id: string) => void;
- onCreate: () => void;
- onDelete?: (directive: DirectiveSummary) => void;
- selectedId?: string;
-}
-
-const statusColors: Record<DirectiveStatus, string> = {
- draft: "text-[#888]",
- planning: "text-yellow-400",
- active: "text-green-400",
- paused: "text-orange-400",
- completed: "text-blue-400",
- archived: "text-[#555]",
- failed: "text-red-400",
-};
-
-export function DirectiveList({
- directives,
- loading,
- onSelect,
- onCreate,
- selectedId,
-}: DirectiveListProps) {
- const [filter, setFilter] = useState<DirectiveStatus | "all">("all");
-
- const filteredDirectives =
- filter === "all"
- ? directives
- : directives.filter((d) => d.status === filter);
-
- if (loading) {
- return (
- <div className="panel h-full flex items-center justify-center">
- <div className="font-mono text-[#9bc3ff] text-sm">Loading...</div>
- </div>
- );
- }
-
- return (
- <div className="panel h-full flex flex-col">
- {/* Header */}
- <div className="p-4 border-b border-dashed border-[rgba(117,170,252,0.35)]">
- <div className="flex items-center justify-between mb-3">
- <h2 className="font-mono text-sm text-[#75aafc] uppercase tracking-wider">
- Directives
- </h2>
- <button
- onClick={onCreate}
- className="px-3 py-1.5 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase"
- >
- + New
- </button>
- </div>
-
- {/* Filter tabs */}
- <div className="flex gap-1 flex-wrap">
- {(["all", "draft", "planning", "active", "paused", "completed", "failed"] as const).map(
- (status) => (
- <button
- key={status}
- onClick={() => setFilter(status)}
- className={`
- px-2 py-1 font-mono text-[10px] uppercase tracking-wider transition-colors
- ${
- filter === status
- ? "bg-[rgba(117,170,252,0.1)] text-[#9bc3ff] border border-[rgba(117,170,252,0.3)]"
- : "text-[#555] hover:text-[#75aafc]"
- }
- `}
- >
- {status}
- </button>
- )
- )}
- </div>
- </div>
-
- {/* List */}
- <div className="flex-1 overflow-y-auto">
- {filteredDirectives.length === 0 ? (
- <div className="p-4 text-center">
- <p className="font-mono text-sm text-[#555]">
- {filter === "all"
- ? "No directives yet"
- : `No ${filter} directives`}
- </p>
- </div>
- ) : (
- <div className="divide-y divide-[rgba(117,170,252,0.15)]">
- {filteredDirectives.map((directive) => (
- <button
- key={directive.id}
- onClick={() => onSelect(directive.id)}
- className={`
- w-full text-left p-4 transition-colors
- ${
- selectedId === directive.id
- ? "bg-[rgba(117,170,252,0.1)]"
- : "hover:bg-[rgba(117,170,252,0.05)]"
- }
- `}
- >
- <div className="flex items-start justify-between gap-2 mb-2">
- <h3 className="font-mono text-sm text-[#dbe7ff] truncate">
- {directive.title}
- </h3>
- <span
- className={`text-[10px] font-mono uppercase shrink-0 ${
- statusColors[directive.status] || "text-[#888]"
- }`}
- >
- {directive.status}
- </span>
- </div>
-
- {directive.goal && (
- <p className="font-mono text-xs text-[#555] mb-2 line-clamp-2">
- {directive.goal}
- </p>
- )}
-
- <div className="flex items-center gap-3 text-[10px] font-mono text-[#555]">
- {directive.chainCount > 0 && (
- <span>{directive.chainCount} chains</span>
- )}
- {directive.stepCount > 0 && (
- <span>{directive.stepCount} steps</span>
- )}
- </div>
- </button>
- ))}
- </div>
- )}
- </div>
- </div>
- );
-}
diff --git a/makima/frontend/src/components/directives/StepDiagram.tsx b/makima/frontend/src/components/directives/StepDiagram.tsx
deleted file mode 100644
index 33892e0..0000000
--- a/makima/frontend/src/components/directives/StepDiagram.tsx
+++ /dev/null
@@ -1,313 +0,0 @@
-import { useNavigate } from "react-router";
-import type { ChainStep, ContractPhase } from "../../lib/api";
-import { PhaseProgressBarCompact } from "../contracts/PhaseProgressBar";
-
-interface StepDiagramProps {
- steps: ChainStep[];
-}
-
-const statusColors: Record<string, { border: string; dot: string; bg: string; glow: string }> = {
- pending: {
- border: "border-[#444]",
- dot: "bg-[#555]",
- bg: "bg-[rgba(40,40,50,0.6)]",
- glow: "",
- },
- running: {
- border: "border-yellow-400/60",
- dot: "bg-yellow-400",
- bg: "bg-[rgba(80,70,20,0.3)]",
- glow: "shadow-[0_0_8px_rgba(250,204,21,0.15)]",
- },
- evaluating: {
- border: "border-blue-400/60",
- dot: "bg-blue-400",
- bg: "bg-[rgba(20,50,80,0.3)]",
- glow: "shadow-[0_0_8px_rgba(96,165,250,0.15)]",
- },
- passed: {
- border: "border-green-400/60",
- dot: "bg-green-400",
- bg: "bg-[rgba(20,60,30,0.3)]",
- glow: "",
- },
- failed: {
- border: "border-red-400/60",
- dot: "bg-red-400",
- bg: "bg-[rgba(60,20,20,0.3)]",
- glow: "",
- },
-};
-
-const statusLabels: Record<string, string> = {
- pending: "Pending",
- ready: "Ready",
- running: "Running",
- evaluating: "Evaluating",
- passed: "Passed",
- failed: "Failed",
- rework: "Rework",
- skipped: "Skipped",
- blocked: "Blocked",
-};
-
-const confidenceColors: Record<string, string> = {
- green: "text-green-400",
- yellow: "text-yellow-400",
- red: "text-red-400",
-};
-
-/**
- * Assign depth to each step via topological sort based on dependsOn UUIDs.
- */
-function assignDepths(steps: ChainStep[]): Map<string, number> {
- const depths = new Map<string, number>();
- const stepMap = new Map(steps.map((s) => [s.id, s]));
-
- function getDepth(id: string): number {
- if (depths.has(id)) return depths.get(id)!;
- const step = stepMap.get(id);
- if (!step || !step.dependsOn || step.dependsOn.length === 0) {
- depths.set(id, 0);
- return 0;
- }
- const maxParent = Math.max(
- ...step.dependsOn.map((depId) => getDepth(depId))
- );
- const d = maxParent + 1;
- depths.set(id, d);
- return d;
- }
-
- for (const step of steps) {
- getDepth(step.id);
- }
-
- return depths;
-}
-
-function StepCard({ step }: { step: ChainStep }) {
- const navigate = useNavigate();
- const colors = statusColors[step.status] || statusColors.pending;
- const summary = step.contractSummary;
- const hasContract = !!step.contractId;
-
- return (
- <div
- className={`
- border ${colors.border} ${colors.bg} ${colors.glow}
- p-3 w-[220px] transition-all duration-200
- ${hasContract ? "cursor-pointer hover:brightness-125" : ""}
- `}
- onClick={() => {
- if (hasContract) navigate(`/contracts/${step.contractId}`);
- }}
- title={hasContract ? "View contract" : undefined}
- >
- {/* Status header */}
- <div className="flex items-center justify-between mb-2">
- <div className="flex items-center gap-2 min-w-0 flex-1">
- <div className={`w-2 h-2 rounded-full ${colors.dot} shrink-0 ${
- step.status === "running" ? "animate-pulse" : ""
- }`} />
- <span className="font-mono text-[11px] text-[#dbe7ff] truncate font-medium">
- {step.name}
- </span>
- </div>
- <span className={`font-mono text-[9px] uppercase tracking-wider shrink-0 ml-2 ${
- step.status === "passed" ? "text-green-400" :
- step.status === "failed" ? "text-red-400" :
- step.status === "running" ? "text-yellow-400" :
- step.status === "evaluating" ? "text-blue-400" :
- "text-[#555]"
- }`}>
- {statusLabels[step.status] || step.status}
- </span>
- </div>
-
- {/* Description */}
- {step.description && (
- <p className="font-mono text-[10px] text-[#7788aa] mb-2 line-clamp-2 leading-relaxed">
- {step.description}
- </p>
- )}
-
- {/* Evaluation info */}
- {(step.confidenceScore != null || step.evaluationCount > 0 || step.reworkCount > 0) && (
- <div className="border-t border-[rgba(117,170,252,0.1)] pt-2 mt-1">
- <div className="flex items-center gap-2 font-mono text-[9px]">
- {step.confidenceScore != null && (
- <span className={confidenceColors[step.confidenceLevel || ""] || "text-[#7788aa]"}>
- {Math.round(step.confidenceScore * 100)}% confidence
- </span>
- )}
- {step.evaluationCount > 0 && (
- <span className="text-[#7788aa]">
- eval #{step.evaluationCount}
- </span>
- )}
- {step.reworkCount > 0 && (
- <span className="text-orange-400">
- rework x{step.reworkCount}
- </span>
- )}
- </div>
- </div>
- )}
-
- {/* Monitoring link (when evaluating) */}
- {step.status === "evaluating" && step.monitoringContractId && (
- <div className="border-t border-[rgba(117,170,252,0.1)] pt-1.5 mt-1">
- <span
- className="font-mono text-[9px] text-blue-400 cursor-pointer hover:text-blue-300"
- onClick={(e) => {
- e.stopPropagation();
- navigate(`/contracts/${step.monitoringContractId}`);
- }}
- >
- evaluation contract &rarr;
- </span>
- </div>
- )}
-
- {/* Contract progress */}
- {summary && (
- <div className="border-t border-[rgba(117,170,252,0.1)] pt-2 mt-1">
- <div className="mb-1.5">
- <PhaseProgressBarCompact
- currentPhase={summary.phase as ContractPhase}
- />
- </div>
- <div className="flex items-center gap-3 font-mono text-[9px]">
- <span className="text-[#7788aa]">
- {summary.tasksDone}/{summary.taskCount} tasks
- </span>
- {summary.tasksRunning > 0 && (
- <span className="text-yellow-400">
- {summary.tasksRunning} running
- </span>
- )}
- {summary.tasksFailed > 0 && (
- <span className="text-red-400">
- {summary.tasksFailed} failed
- </span>
- )}
- </div>
- </div>
- )}
-
- {/* Contract link arrow */}
- {hasContract && !summary && step.status !== "evaluating" && (
- <div className="border-t border-[rgba(117,170,252,0.1)] pt-1.5 mt-1">
- <span className="font-mono text-[9px] text-[#75aafc]">
- view contract &rarr;
- </span>
- </div>
- )}
- </div>
- );
-}
-
-/** Vertical connector between levels */
-function LevelConnector({ count }: { count: number }) {
- return (
- <div className="flex justify-center py-1">
- <div className="flex items-center gap-3">
- {Array.from({ length: count }).map((_, i) => (
- <div key={i} className="flex flex-col items-center">
- <div className="w-px h-4 bg-[rgba(117,170,252,0.25)]" />
- <div className="text-[rgba(117,170,252,0.4)] text-[10px] leading-none">&darr;</div>
- <div className="w-px h-4 bg-[rgba(117,170,252,0.25)]" />
- </div>
- ))}
- </div>
- </div>
- );
-}
-
-export function StepDiagram({ steps }: StepDiagramProps) {
- if (steps.length === 0) {
- return (
- <p className="font-mono text-xs text-[#7788aa]">No steps to display.</p>
- );
- }
-
- const depths = assignDepths(steps);
- const maxDepth = Math.max(...Array.from(depths.values()));
-
- // Group steps by depth level
- const levels: ChainStep[][] = [];
- for (let d = 0; d <= maxDepth; d++) {
- levels.push(
- steps
- .filter((s) => depths.get(s.id) === d)
- .sort((a, b) => a.orderIndex - b.orderIndex)
- );
- }
-
- // Compute overall progress
- const passedCount = steps.filter(s => s.status === "passed").length;
- const failedCount = steps.filter(s => s.status === "failed").length;
- const runningCount = steps.filter(s => s.status === "running").length;
- const evaluatingCount = steps.filter(s => s.status === "evaluating").length;
-
- return (
- <div>
- {/* Progress summary */}
- <div className="flex items-center gap-4 mb-4 font-mono text-[10px]">
- <span className="text-[#7788aa]">
- {levels.length} level{levels.length !== 1 ? "s" : ""} &middot; {steps.length} step{steps.length !== 1 ? "s" : ""}
- </span>
- {passedCount > 0 && (
- <span className="text-green-400">{passedCount} passed</span>
- )}
- {runningCount > 0 && (
- <span className="text-yellow-400">{runningCount} running</span>
- )}
- {evaluatingCount > 0 && (
- <span className="text-blue-400">{evaluatingCount} evaluating</span>
- )}
- {failedCount > 0 && (
- <span className="text-red-400">{failedCount} failed</span>
- )}
- <div className="flex-1 h-1 bg-[rgba(117,170,252,0.1)] rounded-full overflow-hidden">
- <div
- className="h-full bg-green-400/60 rounded-full transition-all duration-500"
- style={{ width: `${(passedCount / steps.length) * 100}%` }}
- />
- </div>
- <span className="text-[#7788aa]">
- {Math.round((passedCount / steps.length) * 100)}%
- </span>
- </div>
-
- {/* Chain flow */}
- <div className="flex flex-col items-center">
- {levels.map((level, li) => (
- <div key={li}>
- {/* Level label */}
- {levels.length > 1 && (
- <div className="flex justify-center mb-2">
- <span className="font-mono text-[9px] text-[#555] uppercase tracking-widest px-2 py-0.5 border border-[rgba(117,170,252,0.1)] bg-[rgba(0,0,0,0.3)]">
- {li === 0 ? "Start" : li === maxDepth ? "Final" : `Level ${li}`}
- </span>
- </div>
- )}
-
- {/* Steps at this level */}
- <div className="flex items-start justify-center gap-3 flex-wrap">
- {level.map((step) => (
- <StepCard key={step.id} step={step} />
- ))}
- </div>
-
- {/* Connector to next level */}
- {li < maxDepth && (
- <LevelConnector count={Math.min(level.length, 3)} />
- )}
- </div>
- ))}
- </div>
- </div>
- );
-}
diff --git a/makima/frontend/src/hooks/useDirectives.ts b/makima/frontend/src/hooks/useDirectives.ts
deleted file mode 100644
index af1c8c6..0000000
--- a/makima/frontend/src/hooks/useDirectives.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-import { useState, useCallback, useEffect } from "react";
-import {
- listDirectives,
- getDirective,
- createDirective,
- updateDirective,
- deleteDirective,
- startDirective as startDirectiveApi,
- type DirectiveSummary,
- type DirectiveWithChains,
- type CreateDirectiveRequest,
- type UpdateDirectiveRequest,
-} from "../lib/api";
-
-export function useDirectives() {
- const [directives, setDirectives] = useState<DirectiveSummary[]>([]);
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState<string | null>(null);
-
- const fetchDirectives = useCallback(async () => {
- setLoading(true);
- setError(null);
- try {
- const response = await listDirectives();
- setDirectives(response.directives);
- } catch (e) {
- setError(e instanceof Error ? e.message : "Failed to fetch directives");
- } finally {
- setLoading(false);
- }
- }, []);
-
- const fetchDirective = useCallback(
- async (id: string): Promise<DirectiveWithChains | null> => {
- setError(null);
- try {
- return await getDirective(id);
- } catch (e) {
- setError(e instanceof Error ? e.message : "Failed to fetch directive");
- return null;
- }
- },
- []
- );
-
- const saveDirective = useCallback(
- async (data: CreateDirectiveRequest): Promise<DirectiveSummary | null> => {
- setError(null);
- try {
- const directive = await createDirective(data);
- await fetchDirectives();
- return directive as unknown as DirectiveSummary;
- } catch (e) {
- setError(e instanceof Error ? e.message : "Failed to create directive");
- return null;
- }
- },
- [fetchDirectives]
- );
-
- const editDirective = useCallback(
- async (
- id: string,
- data: UpdateDirectiveRequest
- ): Promise<DirectiveSummary | null> => {
- setError(null);
- try {
- const directive = await updateDirective(id, data);
- await fetchDirectives();
- return directive as unknown as DirectiveSummary;
- } catch (e) {
- setError(e instanceof Error ? e.message : "Failed to update directive");
- return null;
- }
- },
- [fetchDirectives]
- );
-
- const removeDirective = useCallback(
- async (id: string): Promise<boolean> => {
- setError(null);
- try {
- await deleteDirective(id);
- await fetchDirectives();
- return true;
- } catch (e) {
- setError(e instanceof Error ? e.message : "Failed to delete directive");
- return false;
- }
- },
- [fetchDirectives]
- );
-
- const startDirective = useCallback(
- async (id: string): Promise<boolean> => {
- setError(null);
- try {
- await startDirectiveApi(id);
- await fetchDirectives();
- return true;
- } catch (e) {
- setError(
- e instanceof Error ? e.message : "Failed to start directive"
- );
- return false;
- }
- },
- [fetchDirectives]
- );
-
- // Initial fetch
- useEffect(() => {
- fetchDirectives();
- }, [fetchDirectives]);
-
- return {
- directives,
- loading,
- error,
- fetchDirectives,
- fetchDirective,
- saveDirective,
- editDirective,
- removeDirective,
- startDirective,
- };
-}
diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts
index 5080ee1..7732725 100644
--- a/makima/frontend/src/lib/api.ts
+++ b/makima/frontend/src/lib/api.ts
@@ -3003,217 +3003,4 @@ export async function listTaskPatches(taskId: string, contractId: string): Promi
return res.json();
}
-// =============================================================================
-// Directive Types & API
-// =============================================================================
-
-export type DirectiveStatus = "draft" | "planning" | "active" | "paused" | "completed" | "archived" | "failed";
-export type AutonomyLevel = "full_auto" | "guardrails" | "manual";
-
-export interface DirectiveSummary {
- id: string;
- title: string;
- goal: string;
- status: DirectiveStatus;
- autonomyLevel: AutonomyLevel;
- chainCount: number;
- stepCount: number;
- totalCostUsd: number;
- version: number;
- createdAt: string;
- updatedAt: string;
-}
-
-export interface Directive {
- id: string;
- ownerId: string;
- title: string;
- goal: string;
- requirements: unknown[];
- acceptanceCriteria: unknown[];
- constraints: unknown[];
- externalDependencies: unknown[];
- status: DirectiveStatus;
- autonomyLevel: AutonomyLevel;
- confidenceThresholdGreen: number;
- confidenceThresholdYellow: number;
- maxTotalCostUsd: number | null;
- maxWallTimeMinutes: number | null;
- maxReworkCycles: number | null;
- maxChainRegenerations: number | null;
- repositoryUrl: string | null;
- localPath: string | null;
- baseBranch: string | null;
- orchestratorContractId: string | null;
- currentChainId: string | null;
- chainGenerationCount: number;
- totalCostUsd: number;
- startedAt: string | null;
- completedAt: string | null;
- version: number;
- createdAt: string;
- updatedAt: string;
-}
-
-export interface DirectiveChain {
- id: string;
- directiveId: string;
- generation: number;
- name: string;
- description: string | null;
- rationale: string | null;
- planningModel: string | null;
- status: string;
- totalSteps: number;
- completedSteps: number;
- failedSteps: number;
- currentConfidence: number | null;
- startedAt: string | null;
- completedAt: string | null;
- version: number;
- createdAt: string;
- updatedAt: string;
-}
-
-export interface StepContractSummary {
- id: string;
- name: string;
- contractType: string;
- phase: string;
- status: string;
- taskCount: number;
- tasksDone: number;
- tasksRunning: number;
- tasksFailed: number;
-}
-
-export interface ChainStep {
- id: string;
- chainId: string;
- name: string;
- description: string | null;
- stepType: string;
- contractType: string;
- initialPhase: string | null;
- taskPlan: string | null;
- dependsOn: string[] | null;
- status: string;
- contractId: string | null;
- supervisorTaskId: string | null;
- monitoringContractId: string | null;
- monitoringTaskId: string | null;
- confidenceScore: number | null;
- confidenceLevel: string | null;
- evaluationCount: number;
- reworkCount: number;
- lastEvaluationId: string | null;
- orderIndex: number;
- startedAt: string | null;
- completedAt: string | null;
- createdAt: string;
- contractSummary: StepContractSummary | null;
-}
-
-export interface ChainWithSteps extends DirectiveChain {
- steps: ChainStep[];
-}
-
-export interface DirectiveWithChains extends Directive {
- orchestratorContractSummary: StepContractSummary | null;
- chains: ChainWithSteps[];
-}
-
-export interface DirectiveListResponse {
- directives: DirectiveSummary[];
- total: number;
-}
-
-export interface CreateDirectiveRequest {
- title: string;
- goal: string;
- requirements?: unknown[];
- acceptanceCriteria?: unknown[];
- constraints?: unknown[];
- externalDependencies?: unknown[];
- autonomyLevel?: AutonomyLevel;
- repositoryUrl?: string;
- localPath?: string;
- baseBranch?: string;
-}
-
-export interface UpdateDirectiveRequest {
- title?: string;
- goal?: string;
- status?: DirectiveStatus;
- autonomyLevel?: AutonomyLevel;
- version?: number;
-}
-
-export async function listDirectives(): Promise<DirectiveListResponse> {
- const res = await authFetch(`${API_BASE}/api/v1/directives`);
- if (!res.ok) {
- throw new Error(`Failed to list directives: ${res.statusText}`);
- }
- return res.json();
-}
-
-export async function getDirective(id: string): Promise<DirectiveWithChains> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${id}`);
- if (!res.ok) {
- throw new Error(`Failed to get directive: ${res.statusText}`);
- }
- return res.json();
-}
-
-export async function createDirective(
- data: CreateDirectiveRequest
-): Promise<Directive> {
- const res = await authFetch(`${API_BASE}/api/v1/directives`, {
- method: "POST",
- body: JSON.stringify(data),
- });
- if (!res.ok) {
- throw new Error(`Failed to create directive: ${res.statusText}`);
- }
- return res.json();
-}
-
-export async function updateDirective(
- id: string,
- data: UpdateDirectiveRequest
-): Promise<Directive> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${id}`, {
- method: "PUT",
- body: JSON.stringify(data),
- });
- if (res.status === 409) {
- const conflict = (await res.json()) as ConflictErrorResponse;
- throw new VersionConflictError(conflict);
- }
- if (!res.ok) {
- throw new Error(`Failed to update directive: ${res.statusText}`);
- }
- return res.json();
-}
-
-export async function deleteDirective(id: string): Promise<void> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${id}`, {
- method: "DELETE",
- });
- if (!res.ok) {
- throw new Error(`Failed to delete directive: ${res.statusText}`);
- }
-}
-
-export async function startDirective(id: string): Promise<Directive> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${id}/start`, {
- method: "POST",
- });
- if (!res.ok) {
- const body = await res.json().catch(() => null);
- const msg = body?.message || res.statusText;
- throw new Error(`Failed to start directive: ${msg}`);
- }
- return res.json();
-}
diff --git a/makima/frontend/src/main.tsx b/makima/frontend/src/main.tsx
index f07a143..50fffe4 100644
--- a/makima/frontend/src/main.tsx
+++ b/makima/frontend/src/main.tsx
@@ -18,7 +18,6 @@ import HistoryPage from "./routes/history";
import LoginPage from "./routes/login";
import SettingsPage from "./routes/settings";
import ContractFilePage from "./routes/contract-file";
-import DirectivesPage from "./routes/directives";
import SpeakPage from "./routes/speak";
createRoot(document.getElementById("root")!).render(
@@ -81,22 +80,6 @@ createRoot(document.getElementById("root")!).render(
}
/>
<Route
- path="/directives"
- element={
- <ProtectedRoute>
- <DirectivesPage />
- </ProtectedRoute>
- }
- />
- <Route
- path="/directives/:id"
- element={
- <ProtectedRoute>
- <DirectivesPage />
- </ProtectedRoute>
- }
- />
- <Route
path="/workflow"
element={
<ProtectedRoute>
diff --git a/makima/frontend/src/routes/directives.tsx b/makima/frontend/src/routes/directives.tsx
deleted file mode 100644
index fd3808b..0000000
--- a/makima/frontend/src/routes/directives.tsx
+++ /dev/null
@@ -1,403 +0,0 @@
-import { useState, useCallback, useEffect } from "react";
-import { useParams, useNavigate } from "react-router";
-import { Masthead } from "../components/Masthead";
-import { DirectiveList } from "../components/directives/DirectiveList";
-import { DirectiveDetail } from "../components/directives/DirectiveDetail";
-import { DirectoryInput } from "../components/mesh/DirectoryInput";
-import { useDirectives } from "../hooks/useDirectives";
-import { useAuth } from "../contexts/AuthContext";
-import {
- getDaemonDirectories,
- getRepositorySuggestions,
-} from "../lib/api";
-import type {
- DirectiveWithChains,
- CreateDirectiveRequest,
- RepositorySourceType,
- DaemonDirectory,
- RepositoryHistoryEntry,
-} from "../lib/api";
-
-export default function DirectivesPage() {
- const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth();
- const navigate = useNavigate();
-
- // Redirect to login if not authenticated (when auth is configured)
- useEffect(() => {
- if (!authLoading && isAuthConfigured && !isAuthenticated) {
- navigate("/login");
- }
- }, [authLoading, isAuthConfigured, isAuthenticated, navigate]);
-
- if (authLoading) {
- return (
- <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]">
- <Masthead showNav />
- <main className="flex-1 flex items-center justify-center">
- <p className="text-[#7788aa] font-mono text-sm">Loading...</p>
- </main>
- </div>
- );
- }
-
- if (isAuthConfigured && !isAuthenticated) {
- return null;
- }
-
- return <DirectivesContent />;
-}
-
-function DirectivesContent() {
- const { id } = useParams<{ id?: string }>();
- const navigate = useNavigate();
- const {
- directives,
- loading,
- error,
- fetchDirective,
- saveDirective,
- removeDirective,
- startDirective,
- } = useDirectives();
-
- const [selectedDirective, setSelectedDirective] =
- useState<DirectiveWithChains | null>(null);
- const [detailLoading, setDetailLoading] = useState(false);
- const [showCreateForm, setShowCreateForm] = useState(false);
- const [createTitle, setCreateTitle] = useState("");
- const [createGoal, setCreateGoal] = useState("");
-
- // Repository state
- const [repoType, setRepoType] = useState<RepositorySourceType>("remote");
- const [repoUrl, setRepoUrl] = useState("");
- const [repoPath, setRepoPath] = useState("");
- const [suggestedDirectories, setSuggestedDirectories] = useState<DaemonDirectory[]>([]);
- const [repoSuggestions, setRepoSuggestions] = useState<RepositoryHistoryEntry[]>([]);
- const [showRepoSuggestions, setShowRepoSuggestions] = useState(false);
-
- // Fetch repository suggestions when modal opens and repo type changes
- useEffect(() => {
- if (showCreateForm && (repoType === "remote" || repoType === "local")) {
- getRepositorySuggestions(repoType, undefined, 10)
- .then((res) => {
- setRepoSuggestions(res.entries);
- setShowRepoSuggestions(res.entries.length > 0);
- })
- .catch(() => {
- setRepoSuggestions([]);
- setShowRepoSuggestions(false);
- });
- } else {
- setRepoSuggestions([]);
- setShowRepoSuggestions(false);
- }
- }, [showCreateForm, repoType]);
-
- // Fetch daemon directories when "local" repo type is selected
- useEffect(() => {
- if (repoType === "local" && showCreateForm) {
- getDaemonDirectories()
- .then((res) => setSuggestedDirectories(res.directories))
- .catch(() => setSuggestedDirectories([]));
- }
- }, [repoType, showCreateForm]);
-
- // Apply a repository suggestion
- const applyRepoSuggestion = useCallback((suggestion: RepositoryHistoryEntry) => {
- if (suggestion.repositoryUrl) {
- setRepoUrl(suggestion.repositoryUrl);
- }
- if (suggestion.localPath) {
- setRepoPath(suggestion.localPath);
- }
- setShowRepoSuggestions(false);
- }, []);
-
- // Load directive when ID changes
- useEffect(() => {
- if (id) {
- setDetailLoading(true);
- fetchDirective(id).then((d) => {
- setSelectedDirective(d);
- setDetailLoading(false);
- });
- } else {
- setSelectedDirective(null);
- }
- }, [id, fetchDirective]);
-
- const handleSelect = useCallback(
- (directiveId: string) => {
- navigate(`/directives/${directiveId}`);
- },
- [navigate]
- );
-
- const handleBack = useCallback(() => {
- navigate("/directives");
- }, [navigate]);
-
- const resetCreateForm = useCallback(() => {
- setShowCreateForm(false);
- setCreateTitle("");
- setCreateGoal("");
- setRepoType("remote");
- setRepoUrl("");
- setRepoPath("");
- }, []);
-
- const handleCreate = useCallback(async () => {
- if (!createTitle.trim() || !createGoal.trim()) return;
-
- const data: CreateDirectiveRequest = {
- title: createTitle.trim(),
- goal: createGoal.trim(),
- };
- if (repoType === "remote" && repoUrl.trim()) {
- data.repositoryUrl = repoUrl.trim();
- } else if (repoType === "local" && repoPath.trim()) {
- data.localPath = repoPath.trim();
- }
-
- const result = await saveDirective(data);
- if (result) {
- resetCreateForm();
- }
- }, [createTitle, createGoal, repoType, repoUrl, repoPath, saveDirective, resetCreateForm]);
-
- const handleDelete = useCallback(
- async (directiveId: string) => {
- const ok = await removeDirective(directiveId);
- if (ok && id === directiveId) {
- navigate("/directives");
- }
- },
- [removeDirective, id, navigate]
- );
-
- const handleStart = useCallback(
- async (directiveId: string) => {
- const ok = await startDirective(directiveId);
- if (ok) {
- const updated = await fetchDirective(directiveId);
- if (updated) {
- setSelectedDirective(updated);
- }
- }
- },
- [startDirective, fetchDirective]
- );
-
- const handleRefresh = useCallback(
- (updated: DirectiveWithChains) => {
- setSelectedDirective(updated);
- },
- []
- );
-
- return (
- <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]">
- <Masthead showNav />
- <main className="flex-1 flex flex-col p-4 pt-2 gap-4 overflow-hidden">
- {error && (
- <div className="p-3 bg-red-400/10 border border-red-400/30 text-red-400 font-mono text-sm">
- {error}
- </div>
- )}
-
- {/* Create directive modal */}
- {showCreateForm && (
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
- <div className="w-full max-w-lg max-h-[90vh] overflow-y-auto p-6 bg-[#0a1628] border border-[rgba(117,170,252,0.3)]">
- <h3 className="font-mono text-sm text-[#75aafc] uppercase mb-4">
- New Directive
- </h3>
- <div className="space-y-4">
- {/* Title */}
- <div>
- <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
- Title
- </label>
- <input
- type="text"
- placeholder="Directive title"
- value={createTitle}
- onChange={(e) => setCreateTitle(e.target.value)}
- className="w-full px-3 py-2 font-mono text-sm text-[#dbe7ff] bg-[#0d1b2d] border border-[#3f6fb3] focus:border-[#75aafc] outline-none"
- autoFocus
- />
- </div>
-
- {/* Goal */}
- <div>
- <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
- Goal
- </label>
- <textarea
- placeholder="What should be accomplished?"
- value={createGoal}
- onChange={(e) => setCreateGoal(e.target.value)}
- rows={3}
- className="w-full px-3 py-2 font-mono text-sm text-[#dbe7ff] bg-[#0d1b2d] border border-[#3f6fb3] focus:border-[#75aafc] outline-none resize-none"
- />
- </div>
-
- {/* Repository Configuration */}
- <div className="border-t border-[rgba(117,170,252,0.2)] pt-4">
- <label className="block font-mono text-xs text-[#75aafc] uppercase mb-3">
- Repository (optional)
- </label>
-
- {/* Repository type selector */}
- <div className="flex gap-2 mb-3">
- <button
- type="button"
- onClick={() => setRepoType("remote")}
- className={`flex-1 px-3 py-2 font-mono text-xs uppercase transition-colors ${
- repoType === "remote"
- ? "bg-[#0f3c78] text-[#dbe7ff] border border-[#75aafc]"
- : "bg-[#0d1b2d] text-[#8b949e] border border-[#3f6fb3] hover:border-[#75aafc]"
- }`}
- >
- Remote
- </button>
- <button
- type="button"
- onClick={() => setRepoType("local")}
- className={`flex-1 px-3 py-2 font-mono text-xs uppercase transition-colors ${
- repoType === "local"
- ? "bg-[#0f3c78] text-[#dbe7ff] border border-[#75aafc]"
- : "bg-[#0d1b2d] text-[#8b949e] border border-[#3f6fb3] hover:border-[#75aafc]"
- }`}
- >
- Local
- </button>
- </div>
-
- {/* Repository suggestions */}
- {showRepoSuggestions && repoSuggestions.length > 0 && (
- <div className="mb-3">
- <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
- Recent Repositories
- </label>
- <div className="border border-[rgba(117,170,252,0.2)] bg-[#0a1525] max-h-32 overflow-y-auto">
- {repoSuggestions.map((suggestion) => (
- <button
- key={suggestion.id}
- type="button"
- onClick={() => applyRepoSuggestion(suggestion)}
- className="w-full text-left px-3 py-2 font-mono text-xs hover:bg-[rgba(117,170,252,0.1)] border-b border-[rgba(117,170,252,0.1)] last:border-b-0"
- >
- <div className="flex items-center justify-between">
- <span className="text-[#9bc3ff] truncate">{suggestion.name}</span>
- <span className="text-[10px] text-[#556677] ml-2">
- {suggestion.useCount}&times;
- </span>
- </div>
- <div className="text-[10px] text-[#556677] truncate">
- {repoType === "local" ? suggestion.localPath : suggestion.repositoryUrl}
- </div>
- </button>
- ))}
- </div>
- </div>
- )}
-
- {/* Repository URL (for remote) */}
- {repoType === "remote" && (
- <div>
- <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
- Repository URL
- </label>
- <input
- type="text"
- value={repoUrl}
- onChange={(e) => setRepoUrl(e.target.value)}
- placeholder="https://github.com/user/repo.git"
- className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]"
- />
- </div>
- )}
-
- {/* Repository path (for local) */}
- {repoType === "local" && (
- <div>
- <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
- Local Path
- </label>
- <DirectoryInput
- value={repoPath}
- onChange={setRepoPath}
- suggestions={suggestedDirectories}
- placeholder="/path/to/repository"
- repoUrl={repoUrl || null}
- />
- </div>
- )}
- </div>
-
- {/* Actions */}
- <div className="flex gap-2 justify-end pt-2">
- <button
- onClick={resetCreateForm}
- className="px-4 py-2 font-mono text-xs text-[#9bc3ff] hover:text-[#dbe7ff] transition-colors"
- >
- Cancel
- </button>
- <button
- onClick={handleCreate}
- disabled={!createTitle.trim() || !createGoal.trim()}
- className="px-4 py-2 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
- >
- Create
- </button>
- </div>
- </div>
- </div>
- </div>
- )}
-
- <div className="flex-1 grid grid-cols-[350px_1fr] gap-4 min-h-0">
- {/* Directive list */}
- <DirectiveList
- directives={directives}
- loading={loading}
- onSelect={handleSelect}
- onCreate={() => setShowCreateForm(true)}
- onDelete={(d) => handleDelete(d.id)}
- selectedId={id}
- />
-
- {/* Directive detail or empty state */}
- {detailLoading ? (
- <div className="panel h-full flex items-center justify-center">
- <p className="text-[#7788aa] font-mono text-sm">Loading directive...</p>
- </div>
- ) : selectedDirective ? (
- <DirectiveDetail
- directive={selectedDirective}
- onBack={handleBack}
- onDelete={handleDelete}
- onStart={handleStart}
- onRefresh={handleRefresh}
- />
- ) : (
- <div className="panel h-full flex items-center justify-center">
- <div className="text-center">
- <p className="font-mono text-sm text-[#555] mb-4">
- Select a directive or create a new one
- </p>
- <button
- onClick={() => setShowCreateForm(true)}
- className="px-4 py-2 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase"
- >
- + New Directive
- </button>
- </div>
- </div>
- )}
- </div>
- </main>
- </div>
- );
-}
diff --git a/makima/migrations/20260209000000_remove_directive_system.sql b/makima/migrations/20260209000000_remove_directive_system.sql
new file mode 100644
index 0000000..1cb49dc
--- /dev/null
+++ b/makima/migrations/20260209000000_remove_directive_system.sql
@@ -0,0 +1,18 @@
+-- ============================================================================
+-- Migration: remove_directive_system.sql
+-- Removes the entire directive/chain system.
+-- ============================================================================
+
+-- Drop directive tables in reverse dependency order
+DROP TABLE IF EXISTS directive_approvals CASCADE;
+DROP TABLE IF EXISTS directive_verifiers CASCADE;
+DROP TABLE IF EXISTS directive_events CASCADE;
+DROP TABLE IF EXISTS directive_evaluations CASCADE;
+DROP TABLE IF EXISTS chain_steps CASCADE;
+DROP TABLE IF EXISTS directive_chains CASCADE;
+DROP TABLE IF EXISTS directives CASCADE;
+
+-- Remove directive-related columns from contracts
+ALTER TABLE contracts DROP COLUMN IF EXISTS directive_id;
+ALTER TABLE contracts DROP COLUMN IF EXISTS is_directive_orchestrator;
+ALTER TABLE contracts DROP COLUMN IF EXISTS spawned_directive_id;
diff --git a/makima/src/bin/makima.rs b/makima/src/bin/makima.rs
index 8115387..ee5895c 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,
- DirectiveCommand, SupervisorCommand, ViewArgs,
+ SupervisorCommand, ViewArgs,
};
use makima::daemon::tui::{self, Action, App, ListItem, ViewType, TuiWsClient, WsEvent, OutputLine, OutputMessageType, WsConnectionState, RepositorySuggestion};
use makima::daemon::config::{DaemonConfig, RepoEntry};
@@ -29,7 +29,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Commands::Daemon(args) => run_daemon(args).await,
Commands::Supervisor(cmd) => run_supervisor(cmd).await,
Commands::Contract(cmd) => run_contract(cmd).await,
- Commands::Directive(cmd) => run_directive(cmd).await,
Commands::View(args) => run_view(args).await,
Commands::Config(cmd) => run_config(cmd).await,
}
@@ -712,96 +711,6 @@ async fn run_contract(
Ok(())
}
-/// Run a directive subcommand.
-async fn run_directive(
- cmd: DirectiveCommand,
-) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
- match cmd {
- DirectiveCommand::Status(args) => {
- let client = ApiClient::new(args.api_url, args.api_key)?;
- let result = client.directive_status(args.directive_id).await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- DirectiveCommand::Goals(args) => {
- let client = ApiClient::new(args.api_url, args.api_key)?;
- let result = client.directive_status(args.directive_id).await?;
- // Extract goal-related fields from directive
- let directive = &result.0;
- let goals = serde_json::json!({
- "goal": directive.get("goal"),
- "requirements": directive.get("requirements"),
- "acceptanceCriteria": directive.get("acceptanceCriteria"),
- "constraints": directive.get("constraints"),
- "externalDependencies": directive.get("externalDependencies"),
- });
- println!("{}", serde_json::to_string(&goals)?);
- }
- DirectiveCommand::Chains(args) => {
- let client = ApiClient::new(args.api_url, args.api_key)?;
- let result = client.directive_chains(args.directive_id).await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- DirectiveCommand::Chain(args) => {
- let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
- let result = client
- .directive_chain(args.common.directive_id, args.chain_id)
- .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
- .directive_chain(args.common.directive_id, args.chain_id)
- .await?;
- // Extract steps from chain response
- let steps = result.0.get("steps").cloned().unwrap_or(serde_json::json!([]));
- println!("{}", serde_json::to_string(&steps)?);
- }
- DirectiveCommand::UpdateStatus(args) => {
- let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
- let req = makima::daemon::api::directive::UpdateDirectiveRequest {
- status: Some(args.status),
- version: None,
- };
- let result = client
- .directive_update(args.common.directive_id, req)
- .await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- DirectiveCommand::Start(args) => {
- let client = ApiClient::new(args.api_url, args.api_key)?;
- let result = client.directive_start(args.directive_id).await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- DirectiveCommand::Evaluate(args) => {
- let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
- let result = client
- .directive_evaluate_step(args.common.directive_id, args.step_id)
- .await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- DirectiveCommand::Evaluations(args) => {
- let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
- let result = client
- .directive_evaluations(args.common.directive_id, args.step_id)
- .await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- DirectiveCommand::SubmitPlan(args) => {
- let client = ApiClient::new(args.api_url, args.api_key)?;
- // Read plan JSON from stdin
- let mut plan_json = String::new();
- io::stdin().read_to_string(&mut plan_json)?;
- let result = client
- .directive_submit_plan(args.directive_id, &plan_json)
- .await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- }
-
- Ok(())
-}
-
/// Run the TUI view command.
async fn run_view(args: ViewArgs) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Load CLI config for defaults
diff --git a/makima/src/daemon/api/directive.rs b/makima/src/daemon/api/directive.rs
deleted file mode 100644
index c51882b..0000000
--- a/makima/src/daemon/api/directive.rs
+++ /dev/null
@@ -1,106 +0,0 @@
-//! Directive API methods.
-
-use serde::Serialize;
-use uuid::Uuid;
-
-use super::client::{ApiClient, ApiError};
-use super::supervisor::JsonValue;
-
-/// Request to update a directive.
-#[derive(Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct UpdateDirectiveRequest {
- #[serde(skip_serializing_if = "Option::is_none")]
- pub status: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub version: Option<i32>,
-}
-
-impl ApiClient {
- /// Get directive status and details.
- pub async fn directive_status(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> {
- self.get(&format!("/api/v1/directives/{}", directive_id))
- .await
- }
-
- /// List chains for a directive.
- pub async fn directive_chains(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> {
- self.get(&format!("/api/v1/directives/{}/chains", directive_id))
- .await
- }
-
- /// Get a chain with its steps.
- pub async fn directive_chain(
- &self,
- directive_id: Uuid,
- chain_id: Uuid,
- ) -> Result<JsonValue, ApiError> {
- self.get(&format!(
- "/api/v1/directives/{}/chains/{}",
- directive_id, chain_id
- ))
- .await
- }
-
- /// Update a directive.
- pub async fn directive_update(
- &self,
- directive_id: Uuid,
- req: UpdateDirectiveRequest,
- ) -> Result<JsonValue, ApiError> {
- self.put(&format!("/api/v1/directives/{}", directive_id), &req)
- .await
- }
-
- /// Start a directive (transition from draft to planning).
- pub async fn directive_start(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> {
- self.post_empty(&format!("/api/v1/directives/{}/start", directive_id))
- .await
- }
-
- /// Trigger a manual evaluation for a step.
- pub async fn directive_evaluate_step(
- &self,
- directive_id: Uuid,
- step_id: Uuid,
- ) -> Result<JsonValue, ApiError> {
- self.post_empty(&format!(
- "/api/v1/directives/{}/steps/{}/evaluate",
- directive_id, step_id
- ))
- .await
- }
-
- /// Submit a chain plan for a directive.
- pub async fn directive_submit_plan(
- &self,
- directive_id: Uuid,
- plan_json: &str,
- ) -> Result<JsonValue, ApiError> {
- #[derive(serde::Serialize)]
- #[serde(rename_all = "camelCase")]
- struct SubmitPlanBody {
- plan: String,
- }
- self.post(
- &format!("/api/v1/directives/{}/submit-plan", directive_id),
- &SubmitPlanBody {
- plan: plan_json.to_string(),
- },
- )
- .await
- }
-
- /// List evaluations for a step.
- pub async fn directive_evaluations(
- &self,
- directive_id: Uuid,
- step_id: Uuid,
- ) -> Result<JsonValue, ApiError> {
- self.get(&format!(
- "/api/v1/directives/{}/steps/{}/evaluations",
- directive_id, step_id
- ))
- .await
- }
-}
diff --git a/makima/src/daemon/api/mod.rs b/makima/src/daemon/api/mod.rs
index 2d1efbf..49d80e0 100644
--- a/makima/src/daemon/api/mod.rs
+++ b/makima/src/daemon/api/mod.rs
@@ -2,7 +2,6 @@
pub mod client;
pub mod contract;
-pub mod directive;
pub mod supervisor;
pub use client::ApiClient;
diff --git a/makima/src/daemon/cli/directive.rs b/makima/src/daemon/cli/directive.rs
deleted file mode 100644
index 4c29c14..0000000
--- a/makima/src/daemon/cli/directive.rs
+++ /dev/null
@@ -1,60 +0,0 @@
-//! Directive subcommand - directive orchestration commands.
-
-use clap::Args;
-use uuid::Uuid;
-
-/// Common arguments for directive commands.
-#[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,
-
- /// Directive ID
- #[arg(long, env = "MAKIMA_DIRECTIVE_ID", global = true)]
- pub directive_id: Uuid,
-}
-
-/// Arguments for chain command (get specific chain).
-#[derive(Args, Debug)]
-pub struct ChainArgs {
- #[command(flatten)]
- pub common: DirectiveArgs,
-
- /// Chain ID to retrieve
- pub chain_id: Uuid,
-}
-
-/// Arguments for update-status command.
-#[derive(Args, Debug)]
-pub struct UpdateStatusArgs {
- #[command(flatten)]
- pub common: DirectiveArgs,
-
- /// New status (draft, planning, active, paused, completed, archived, failed)
- pub status: String,
-}
-
-/// Arguments for evaluate command (trigger manual evaluation).
-#[derive(Args, Debug)]
-pub struct EvaluateArgs {
- #[command(flatten)]
- pub common: DirectiveArgs,
-
- /// Step ID to evaluate
- pub step_id: Uuid,
-}
-
-/// Arguments for evaluations command (list evaluation history).
-#[derive(Args, Debug)]
-pub struct EvaluationsArgs {
- #[command(flatten)]
- pub common: DirectiveArgs,
-
- /// Step ID to list evaluations for
- pub step_id: Uuid,
-}
diff --git a/makima/src/daemon/cli/mod.rs b/makima/src/daemon/cli/mod.rs
index 954f219..0805edd 100644
--- a/makima/src/daemon/cli/mod.rs
+++ b/makima/src/daemon/cli/mod.rs
@@ -3,7 +3,6 @@
pub mod config;
pub mod contract;
pub mod daemon;
-pub mod directive;
pub mod server;
pub mod supervisor;
pub mod view;
@@ -13,7 +12,6 @@ use clap::{Parser, Subcommand};
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;
@@ -43,10 +41,6 @@ pub enum Commands {
#[command(subcommand)]
Contract(ContractCommand),
- /// Directive commands for autonomous goal-driven execution
- #[command(subcommand)]
- Directive(DirectiveCommand),
-
/// Interactive TUI browser for contracts and tasks
///
/// Provides a drill-down interface for browsing contracts, viewing their
@@ -202,40 +196,6 @@ pub enum ContractCommand {
CreateFile(contract::CreateFileArgs),
}
-/// Directive subcommands for autonomous goal-driven execution.
-#[derive(Subcommand, Debug)]
-pub enum DirectiveCommand {
- /// Get directive status and details
- Status(DirectiveArgs),
-
- /// Get goal, requirements, acceptance criteria
- Goals(DirectiveArgs),
-
- /// List chains for the directive
- Chains(DirectiveArgs),
-
- /// Get a chain with its steps
- Chain(directive::ChainArgs),
-
- /// List steps in a chain
- Steps(directive::ChainArgs),
-
- /// Update directive status
- UpdateStatus(directive::UpdateStatusArgs),
-
- /// Start a directive (create planning contract and begin orchestration)
- Start(DirectiveArgs),
-
- /// Trigger a manual evaluation for a step
- Evaluate(directive::EvaluateArgs),
-
- /// List evaluation history for a step
- Evaluations(directive::EvaluationsArgs),
-
- /// Submit a chain plan for a directive (reads JSON from stdin)
- SubmitPlan(DirectiveArgs),
-}
-
impl Cli {
/// Parse command-line arguments
pub fn parse_args() -> Self {
diff --git a/makima/src/daemon/skills/directive.md b/makima/src/daemon/skills/directive.md
deleted file mode 100644
index 0d1e9d6..0000000
--- a/makima/src/daemon/skills/directive.md
+++ /dev/null
@@ -1,88 +0,0 @@
----
-name: makima-directive
-description: Directive orchestration tools for autonomous goal-driven execution. Use when working with directives, chains, steps, verifiers, and approvals.
----
-
-# Makima Directive Commands
-
-These commands let orchestrators interact with directive state. Environment variables (`MAKIMA_API_URL`, `MAKIMA_API_KEY`, `MAKIMA_DIRECTIVE_ID`) are pre-configured by the daemon.
-
-## Status and Information
-
-### Get directive status
-```bash
-makima directive status
-```
-Returns full directive details including status, autonomy level, thresholds, and tracking info.
-
-### Get directive goals
-```bash
-makima directive goals
-```
-Returns the goal, requirements, acceptance criteria, constraints, and external dependencies.
-
-### List chains
-```bash
-makima directive chains
-```
-Returns all chains (plan generations) for the directive, ordered by generation.
-
-### Get chain with steps
-```bash
-makima directive chain <chain_id>
-```
-Returns a chain and all its steps with status, dependencies, and evaluation info.
-
-### List steps in a chain
-```bash
-makima directive steps <chain_id>
-```
-Returns just the steps array from a chain.
-
-## Status Updates
-
-### Update directive status
-```bash
-makima directive update-status <status>
-```
-Updates the directive status. Valid statuses: `draft`, `planning`, `active`, `paused`, `completed`, `archived`, `failed`.
-
-## Evaluation
-
-### Trigger manual evaluation for a step
-```bash
-makima directive evaluate <step_id>
-```
-Triggers a monitoring evaluation for the specified step. The step must have been executed (have a contract). Sets the step to "evaluating" and dispatches a monitoring contract.
-
-### List evaluations for a step
-```bash
-makima directive evaluations <step_id>
-```
-Returns the evaluation history for a step, ordered by evaluation number.
-
-## Output Format
-
-All commands output JSON to stdout.
-
-Example workflow:
-```bash
-# Check directive details and goals
-makima directive status
-makima directive goals
-
-# List execution chains
-makima directive chains
-
-# Get details of a specific chain
-makima directive chain <chain_id>
-
-# Trigger manual evaluation of a step
-makima directive evaluate <step_id>
-
-# Check evaluation history
-makima directive evaluations <step_id>
-
-# Update status to active
-makima directive update-status active
-```
diff --git a/makima/src/daemon/skills/mod.rs b/makima/src/daemon/skills/mod.rs
index 6e5d0a8..0b05f3a 100644
--- a/makima/src/daemon/skills/mod.rs
+++ b/makima/src/daemon/skills/mod.rs
@@ -9,12 +9,8 @@ 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");
-/// Directive skill content - directive orchestration commands
-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-directive", DIRECTIVE_SKILL),
];
diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs
index 6045c7d..d0a0bd6 100644
--- a/makima/src/db/models.rs
+++ b/makima/src/db/models.rs
@@ -1446,16 +1446,6 @@ pub struct Contract {
/// Use `get_phase_config()` to get the parsed PhaseConfig.
#[serde(skip_serializing_if = "Option::is_none")]
pub phase_config: Option<serde_json::Value>,
- /// Directive ID if this contract is part of a directive's chain
- #[serde(skip_serializing_if = "Option::is_none")]
- pub directive_id: Option<Uuid>,
- /// Whether this contract is a directive orchestrator
- #[serde(default)]
- #[sqlx(default)]
- pub is_directive_orchestrator: bool,
- /// Reference to directive spawned by this orchestrator contract
- #[serde(skip_serializing_if = "Option::is_none")]
- pub spawned_directive_id: Option<Uuid>,
pub version: i32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
@@ -2692,326 +2682,3 @@ mod tests {
}
// =============================================================================
-// Directive Types
-// =============================================================================
-
-/// Default autonomy level for directives
-fn default_autonomy_level() -> String {
- "guardrails".to_string()
-}
-
-/// Default empty JSON array
-fn default_json_array() -> serde_json::Value {
- serde_json::json!([])
-}
-
-/// Default empty JSON object
-fn default_json_object() -> serde_json::Value {
- serde_json::json!({})
-}
-
-/// Default confidence threshold (green)
-fn default_confidence_green() -> f64 {
- 0.85
-}
-
-/// Default confidence threshold (yellow)
-fn default_confidence_yellow() -> f64 {
- 0.60
-}
-
-/// Default max rework cycles
-fn default_max_rework_cycles() -> Option<i32> {
- Some(3)
-}
-
-/// Default max chain regenerations
-fn default_max_chain_regenerations() -> Option<i32> {
- Some(2)
-}
-
-/// Full directive row from the database.
-#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct Directive {
- pub id: Uuid,
- pub owner_id: Uuid,
- pub title: String,
- pub goal: String,
- #[sqlx(json)]
- pub requirements: serde_json::Value,
- #[sqlx(json)]
- pub acceptance_criteria: serde_json::Value,
- #[sqlx(json)]
- pub constraints: serde_json::Value,
- #[sqlx(json)]
- pub external_dependencies: serde_json::Value,
- pub status: String,
- pub autonomy_level: String,
- pub confidence_threshold_green: f64,
- pub confidence_threshold_yellow: f64,
- pub max_total_cost_usd: Option<f64>,
- pub max_wall_time_minutes: Option<i32>,
- pub max_rework_cycles: Option<i32>,
- pub max_chain_regenerations: Option<i32>,
- pub repository_url: Option<String>,
- pub local_path: Option<String>,
- pub base_branch: Option<String>,
- pub orchestrator_contract_id: Option<Uuid>,
- pub current_chain_id: Option<Uuid>,
- pub chain_generation_count: i32,
- pub total_cost_usd: f64,
- pub started_at: Option<DateTime<Utc>>,
- pub completed_at: Option<DateTime<Utc>>,
- pub version: i32,
- pub created_at: DateTime<Utc>,
- pub updated_at: DateTime<Utc>,
-}
-
-/// Summary of a directive for list views.
-#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct DirectiveSummary {
- pub id: Uuid,
- pub title: String,
- pub goal: String,
- pub status: String,
- pub autonomy_level: String,
- pub chain_count: i64,
- pub step_count: i64,
- pub total_cost_usd: f64,
- pub version: i32,
- pub created_at: DateTime<Utc>,
- pub updated_at: DateTime<Utc>,
-}
-
-/// Response for directive list endpoint.
-#[derive(Debug, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct DirectiveListResponse {
- pub directives: Vec<DirectiveSummary>,
- pub total: i64,
-}
-
-/// Request to create a new directive.
-#[derive(Debug, Clone, Deserialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct CreateDirectiveRequest {
- pub title: String,
- pub goal: String,
- #[serde(default = "default_json_array")]
- pub requirements: serde_json::Value,
- #[serde(default = "default_json_array")]
- pub acceptance_criteria: serde_json::Value,
- #[serde(default = "default_json_array")]
- pub constraints: serde_json::Value,
- #[serde(default = "default_json_array")]
- pub external_dependencies: serde_json::Value,
- #[serde(default = "default_autonomy_level")]
- pub autonomy_level: String,
- #[serde(default = "default_confidence_green")]
- pub confidence_threshold_green: f64,
- #[serde(default = "default_confidence_yellow")]
- pub confidence_threshold_yellow: f64,
- pub max_total_cost_usd: Option<f64>,
- pub max_wall_time_minutes: Option<i32>,
- #[serde(default = "default_max_rework_cycles")]
- pub max_rework_cycles: Option<i32>,
- #[serde(default = "default_max_chain_regenerations")]
- pub max_chain_regenerations: Option<i32>,
- pub repository_url: Option<String>,
- pub local_path: Option<String>,
- pub base_branch: Option<String>,
-}
-
-/// Request to submit a chain plan for a directive.
-#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct SubmitPlanRequest {
- pub plan: String,
-}
-
-/// Request to update an existing directive.
-#[derive(Debug, Clone, Deserialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct UpdateDirectiveRequest {
- pub title: Option<String>,
- pub goal: Option<String>,
- pub requirements: Option<serde_json::Value>,
- pub acceptance_criteria: Option<serde_json::Value>,
- pub constraints: Option<serde_json::Value>,
- pub external_dependencies: Option<serde_json::Value>,
- pub status: Option<String>,
- pub autonomy_level: Option<String>,
- pub confidence_threshold_green: Option<f64>,
- pub confidence_threshold_yellow: Option<f64>,
- pub max_total_cost_usd: Option<f64>,
- pub max_wall_time_minutes: Option<i32>,
- pub max_rework_cycles: Option<i32>,
- pub max_chain_regenerations: Option<i32>,
- pub repository_url: Option<String>,
- pub local_path: Option<String>,
- pub base_branch: Option<String>,
- /// Version for optimistic locking
- pub version: Option<i32>,
-}
-
-/// Lightweight contract summary attached to a chain step.
-#[derive(Debug, FromRow, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct StepContractSummary {
- pub id: Uuid,
- pub name: String,
- pub contract_type: String,
- pub phase: String,
- pub status: String,
- pub task_count: i64,
- pub tasks_done: i64,
- pub tasks_running: i64,
- pub tasks_failed: i64,
-}
-
-/// Chain step enriched with optional contract summary.
-#[derive(Debug, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct ChainStepWithContract {
- #[serde(flatten)]
- pub step: ChainStep,
- pub contract_summary: Option<StepContractSummary>,
-}
-
-/// Directive with its chains and steps for detail view.
-#[derive(Debug, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct DirectiveWithChains {
- #[serde(flatten)]
- pub directive: Directive,
- pub orchestrator_contract_summary: Option<StepContractSummary>,
- pub chains: Vec<ChainWithSteps>,
-}
-
-/// Full row from directive_chains table.
-#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct DirectiveChain {
- pub id: Uuid,
- pub directive_id: Uuid,
- pub generation: i32,
- pub name: String,
- pub description: Option<String>,
- pub rationale: Option<String>,
- pub planning_model: Option<String>,
- pub status: String,
- pub total_steps: i32,
- pub completed_steps: i32,
- pub failed_steps: i32,
- pub current_confidence: Option<f64>,
- pub started_at: Option<DateTime<Utc>>,
- pub completed_at: Option<DateTime<Utc>>,
- pub version: i32,
- pub created_at: DateTime<Utc>,
- pub updated_at: DateTime<Utc>,
-}
-
-/// Full row from chain_steps table.
-#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct ChainStep {
- pub id: Uuid,
- pub chain_id: Uuid,
- pub name: String,
- pub description: Option<String>,
- pub step_type: String,
- pub contract_type: String,
- pub initial_phase: Option<String>,
- pub task_plan: Option<String>,
- pub phases: Option<Vec<String>>,
- pub depends_on: Option<Vec<Uuid>>,
- pub parallel_group: Option<String>,
- pub requirement_ids: Option<Vec<String>>,
- pub acceptance_criteria_ids: Option<Vec<String>>,
- #[sqlx(json)]
- pub verifier_config: serde_json::Value,
- pub status: String,
- pub contract_id: Option<Uuid>,
- pub supervisor_task_id: Option<Uuid>,
- pub monitoring_contract_id: Option<Uuid>,
- pub monitoring_task_id: Option<Uuid>,
- pub confidence_score: Option<f64>,
- pub confidence_level: Option<String>,
- pub evaluation_count: i32,
- pub rework_count: i32,
- pub last_evaluation_id: Option<Uuid>,
- pub editor_x: Option<f64>,
- pub editor_y: Option<f64>,
- pub order_index: i32,
- pub started_at: Option<DateTime<Utc>>,
- pub completed_at: Option<DateTime<Utc>>,
- pub created_at: DateTime<Utc>,
-}
-
-/// Chain with its steps for detail view.
-#[derive(Debug, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct ChainWithSteps {
- #[serde(flatten)]
- pub chain: DirectiveChain,
- pub steps: Vec<ChainStepWithContract>,
-}
-
-/// Full row from directive_evaluations table.
-#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct DirectiveEvaluation {
- pub id: Uuid,
- pub directive_id: Uuid,
- pub chain_id: Option<Uuid>,
- pub step_id: Option<Uuid>,
- pub contract_id: Option<Uuid>,
- pub evaluation_type: String,
- pub evaluation_number: i32,
- pub evaluator: Option<String>,
- pub passed: bool,
- pub overall_score: Option<f64>,
- pub confidence_level: Option<String>,
- #[sqlx(json)]
- pub programmatic_results: serde_json::Value,
- #[sqlx(json)]
- pub llm_results: serde_json::Value,
- #[sqlx(json)]
- pub criteria_results: serde_json::Value,
- pub summary_feedback: String,
- pub rework_instructions: Option<String>,
- #[sqlx(json)]
- pub directive_snapshot: Option<serde_json::Value>,
- #[sqlx(json)]
- pub deliverables_snapshot: Option<serde_json::Value>,
- pub started_at: DateTime<Utc>,
- pub completed_at: Option<DateTime<Utc>>,
- pub created_at: DateTime<Utc>,
-}
-
-/// Full row from directive_events table.
-#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct DirectiveEvent {
- pub id: Uuid,
- pub directive_id: Uuid,
- pub chain_id: Option<Uuid>,
- pub step_id: Option<Uuid>,
- pub event_type: String,
- pub severity: String,
- #[sqlx(json)]
- pub event_data: Option<serde_json::Value>,
- pub actor_type: String,
- pub actor_id: Option<Uuid>,
- pub created_at: DateTime<Utc>,
-}
-
-/// Response for evaluation list endpoint.
-#[derive(Debug, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct EvaluationListResponse {
- pub evaluations: Vec<DirectiveEvaluation>,
- pub total: i64,
-}
diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs
index 4298fa5..4ed2298 100644
--- a/makima/src/db/repository.rs
+++ b/makima/src/db/repository.rs
@@ -6,18 +6,17 @@ use sqlx::PgPool;
use uuid::Uuid;
use super::models::{
- ChainStep, CheckpointPatch, CheckpointPatchInfo, Contract, ContractChatConversation,
+ CheckpointPatch, CheckpointPatchInfo, Contract, ContractChatConversation,
ContractChatMessageRecord, ContractEvent, ContractRepository, ContractSummary,
ContractTypeTemplateRecord, ConversationMessage, ConversationSnapshot,
- CreateContractRequest, CreateDirectiveRequest, CreateFileRequest, CreateTaskRequest,
+ CreateContractRequest, CreateFileRequest, CreateTaskRequest,
CreateTemplateRequest, Daemon, DaemonTaskAssignment, DaemonWithCapacity,
- DeliverableDefinition, Directive, DirectiveChain, DirectiveEvaluation, DirectiveEvent,
- DirectiveSummary,
+ DeliverableDefinition,
File, FileSummary, FileVersion, HistoryEvent, HistoryQueryFilters,
MeshChatConversation, MeshChatMessageRecord, PhaseChangeResult, PhaseConfig,
- PhaseDefinition, StepContractSummary, SupervisorHeartbeatRecord, SupervisorState,
+ PhaseDefinition, SupervisorHeartbeatRecord, SupervisorState,
Task, TaskCheckpoint, TaskEvent, TaskSummary, UpdateContractRequest,
- UpdateDirectiveRequest, UpdateFileRequest, UpdateTaskRequest, UpdateTemplateRequest,
+ UpdateFileRequest, UpdateTaskRequest, UpdateTemplateRequest,
};
/// Repository error types.
@@ -816,10 +815,7 @@ pub async fn get_pending_tasks_for_contract(
WHERE t.contract_id = $1 AND t.owner_id = $2
AND t.status = 'pending'
AND t.retry_count < t.max_retries
- AND (t.is_supervisor = false
- OR EXISTS (SELECT 1 FROM contracts c
- WHERE c.id = t.contract_id
- AND (c.directive_id IS NOT NULL OR c.is_directive_orchestrator = true)))
+ AND t.is_supervisor = false
ORDER BY
t.interrupted_at DESC NULLS LAST,
t.priority DESC,
@@ -844,10 +840,7 @@ pub async fn get_all_pending_task_contracts(
WHERE t.contract_id IS NOT NULL
AND t.status = 'pending'
AND t.retry_count < t.max_retries
- AND (t.is_supervisor = false
- OR EXISTS (SELECT 1 FROM contracts c
- WHERE c.id = t.contract_id
- AND (c.directive_id IS NOT NULL OR c.is_directive_orchestrator = true)))
+ AND t.is_supervisor = false
ORDER BY t.owner_id, t.contract_id
"#,
)
@@ -4919,870 +4912,3 @@ fn truncate_string(s: &str, max_len: usize) -> String {
}
}
-// =============================================================================
-// Directive CRUD
-// =============================================================================
-
-/// Create a new directive, scoped to owner.
-pub async fn create_directive_for_owner(
- pool: &PgPool,
- owner_id: Uuid,
- req: CreateDirectiveRequest,
-) -> Result<Directive, sqlx::Error> {
- sqlx::query_as::<_, Directive>(
- r#"
- INSERT INTO directives (
- owner_id, title, goal,
- requirements, acceptance_criteria, constraints, external_dependencies,
- autonomy_level, confidence_threshold_green, confidence_threshold_yellow,
- max_total_cost_usd, max_wall_time_minutes, max_rework_cycles, max_chain_regenerations,
- repository_url, local_path, base_branch
- )
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
- RETURNING *
- "#,
- )
- .bind(owner_id)
- .bind(&req.title)
- .bind(&req.goal)
- .bind(&req.requirements)
- .bind(&req.acceptance_criteria)
- .bind(&req.constraints)
- .bind(&req.external_dependencies)
- .bind(&req.autonomy_level)
- .bind(req.confidence_threshold_green)
- .bind(req.confidence_threshold_yellow)
- .bind(req.max_total_cost_usd)
- .bind(req.max_wall_time_minutes)
- .bind(req.max_rework_cycles)
- .bind(req.max_chain_regenerations)
- .bind(&req.repository_url)
- .bind(&req.local_path)
- .bind(&req.base_branch)
- .fetch_one(pool)
- .await
-}
-
-/// Get a directive by ID, scoped to owner.
-pub async fn get_directive_for_owner(
- pool: &PgPool,
- id: Uuid,
- owner_id: Uuid,
-) -> Result<Option<Directive>, sqlx::Error> {
- sqlx::query_as::<_, Directive>(
- r#"
- SELECT *
- FROM directives
- WHERE id = $1 AND owner_id = $2
- "#,
- )
- .bind(id)
- .bind(owner_id)
- .fetch_optional(pool)
- .await
-}
-
-/// List all directives for an owner, ordered by created_at DESC.
-pub async fn list_directives_for_owner(
- pool: &PgPool,
- owner_id: Uuid,
-) -> Result<Vec<DirectiveSummary>, sqlx::Error> {
- sqlx::query_as::<_, DirectiveSummary>(
- r#"
- SELECT
- d.id, d.title, d.goal, d.status, d.autonomy_level,
- (SELECT COUNT(*) FROM directive_chains WHERE directive_id = d.id) as chain_count,
- (SELECT COUNT(*) FROM chain_steps cs JOIN directive_chains dc ON cs.chain_id = dc.id WHERE dc.directive_id = d.id) as step_count,
- d.total_cost_usd, d.version, d.created_at, d.updated_at
- FROM directives d
- WHERE d.owner_id = $1
- ORDER BY d.created_at DESC
- "#,
- )
- .bind(owner_id)
- .fetch_all(pool)
- .await
-}
-
-/// Update a directive by ID with optimistic locking, scoped to owner.
-pub async fn update_directive_for_owner(
- pool: &PgPool,
- id: Uuid,
- owner_id: Uuid,
- req: UpdateDirectiveRequest,
-) -> Result<Option<Directive>, RepositoryError> {
- let existing = get_directive_for_owner(pool, id, owner_id).await?;
- let Some(existing) = existing else {
- return Ok(None);
- };
-
- // Check version if provided (optimistic locking)
- if let Some(expected_version) = req.version {
- if existing.version != expected_version {
- return Err(RepositoryError::VersionConflict {
- expected: expected_version,
- actual: existing.version,
- });
- }
- }
-
- // Apply updates
- let title = req.title.unwrap_or(existing.title);
- let goal = req.goal.unwrap_or(existing.goal);
- let requirements = req.requirements.unwrap_or(existing.requirements);
- let acceptance_criteria = req.acceptance_criteria.unwrap_or(existing.acceptance_criteria);
- let constraints = req.constraints.unwrap_or(existing.constraints);
- let external_dependencies = req.external_dependencies.unwrap_or(existing.external_dependencies);
- let status = req.status.unwrap_or(existing.status);
- let autonomy_level = req.autonomy_level.unwrap_or(existing.autonomy_level);
- let confidence_threshold_green = req.confidence_threshold_green.unwrap_or(existing.confidence_threshold_green);
- let confidence_threshold_yellow = req.confidence_threshold_yellow.unwrap_or(existing.confidence_threshold_yellow);
- let max_total_cost_usd = req.max_total_cost_usd.or(existing.max_total_cost_usd);
- let max_wall_time_minutes = req.max_wall_time_minutes.or(existing.max_wall_time_minutes);
- let max_rework_cycles = req.max_rework_cycles.or(existing.max_rework_cycles);
- let max_chain_regenerations = req.max_chain_regenerations.or(existing.max_chain_regenerations);
- let repository_url = req.repository_url.or(existing.repository_url);
- let local_path = req.local_path.or(existing.local_path);
- let base_branch = req.base_branch.or(existing.base_branch);
-
- let result = if req.version.is_some() {
- sqlx::query_as::<_, Directive>(
- r#"
- UPDATE directives
- SET title = $3, goal = $4,
- requirements = $5, acceptance_criteria = $6, constraints = $7, external_dependencies = $8,
- status = $9, autonomy_level = $10,
- confidence_threshold_green = $11, confidence_threshold_yellow = $12,
- max_total_cost_usd = $13, max_wall_time_minutes = $14,
- max_rework_cycles = $15, max_chain_regenerations = $16,
- repository_url = $17, local_path = $18, base_branch = $19,
- version = version + 1, updated_at = NOW()
- WHERE id = $1 AND owner_id = $2 AND version = $20
- RETURNING *
- "#,
- )
- .bind(id)
- .bind(owner_id)
- .bind(&title)
- .bind(&goal)
- .bind(&requirements)
- .bind(&acceptance_criteria)
- .bind(&constraints)
- .bind(&external_dependencies)
- .bind(&status)
- .bind(&autonomy_level)
- .bind(confidence_threshold_green)
- .bind(confidence_threshold_yellow)
- .bind(max_total_cost_usd)
- .bind(max_wall_time_minutes)
- .bind(max_rework_cycles)
- .bind(max_chain_regenerations)
- .bind(&repository_url)
- .bind(&local_path)
- .bind(&base_branch)
- .bind(req.version.unwrap())
- .fetch_optional(pool)
- .await?
- } else {
- sqlx::query_as::<_, Directive>(
- r#"
- UPDATE directives
- SET title = $3, goal = $4,
- requirements = $5, acceptance_criteria = $6, constraints = $7, external_dependencies = $8,
- status = $9, autonomy_level = $10,
- confidence_threshold_green = $11, confidence_threshold_yellow = $12,
- max_total_cost_usd = $13, max_wall_time_minutes = $14,
- max_rework_cycles = $15, max_chain_regenerations = $16,
- repository_url = $17, local_path = $18, base_branch = $19,
- version = version + 1, updated_at = NOW()
- WHERE id = $1 AND owner_id = $2
- RETURNING *
- "#,
- )
- .bind(id)
- .bind(owner_id)
- .bind(&title)
- .bind(&goal)
- .bind(&requirements)
- .bind(&acceptance_criteria)
- .bind(&constraints)
- .bind(&external_dependencies)
- .bind(&status)
- .bind(&autonomy_level)
- .bind(confidence_threshold_green)
- .bind(confidence_threshold_yellow)
- .bind(max_total_cost_usd)
- .bind(max_wall_time_minutes)
- .bind(max_rework_cycles)
- .bind(max_chain_regenerations)
- .bind(&repository_url)
- .bind(&local_path)
- .bind(&base_branch)
- .fetch_optional(pool)
- .await?
- };
-
- // If versioned update returned None, there was a race condition
- if result.is_none() && req.version.is_some() {
- if let Some(current) = get_directive_for_owner(pool, id, owner_id).await? {
- return Err(RepositoryError::VersionConflict {
- expected: req.version.unwrap(),
- actual: current.version,
- });
- }
- }
-
- Ok(result)
-}
-
-/// Delete a directive by ID, scoped to owner.
-/// Also deletes all contracts (and their cascaded tasks/files) associated with this directive.
-pub async fn delete_directive_for_owner(
- pool: &PgPool,
- id: Uuid,
- owner_id: Uuid,
-) -> Result<bool, sqlx::Error> {
- // First verify the directive exists and belongs to the owner
- let directive = get_directive_for_owner(pool, id, owner_id).await?;
- let Some(_directive) = directive else {
- return Ok(false);
- };
-
- // Delete all contracts linked to this directive (tasks/files cascade from contracts).
- // This covers step contracts (directive_id FK) and the orchestrator contract.
- sqlx::query(
- r#"
- DELETE FROM contracts
- WHERE directive_id = $1
- OR id = (SELECT orchestrator_contract_id FROM directives WHERE id = $1)
- "#,
- )
- .bind(id)
- .execute(pool)
- .await?;
-
- // Now delete the directive itself (chains, steps, events, evaluations cascade via FK)
- let result = sqlx::query(
- r#"
- DELETE FROM directives
- WHERE id = $1 AND owner_id = $2
- "#,
- )
- .bind(id)
- .bind(owner_id)
- .execute(pool)
- .await?;
-
- Ok(result.rows_affected() > 0)
-}
-
-/// List chains for a directive (read-only).
-pub async fn list_chains_for_directive(
- pool: &PgPool,
- directive_id: Uuid,
-) -> Result<Vec<DirectiveChain>, sqlx::Error> {
- sqlx::query_as::<_, DirectiveChain>(
- r#"
- SELECT *
- FROM directive_chains
- WHERE directive_id = $1
- ORDER BY generation DESC, created_at DESC
- "#,
- )
- .bind(directive_id)
- .fetch_all(pool)
- .await
-}
-
-/// List steps for a chain (read-only).
-pub async fn list_steps_for_chain(
- pool: &PgPool,
- chain_id: Uuid,
-) -> Result<Vec<ChainStep>, sqlx::Error> {
- sqlx::query_as::<_, ChainStep>(
- r#"
- SELECT *
- FROM chain_steps
- WHERE chain_id = $1
- ORDER BY order_index ASC, created_at ASC
- "#,
- )
- .bind(chain_id)
- .fetch_all(pool)
- .await
-}
-
-/// Batch-fetch lightweight contract summaries for a set of contract IDs.
-pub async fn get_contract_summaries_batch(
- pool: &PgPool,
- contract_ids: &[Uuid],
-) -> Result<Vec<StepContractSummary>, sqlx::Error> {
- sqlx::query_as::<_, StepContractSummary>(
- r#"
- SELECT c.id, c.name, c.contract_type, c.phase, c.status,
- COUNT(t.id) as task_count,
- COUNT(t.id) FILTER (WHERE t.status IN ('done','merged')) as tasks_done,
- COUNT(t.id) FILTER (WHERE t.status IN ('running','initializing','starting')) as tasks_running,
- COUNT(t.id) FILTER (WHERE t.status = 'failed') as tasks_failed
- FROM contracts c
- LEFT JOIN tasks t ON t.contract_id = c.id
- WHERE c.id = ANY($1)
- GROUP BY c.id, c.name, c.contract_type, c.phase, c.status
- "#,
- )
- .bind(contract_ids)
- .fetch_all(pool)
- .await
-}
-
-// ── Directive orchestration functions ───────────────────────────────────────
-
-/// Update directive status with automatic timestamp management.
-pub async fn update_directive_status(
- pool: &PgPool,
- id: Uuid,
- new_status: &str,
-) -> Result<Option<Directive>, sqlx::Error> {
- sqlx::query_as::<_, Directive>(
- r#"
- UPDATE directives
- SET status = $2,
- started_at = CASE WHEN $2 = 'active' AND started_at IS NULL THEN NOW() ELSE started_at END,
- completed_at = CASE WHEN $2 IN ('completed', 'failed') THEN NOW() ELSE completed_at END,
- version = version + 1,
- updated_at = NOW()
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(id)
- .bind(new_status)
- .fetch_optional(pool)
- .await
-}
-
-/// Set the orchestrator contract ID on a directive.
-pub async fn set_directive_orchestrator_contract(
- pool: &PgPool,
- directive_id: Uuid,
- contract_id: Uuid,
-) -> Result<Option<Directive>, sqlx::Error> {
- sqlx::query_as::<_, Directive>(
- r#"
- UPDATE directives
- SET orchestrator_contract_id = $2,
- version = version + 1,
- updated_at = NOW()
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(directive_id)
- .bind(contract_id)
- .fetch_optional(pool)
- .await
-}
-
-/// Set the current chain ID on a directive and increment chain_generation_count.
-pub async fn set_directive_current_chain(
- pool: &PgPool,
- directive_id: Uuid,
- chain_id: Uuid,
-) -> Result<Option<Directive>, sqlx::Error> {
- sqlx::query_as::<_, Directive>(
- r#"
- UPDATE directives
- SET current_chain_id = $2,
- chain_generation_count = chain_generation_count + 1,
- version = version + 1,
- updated_at = NOW()
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(directive_id)
- .bind(chain_id)
- .fetch_optional(pool)
- .await
-}
-
-/// Increment the chain_generation_count on a directive (without setting current_chain_id).
-pub async fn increment_chain_generation_count(
- pool: &PgPool,
- directive_id: Uuid,
-) -> Result<Option<Directive>, sqlx::Error> {
- sqlx::query_as::<_, Directive>(
- r#"
- UPDATE directives
- SET chain_generation_count = chain_generation_count + 1,
- version = version + 1,
- updated_at = NOW()
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(directive_id)
- .fetch_optional(pool)
- .await
-}
-
-/// Create a new directive chain.
-pub async fn create_directive_chain(
- pool: &PgPool,
- directive_id: Uuid,
- name: &str,
- description: Option<&str>,
- rationale: Option<&str>,
- total_steps: i32,
-) -> Result<DirectiveChain, sqlx::Error> {
- // Get next generation number
- let next_gen: (i32,) = sqlx::query_as(
- "SELECT COALESCE(MAX(generation), 0) + 1 FROM directive_chains WHERE directive_id = $1",
- )
- .bind(directive_id)
- .fetch_one(pool)
- .await?;
-
- sqlx::query_as::<_, DirectiveChain>(
- r#"
- INSERT INTO directive_chains (directive_id, generation, name, description, rationale, total_steps, status)
- VALUES ($1, $2, $3, $4, $5, $6, 'running')
- RETURNING *
- "#,
- )
- .bind(directive_id)
- .bind(next_gen.0)
- .bind(name)
- .bind(description)
- .bind(rationale)
- .bind(total_steps)
- .fetch_one(pool)
- .await
-}
-
-/// Create a chain step.
-pub async fn create_chain_step(
- pool: &PgPool,
- chain_id: Uuid,
- name: &str,
- description: Option<&str>,
- step_type: &str,
- contract_type: &str,
- initial_phase: Option<&str>,
- task_plan: Option<&str>,
- depends_on: Option<Vec<Uuid>>,
- order_index: i32,
-) -> Result<ChainStep, sqlx::Error> {
- sqlx::query_as::<_, ChainStep>(
- r#"
- INSERT INTO chain_steps (chain_id, name, description, step_type, contract_type, initial_phase, task_plan, depends_on, order_index, status)
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 'pending')
- RETURNING *
- "#,
- )
- .bind(chain_id)
- .bind(name)
- .bind(description)
- .bind(step_type)
- .bind(contract_type)
- .bind(initial_phase)
- .bind(task_plan)
- .bind(depends_on.as_deref())
- .bind(order_index)
- .fetch_one(pool)
- .await
-}
-
-/// Get a single chain step by ID.
-pub async fn get_chain_step(
- pool: &PgPool,
- step_id: Uuid,
-) -> Result<Option<ChainStep>, sqlx::Error> {
- sqlx::query_as::<_, ChainStep>(
- "SELECT * FROM chain_steps WHERE id = $1",
- )
- .bind(step_id)
- .fetch_optional(pool)
- .await
-}
-
-/// Increment completed_steps counter on a directive chain.
-pub async fn increment_chain_completed_steps(
- pool: &PgPool,
- chain_id: Uuid,
-) -> Result<(), sqlx::Error> {
- sqlx::query(
- "UPDATE directive_chains SET completed_steps = completed_steps + 1, updated_at = NOW() WHERE id = $1",
- )
- .bind(chain_id)
- .execute(pool)
- .await?;
- Ok(())
-}
-
-/// Increment failed_steps counter on a directive chain.
-pub async fn increment_chain_failed_steps(
- pool: &PgPool,
- chain_id: Uuid,
-) -> Result<(), sqlx::Error> {
- sqlx::query(
- "UPDATE directive_chains SET failed_steps = failed_steps + 1, updated_at = NOW() WHERE id = $1",
- )
- .bind(chain_id)
- .execute(pool)
- .await?;
- Ok(())
-}
-
-/// Update a chain step's status with automatic timestamp management.
-pub async fn update_step_status(
- pool: &PgPool,
- step_id: Uuid,
- new_status: &str,
-) -> Result<Option<ChainStep>, sqlx::Error> {
- sqlx::query_as::<_, ChainStep>(
- r#"
- UPDATE chain_steps
- SET status = $2,
- started_at = CASE WHEN $2 = 'running' AND started_at IS NULL THEN NOW() ELSE started_at END,
- completed_at = CASE WHEN $2 IN ('passed', 'failed') THEN NOW() ELSE completed_at END
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(step_id)
- .bind(new_status)
- .fetch_optional(pool)
- .await
-}
-
-/// Link a chain step to a contract and supervisor task.
-pub async fn update_step_contract(
- pool: &PgPool,
- step_id: Uuid,
- contract_id: Uuid,
- supervisor_task_id: Uuid,
-) -> Result<Option<ChainStep>, sqlx::Error> {
- sqlx::query_as::<_, ChainStep>(
- r#"
- UPDATE chain_steps
- SET contract_id = $2,
- supervisor_task_id = $3
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(step_id)
- .bind(contract_id)
- .bind(supervisor_task_id)
- .fetch_optional(pool)
- .await
-}
-
-/// Find steps that are ready to execute (pending, with all dependencies passed).
-pub async fn find_ready_steps(
- pool: &PgPool,
- chain_id: Uuid,
-) -> Result<Vec<ChainStep>, sqlx::Error> {
- sqlx::query_as::<_, ChainStep>(
- r#"
- SELECT * FROM chain_steps
- WHERE chain_id = $1
- AND status = 'pending'
- AND (
- depends_on IS NULL
- OR array_length(depends_on, 1) IS NULL
- OR NOT EXISTS (
- SELECT 1 FROM unnest(depends_on) AS dep_id
- WHERE dep_id NOT IN (
- SELECT id FROM chain_steps WHERE chain_id = $1 AND status = 'passed'
- )
- )
- )
- ORDER BY order_index ASC
- "#,
- )
- .bind(chain_id)
- .fetch_all(pool)
- .await
-}
-
-/// Get a chain step by its linked contract ID.
-pub async fn get_step_by_contract_id(
- pool: &PgPool,
- contract_id: Uuid,
-) -> Result<Option<ChainStep>, sqlx::Error> {
- sqlx::query_as::<_, ChainStep>(
- r#"SELECT * FROM chain_steps WHERE contract_id = $1"#,
- )
- .bind(contract_id)
- .fetch_optional(pool)
- .await
-}
-
-/// Get a directive by its orchestrator contract ID.
-pub async fn get_directive_by_orchestrator_contract(
- pool: &PgPool,
- contract_id: Uuid,
-) -> Result<Option<Directive>, sqlx::Error> {
- sqlx::query_as::<_, Directive>(
- r#"SELECT * FROM directives WHERE orchestrator_contract_id = $1"#,
- )
- .bind(contract_id)
- .fetch_optional(pool)
- .await
-}
-
-/// Set directive-related fields on a contract (directive_id, is_directive_orchestrator).
-pub async fn set_contract_directive_fields(
- pool: &PgPool,
- contract_id: Uuid,
- directive_id: Option<Uuid>,
- is_orchestrator: bool,
-) -> Result<(), sqlx::Error> {
- sqlx::query(
- r#"
- UPDATE contracts
- SET directive_id = $2,
- is_directive_orchestrator = $3
- WHERE id = $1
- "#,
- )
- .bind(contract_id)
- .bind(directive_id)
- .bind(is_orchestrator)
- .execute(pool)
- .await?;
- Ok(())
-}
-
-/// Get a directive by ID (no owner scoping, for internal use).
-pub async fn get_directive(
- pool: &PgPool,
- id: Uuid,
-) -> Result<Option<Directive>, sqlx::Error> {
- sqlx::query_as::<_, Directive>(
- r#"SELECT * FROM directives WHERE id = $1"#,
- )
- .bind(id)
- .fetch_optional(pool)
- .await
-}
-
-/// Update chain status.
-pub async fn update_chain_status(
- pool: &PgPool,
- chain_id: Uuid,
- new_status: &str,
-) -> Result<Option<DirectiveChain>, sqlx::Error> {
- sqlx::query_as::<_, DirectiveChain>(
- r#"
- UPDATE directive_chains
- SET status = $2,
- completed_at = CASE WHEN $2 IN ('completed', 'failed') THEN NOW() ELSE completed_at END,
- version = version + 1,
- updated_at = NOW()
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(chain_id)
- .bind(new_status)
- .fetch_optional(pool)
- .await
-}
-
-// ── Directive monitoring / evaluation functions ─────────────────────────────
-
-/// Create a directive evaluation record. evaluation_number is auto-incremented per step.
-pub async fn create_directive_evaluation(
- pool: &PgPool,
- directive_id: Uuid,
- chain_id: Uuid,
- step_id: Uuid,
- contract_id: Uuid,
- evaluation_type: &str,
- evaluator: Option<&str>,
- passed: bool,
- overall_score: Option<f64>,
- confidence_level: Option<&str>,
- criteria_results: &serde_json::Value,
- summary_feedback: &str,
- rework_instructions: Option<&str>,
-) -> Result<DirectiveEvaluation, sqlx::Error> {
- sqlx::query_as::<_, DirectiveEvaluation>(
- r#"
- INSERT INTO directive_evaluations (
- directive_id, chain_id, step_id, contract_id,
- evaluation_type, evaluation_number, evaluator,
- passed, overall_score, confidence_level,
- criteria_results, summary_feedback, rework_instructions,
- completed_at
- )
- VALUES (
- $1, $2, $3, $4,
- $5, COALESCE((SELECT MAX(evaluation_number) FROM directive_evaluations WHERE step_id = $3), 0) + 1, $6,
- $7, $8, $9,
- $10, $11, $12,
- NOW()
- )
- RETURNING *
- "#,
- )
- .bind(directive_id)
- .bind(chain_id)
- .bind(step_id)
- .bind(contract_id)
- .bind(evaluation_type)
- .bind(evaluator)
- .bind(passed)
- .bind(overall_score)
- .bind(confidence_level)
- .bind(criteria_results)
- .bind(summary_feedback)
- .bind(rework_instructions)
- .fetch_one(pool)
- .await
-}
-
-/// List evaluations for a step, ordered by evaluation_number.
-pub async fn list_evaluations_for_step(
- pool: &PgPool,
- step_id: Uuid,
-) -> Result<Vec<DirectiveEvaluation>, sqlx::Error> {
- sqlx::query_as::<_, DirectiveEvaluation>(
- r#"
- SELECT * FROM directive_evaluations
- WHERE step_id = $1
- ORDER BY evaluation_number ASC
- "#,
- )
- .bind(step_id)
- .fetch_all(pool)
- .await
-}
-
-/// Get a single directive evaluation by ID.
-pub async fn get_directive_evaluation(
- pool: &PgPool,
- evaluation_id: Uuid,
-) -> Result<Option<DirectiveEvaluation>, sqlx::Error> {
- sqlx::query_as::<_, DirectiveEvaluation>(
- "SELECT * FROM directive_evaluations WHERE id = $1",
- )
- .bind(evaluation_id)
- .fetch_optional(pool)
- .await
-}
-
-/// Create a directive event.
-pub async fn create_directive_event(
- pool: &PgPool,
- directive_id: Uuid,
- chain_id: Option<Uuid>,
- step_id: Option<Uuid>,
- event_type: &str,
- severity: &str,
- event_data: Option<&serde_json::Value>,
- actor_type: &str,
- actor_id: Option<Uuid>,
-) -> Result<DirectiveEvent, sqlx::Error> {
- sqlx::query_as::<_, DirectiveEvent>(
- r#"
- INSERT INTO directive_events (directive_id, chain_id, step_id, event_type, severity, event_data, actor_type, actor_id)
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
- RETURNING *
- "#,
- )
- .bind(directive_id)
- .bind(chain_id)
- .bind(step_id)
- .bind(event_type)
- .bind(severity)
- .bind(event_data)
- .bind(actor_type)
- .bind(actor_id)
- .fetch_one(pool)
- .await
-}
-
-/// Update step evaluation fields after an evaluation completes.
-pub async fn update_step_evaluation_fields(
- pool: &PgPool,
- step_id: Uuid,
- confidence_score: Option<f64>,
- confidence_level: Option<&str>,
- last_evaluation_id: Uuid,
-) -> Result<Option<ChainStep>, sqlx::Error> {
- sqlx::query_as::<_, ChainStep>(
- r#"
- UPDATE chain_steps
- SET confidence_score = $2,
- confidence_level = $3,
- evaluation_count = evaluation_count + 1,
- last_evaluation_id = $4
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(step_id)
- .bind(confidence_score)
- .bind(confidence_level)
- .bind(last_evaluation_id)
- .fetch_optional(pool)
- .await
-}
-
-/// Update step monitoring contract/task references.
-pub async fn update_step_monitoring_contract(
- pool: &PgPool,
- step_id: Uuid,
- monitoring_contract_id: Uuid,
- monitoring_task_id: Uuid,
-) -> Result<Option<ChainStep>, sqlx::Error> {
- sqlx::query_as::<_, ChainStep>(
- r#"
- UPDATE chain_steps
- SET monitoring_contract_id = $2,
- monitoring_task_id = $3
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(step_id)
- .bind(monitoring_contract_id)
- .bind(monitoring_task_id)
- .fetch_optional(pool)
- .await
-}
-
-/// Increment step rework_count.
-pub async fn increment_step_rework_count(
- pool: &PgPool,
- step_id: Uuid,
-) -> Result<Option<ChainStep>, sqlx::Error> {
- sqlx::query_as::<_, ChainStep>(
- r#"
- UPDATE chain_steps
- SET rework_count = rework_count + 1
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(step_id)
- .fetch_optional(pool)
- .await
-}
-
-/// Get a chain step by its monitoring contract ID.
-pub async fn get_step_by_monitoring_contract_id(
- pool: &PgPool,
- contract_id: Uuid,
-) -> Result<Option<ChainStep>, sqlx::Error> {
- sqlx::query_as::<_, ChainStep>(
- r#"SELECT * FROM chain_steps WHERE monitoring_contract_id = $1"#,
- )
- .bind(contract_id)
- .fetch_optional(pool)
- .await
-}
diff --git a/makima/src/orchestration/directive.rs b/makima/src/orchestration/directive.rs
deleted file mode 100644
index 46d9425..0000000
--- a/makima/src/orchestration/directive.rs
+++ /dev/null
@@ -1,1685 +0,0 @@
-//! Directive orchestration — init, planning completion, chain advancement.
-
-use serde::Deserialize;
-use sqlx::PgPool;
-use uuid::Uuid;
-
-use serde::Serialize;
-use crate::db::models::{
- ChainStep, CreateContractRequest, CreateTaskRequest, Directive, Task,
- UpdateContractRequest, UpdateTaskRequest,
-};
-use crate::db::repository;
-use crate::server::state::{DaemonCommand, SharedState};
-
-/// A single step in the chain plan produced by the planning supervisor.
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "snake_case")]
-struct ChainPlanStep {
- name: String,
- description: String,
- #[serde(alias = "taskPlan")]
- task_plan: String,
- #[serde(default, alias = "dependsOn")]
- depends_on: Vec<String>, // names of steps this depends on
-}
-
-/// Wrapper for the plan JSON written by the planning supervisor.
-#[derive(Debug, Deserialize)]
-struct ChainPlan {
- steps: Vec<ChainPlanStep>,
-}
-
-/// Result written by the monitoring supervisor after evaluating a step.
-#[derive(Debug, Deserialize, Serialize)]
-#[serde(rename_all = "camelCase")]
-struct MonitoringResult {
- passed: bool,
- overall_score: Option<f64>,
- confidence_level: Option<String>,
- #[serde(default)]
- criteria_results: serde_json::Value,
- #[serde(default)]
- summary_feedback: String,
- rework_instructions: Option<String>,
-}
-
-/// Dispatch a task to an available daemon. Finds a connected daemon with capacity,
-/// assigns the task, and sends a SpawnTask command.
-async fn dispatch_task_to_daemon(
- pool: &PgPool,
- state: &SharedState,
- task: &Task,
- contract_local_only: bool,
- contract_auto_merge_local: bool,
- owner_id: Uuid,
-) -> Result<(), String> {
- // Find available daemons
- let daemons = repository::get_available_daemons_excluding(pool, owner_id, &[])
- .await
- .map_err(|e| format!("Failed to get available daemons: {}", e))?;
-
- let available_daemon = daemons.iter().find(|d| {
- d.current_task_count < d.max_concurrent_tasks
- && state.daemon_connections.contains_key(&d.connection_id)
- });
-
- let daemon = match available_daemon {
- Some(d) => d,
- None => {
- tracing::warn!(
- task_id = %task.id,
- "No daemon available to dispatch task — will be picked up by retry loop"
- );
- return Ok(());
- }
- };
-
- // Assign task to daemon
- let update_req = UpdateTaskRequest {
- status: Some("starting".to_string()),
- daemon_id: Some(daemon.id),
- version: Some(task.version),
- ..Default::default()
- };
-
- let updated = repository::update_task_for_owner(pool, task.id, owner_id, update_req)
- .await
- .map_err(|e| format!("Failed to assign task to daemon: {:?}", e))?;
-
- let Some(updated_task) = updated else {
- return Err("Task not found when assigning to daemon".to_string());
- };
-
- // Get repo URL from task or contract repositories
- let repo_url = if let Some(url) = &updated_task.repository_url {
- Some(url.clone())
- } else if let Some(contract_id) = updated_task.contract_id {
- match repository::list_contract_repositories(pool, contract_id).await {
- Ok(repos) => repos
- .iter()
- .find(|r| r.is_primary)
- .or(repos.first())
- .and_then(|r| r.repository_url.clone().or_else(|| r.local_path.clone())),
- Err(_) => None,
- }
- } else {
- None
- };
-
- let cmd = DaemonCommand::SpawnTask {
- task_id: updated_task.id,
- task_name: updated_task.name.clone(),
- plan: updated_task.plan.clone(),
- repo_url,
- base_branch: updated_task.base_branch.clone(),
- target_branch: updated_task.target_branch.clone(),
- parent_task_id: updated_task.parent_task_id,
- depth: updated_task.depth,
- is_orchestrator: false,
- target_repo_path: updated_task.target_repo_path.clone(),
- completion_action: updated_task.completion_action.clone(),
- continue_from_task_id: updated_task.continue_from_task_id,
- copy_files: updated_task.copy_files.as_ref().and_then(|v| serde_json::from_value(v.clone()).ok()),
- contract_id: updated_task.contract_id,
- is_supervisor: updated_task.is_supervisor,
- autonomous_loop: updated_task.contract_id.is_some(),
- resume_session: false,
- conversation_history: None,
- patch_data: None,
- patch_base_sha: None,
- local_only: contract_local_only,
- auto_merge_local: contract_auto_merge_local,
- supervisor_worktree_task_id: None,
- };
-
- if let Err(e) = state.send_daemon_command(daemon.id, cmd).await {
- tracing::warn!(
- task_id = %task.id,
- daemon_id = %daemon.id,
- error = %e,
- "Failed to send spawn command — rolling back"
- );
- let rollback = UpdateTaskRequest {
- status: Some("pending".to_string()),
- clear_daemon_id: true,
- ..Default::default()
- };
- let _ = repository::update_task_for_owner(pool, task.id, owner_id, rollback).await;
- return Ok(()); // Non-fatal, retry loop will pick it up
- }
-
- tracing::info!(
- task_id = %task.id,
- daemon_id = %daemon.id,
- "Dispatched directive task to daemon"
- );
-
- Ok(())
-}
-
-/// Initialize a directive: create a planning contract and transition to "planning".
-pub async fn init_directive(
- pool: &PgPool,
- state: &SharedState,
- owner_id: Uuid,
- directive_id: Uuid,
-) -> Result<Directive, String> {
- // 1. Get directive, verify status
- let directive = repository::get_directive_for_owner(pool, directive_id, owner_id)
- .await
- .map_err(|e| format!("Failed to get directive: {}", e))?
- .ok_or("Directive not found")?;
-
- if directive.status != "draft" {
- return Err(format!(
- "Directive must be in 'draft' status to start, current status: '{}'",
- directive.status
- ));
- }
-
- // 2. Create planning contract
- let contract = repository::create_contract_for_owner(
- pool,
- owner_id,
- CreateContractRequest {
- name: format!("{} - Planning", directive.title),
- description: Some(format!(
- "Planning contract for directive: {}",
- directive.title
- )),
- contract_type: Some("simple".to_string()),
- template_id: None,
- initial_phase: Some("plan".to_string()),
- autonomous_loop: Some(true),
- phase_guard: None,
- local_only: Some(true),
- auto_merge_local: None,
- },
- )
- .await
- .map_err(|e| format!("Failed to create planning contract: {}", e))?;
-
- // 3. Mark contract as directive orchestrator
- repository::set_contract_directive_fields(pool, contract.id, Some(directive_id), true)
- .await
- .map_err(|e| format!("Failed to set contract directive fields: {}", e))?;
-
- // 4. Build planning prompt
- let planning_prompt = build_planning_prompt(&directive);
-
- // 5. Create supervisor task
- let supervisor_task = repository::create_task_for_owner(
- pool,
- owner_id,
- CreateTaskRequest {
- contract_id: Some(contract.id),
- name: format!("{} - Planner", directive.title),
- description: Some("Decompose directive goal into executable chain steps".to_string()),
- plan: planning_prompt,
- parent_task_id: None,
- is_supervisor: true,
- priority: 10,
- repository_url: directive.repository_url.clone(),
- base_branch: directive.base_branch.clone(),
- target_branch: None,
- merge_mode: None,
- target_repo_path: directive.local_path.clone(),
- completion_action: None,
- continue_from_task_id: None,
- copy_files: None,
- checkpoint_sha: None,
- branched_from_task_id: None,
- conversation_history: None,
- supervisor_worktree_task_id: None,
- },
- )
- .await
- .map_err(|e| format!("Failed to create supervisor task: {}", e))?;
-
- // 6. Link supervisor to contract
- repository::update_contract_for_owner(
- pool,
- contract.id,
- owner_id,
- UpdateContractRequest {
- supervisor_task_id: Some(supervisor_task.id),
- ..Default::default()
- },
- )
- .await
- .map_err(|e| match e {
- crate::db::repository::RepositoryError::Database(e) => {
- format!("Failed to link supervisor to contract: {}", e)
- }
- other => format!("Failed to link supervisor to contract: {:?}", other),
- })?;
-
- // 7. Set orchestrator_contract_id on directive
- repository::set_directive_orchestrator_contract(pool, directive_id, contract.id)
- .await
- .map_err(|e| format!("Failed to set orchestrator contract: {}", e))?;
-
- // 8. Transition directive to "planning"
- let updated = repository::update_directive_status(pool, directive_id, "planning")
- .await
- .map_err(|e| format!("Failed to update directive status: {}", e))?
- .ok_or("Directive not found after status update")?;
-
- // 9. Copy repo config to contract if repository_url is set
- if let Some(ref repo_url) = directive.repository_url {
- let _ = repository::add_remote_repository(
- pool,
- contract.id,
- "directive-repo",
- repo_url,
- true,
- )
- .await;
- } else if let Some(ref local_path) = directive.local_path {
- let _ = repository::add_local_repository(
- pool,
- contract.id,
- "directive-repo",
- local_path,
- true,
- )
- .await;
- }
-
- tracing::info!(
- directive_id = %directive_id,
- contract_id = %contract.id,
- task_id = %supervisor_task.id,
- "Directive started: planning contract created"
- );
-
- // 10. Dispatch planning task to an available daemon immediately
- dispatch_task_to_daemon(
- pool, state, &supervisor_task,
- contract.local_only, contract.auto_merge_local,
- owner_id,
- ).await?;
-
- Ok(updated)
-}
-
-/// Submit a chain plan for a directive via the CLI/API (instead of file-based extraction).
-pub async fn submit_plan(
- pool: &PgPool,
- state: &SharedState,
- owner_id: Uuid,
- directive_id: Uuid,
- plan_json: &str,
-) -> Result<Directive, String> {
- // 1. Get directive, verify status
- let directive = repository::get_directive_for_owner(pool, directive_id, owner_id)
- .await
- .map_err(|e| format!("Failed to get directive: {}", e))?
- .ok_or("Directive not found")?;
-
- if directive.status != "planning" {
- return Err(format!(
- "Directive must be in 'planning' status to submit a plan, current status: '{}'",
- directive.status
- ));
- }
-
- // 2. Idempotency: if current_chain_id already set, return existing directive
- if directive.current_chain_id.is_some() {
- tracing::info!(
- directive_id = %directive_id,
- "Plan already submitted (current_chain_id set), returning existing directive"
- );
- return Ok(directive);
- }
-
- // 3. Parse the plan JSON
- let chain_plan: ChainPlan = serde_json::from_str(plan_json)
- .map_err(|e| format!("Failed to parse chain plan JSON: {}", e))?;
-
- if chain_plan.steps.is_empty() {
- return Err("Chain plan has no steps".to_string());
- }
-
- // 4. Create chain and steps, transition to active
- create_chain_and_steps(pool, state, &directive, &chain_plan, owner_id).await?;
-
- // 5. Re-fetch and return the updated directive
- let updated = repository::get_directive(pool, directive_id)
- .await
- .map_err(|e| format!("Failed to re-fetch directive: {}", e))?
- .ok_or("Directive not found after plan submission")?;
-
- tracing::info!(
- directive_id = %directive_id,
- step_count = chain_plan.steps.len(),
- "Plan submitted via API, directive now active"
- );
-
- Ok(updated)
-}
-
-/// Called when any task completes — checks if it's directive-related and advances.
-/// Called when a contract's status is updated to "completed" via the API.
-/// This is the primary entry point for directive orchestration because supervisor
-/// tasks do not send TaskComplete messages — they complete via contract status updates.
-pub async fn on_contract_completed(
- pool: &PgPool,
- state: &SharedState,
- contract: &crate::db::models::Contract,
- owner_id: Uuid,
-) -> Result<(), String> {
- if contract.status != "completed" {
- return Ok(());
- }
-
- if contract.is_directive_orchestrator {
- let directive =
- repository::get_directive_by_orchestrator_contract(pool, contract.id)
- .await
- .map_err(|e| format!("Failed to get directive by orchestrator: {}", e))?;
-
- if let Some(directive) = directive {
- tracing::info!(
- directive_id = %directive.id,
- contract_id = %contract.id,
- "Directive orchestrator contract completed, handling planning completion"
- );
- handle_planning_completion(pool, state, &directive, owner_id).await?;
- } else {
- tracing::warn!(
- contract_id = %contract.id,
- "Directive orchestrator contract completed but no directive found"
- );
- }
- } else if let Some(directive_id) = contract.directive_id {
- // Check if this is a monitoring contract
- let monitoring_step =
- repository::get_step_by_monitoring_contract_id(pool, contract.id)
- .await
- .map_err(|e| format!("Failed to check monitoring contract: {}", e))?;
-
- if let Some(step) = monitoring_step {
- tracing::info!(
- directive_id = %directive_id,
- step_id = %step.id,
- contract_id = %contract.id,
- "Monitoring contract completed"
- );
- process_monitoring_result(pool, state, contract, &step, owner_id).await?;
- } else {
- // Step contract completed
- let step = repository::get_step_by_contract_id(pool, contract.id)
- .await
- .map_err(|e| format!("Failed to get step by contract: {}", e))?;
-
- if let Some(step) = step {
- // Idempotency: only dispatch monitoring if step is still "running"
- // (on_step_completed may also fire via the task path)
- if step.status != "running" {
- tracing::info!(
- step_id = %step.id,
- status = %step.status,
- "Skipping step contract completion: step no longer running"
- );
- return Ok(());
- }
-
- let directive = repository::get_directive(pool, directive_id)
- .await
- .map_err(|e| format!("Failed to get directive: {}", e))?
- .ok_or("Directive not found")?;
-
- tracing::info!(
- directive_id = %directive_id,
- step_id = %step.id,
- contract_id = %contract.id,
- "Step contract completed, dispatching monitoring"
- );
-
- // Step contract completed successfully — dispatch monitoring
- repository::update_step_status(pool, step.id, "evaluating")
- .await
- .map_err(|e| format!("Failed to update step status: {}", e))?;
-
- let _ = repository::create_directive_event(
- pool,
- directive.id,
- directive.current_chain_id,
- Some(step.id),
- "step_evaluating",
- "info",
- None,
- "system",
- None,
- )
- .await;
-
- dispatch_monitoring(pool, state, &directive, &step, contract, owner_id).await?;
- }
- }
- }
-
- Ok(())
-}
-
-pub async fn on_task_completed(
- pool: &PgPool,
- state: &SharedState,
- task: &Task,
- owner_id: Uuid,
-) -> Result<(), String> {
- let Some(contract_id) = task.contract_id else {
- return Ok(());
- };
-
- let contract = repository::get_contract_for_owner(pool, contract_id, owner_id)
- .await
- .map_err(|e| format!("Failed to get contract: {}", e))?;
-
- let Some(contract) = contract else {
- return Ok(());
- };
-
- if contract.is_directive_orchestrator {
- // This is a planning contract completion
- let directive =
- repository::get_directive_by_orchestrator_contract(pool, contract_id)
- .await
- .map_err(|e| format!("Failed to get directive by orchestrator: {}", e))?;
-
- if let Some(directive) = directive {
- on_planning_completed(pool, state, &directive, task, owner_id).await?;
- }
- } else if contract.directive_id.is_some() {
- // Check if this is a monitoring contract completion
- let monitoring_step =
- repository::get_step_by_monitoring_contract_id(pool, contract_id)
- .await
- .map_err(|e| format!("Failed to check monitoring contract: {}", e))?;
-
- if let Some(step) = monitoring_step {
- on_monitoring_completed(pool, state, &contract, &step, task, owner_id).await?;
- } else {
- // This is a step contract completion
- on_step_completed(pool, state, &contract, task, owner_id).await?;
- }
- }
-
- Ok(())
-}
-
-/// Handle planning task completion: parse chain plan, create steps, advance.
-async fn on_planning_completed(
- pool: &PgPool,
- state: &SharedState,
- directive: &Directive,
- task: &Task,
- owner_id: Uuid,
-) -> Result<(), String> {
- // If task failed, fail the directive
- if task.status == "failed" {
- tracing::warn!(
- directive_id = %directive.id,
- task_id = %task.id,
- "Planning task failed, marking directive as failed"
- );
- repository::update_directive_status(pool, directive.id, "failed")
- .await
- .map_err(|e| format!("Failed to update directive status: {}", e))?;
- return Ok(());
- }
-
- // Only process when the supervisor task itself is done
- if task.status != "done" || !task.is_supervisor {
- return Ok(());
- }
-
- handle_planning_completion(pool, state, directive, owner_id).await
-}
-
-/// Handle planning contract/task completion.
-/// Checks if a plan was submitted via the CLI; if not, retries or fails.
-async fn handle_planning_completion(
- pool: &PgPool,
- state: &SharedState,
- directive: &Directive,
- owner_id: Uuid,
-) -> Result<(), String> {
- // Re-fetch directive to check latest state
- let current = repository::get_directive(pool, directive.id)
- .await
- .map_err(|e| format!("Failed to re-fetch directive: {}", e))?
- .ok_or("Directive not found")?;
-
- // Idempotency: only process if still in "planning" status
- if current.status != "planning" {
- tracing::info!(
- directive_id = %directive.id,
- status = %current.status,
- "Skipping handle_planning_completion: directive no longer in planning status"
- );
- return Ok(());
- }
-
- // If plan was already submitted via CLI (current_chain_id is set), nothing to do
- if current.current_chain_id.is_some() {
- tracing::info!(
- directive_id = %directive.id,
- "Plan already submitted via CLI, skipping handle_planning_completion"
- );
- return Ok(());
- }
-
- // No plan was submitted — check retry budget
- let max_regenerations = current.max_chain_regenerations.unwrap_or(2);
- if current.chain_generation_count < max_regenerations {
- tracing::warn!(
- directive_id = %directive.id,
- attempt = current.chain_generation_count + 1,
- max = max_regenerations,
- "Planning completed without plan submission, retrying"
- );
-
- let _ = repository::create_directive_event(
- pool,
- directive.id,
- None,
- None,
- "planning_retry",
- "warn",
- Some(&serde_json::json!({
- "attempt": current.chain_generation_count + 1,
- "maxRegenerations": max_regenerations,
- "reason": "Planning contract completed without submitting a plan"
- })),
- "system",
- None,
- )
- .await;
-
- // Increment generation count
- repository::increment_chain_generation_count(pool, directive.id)
- .await
- .map_err(|e| format!("Failed to increment chain generation count: {}", e))?;
-
- // Reset to draft so init_directive can be called again
- repository::update_directive_status(pool, directive.id, "draft")
- .await
- .map_err(|e| format!("Failed to reset directive status: {}", e))?;
-
- // Re-init planning
- init_directive(pool, state, owner_id, directive.id).await?;
-
- Ok(())
- } else {
- tracing::error!(
- directive_id = %directive.id,
- attempts = current.chain_generation_count,
- max = max_regenerations,
- "Planning failed: max regeneration attempts exhausted without plan submission"
- );
-
- let _ = repository::create_directive_event(
- pool,
- directive.id,
- None,
- None,
- "planning_failed",
- "error",
- Some(&serde_json::json!({
- "attempts": current.chain_generation_count,
- "maxRegenerations": max_regenerations,
- "reason": "Max chain regeneration attempts exhausted without plan submission"
- })),
- "system",
- None,
- )
- .await;
-
- repository::update_directive_status(pool, directive.id, "failed")
- .await
- .map_err(|e| format!("Failed to update directive status: {}", e))?;
-
- Ok(())
- }
-}
-
-/// Inner helper: create chain, steps, set current chain, transition to active, and advance.
-/// Extracted so that `process_planning_result` can catch errors and mark the directive failed.
-async fn create_chain_and_steps(
- pool: &PgPool,
- state: &SharedState,
- directive: &Directive,
- chain_plan: &ChainPlan,
- owner_id: Uuid,
-) -> Result<(), String> {
- // Create chain
- let chain = repository::create_directive_chain(
- pool,
- directive.id,
- &format!("{} - Chain", directive.title),
- Some("Auto-generated from planning"),
- None,
- chain_plan.steps.len() as i32,
- )
- .await
- .map_err(|e| format!("Failed to create directive chain: {}", e))?;
-
- // Create steps (two passes: first create all, then resolve dependencies)
- let mut step_ids: Vec<(String, Uuid)> = Vec::new();
-
- for (i, plan_step) in chain_plan.steps.iter().enumerate() {
- let step = repository::create_chain_step(
- pool,
- chain.id,
- &plan_step.name,
- Some(&plan_step.description),
- "task",
- "simple",
- Some("plan"),
- Some(&plan_step.task_plan),
- None, // dependencies set in second pass
- i as i32,
- )
- .await
- .map_err(|e| format!("Failed to create chain step: {}", e))?;
-
- step_ids.push((plan_step.name.clone(), step.id));
- }
-
- // Second pass: resolve name-based dependencies to UUIDs and update
- for (i, plan_step) in chain_plan.steps.iter().enumerate() {
- if plan_step.depends_on.is_empty() {
- continue;
- }
-
- let dep_uuids: Vec<Uuid> = plan_step
- .depends_on
- .iter()
- .filter_map(|dep_name| {
- step_ids
- .iter()
- .find(|(name, _)| name == dep_name)
- .map(|(_, id)| *id)
- })
- .collect();
-
- if !dep_uuids.is_empty() {
- let step_id = step_ids[i].1;
- sqlx::query(
- "UPDATE chain_steps SET depends_on = $2 WHERE id = $1",
- )
- .bind(step_id)
- .bind(&dep_uuids)
- .execute(pool)
- .await
- .map_err(|e| format!("Failed to update step dependencies: {}", e))?;
- }
- }
-
- // Set current chain on directive
- repository::set_directive_current_chain(pool, directive.id, chain.id)
- .await
- .map_err(|e| format!("Failed to set current chain: {}", e))?;
-
- // Transition directive to active
- let updated_directive = repository::update_directive_status(pool, directive.id, "active")
- .await
- .map_err(|e| format!("Failed to update directive status: {}", e))?
- .ok_or("Directive not found after status update")?;
-
- tracing::info!(
- directive_id = %directive.id,
- chain_id = %chain.id,
- step_count = chain_plan.steps.len(),
- "Chain plan created, advancing chain"
- );
-
- // Advance chain to dispatch ready steps
- advance_chain(pool, state, &updated_directive, owner_id).await
-}
-
-/// Handle a step contract task completion.
-async fn on_step_completed(
- pool: &PgPool,
- state: &SharedState,
- contract: &crate::db::models::Contract,
- task: &Task,
- owner_id: Uuid,
-) -> Result<(), String> {
- // Only process supervisor task completions
- if !task.is_supervisor {
- return Ok(());
- }
-
- let Some(directive_id) = contract.directive_id else {
- return Ok(());
- };
-
- // Find the step linked to this contract
- let step = repository::get_step_by_contract_id(pool, contract.id)
- .await
- .map_err(|e| format!("Failed to get step by contract: {}", e))?;
-
- let Some(step) = step else {
- return Ok(());
- };
-
- // Idempotency: only process if step is still "running"
- // (on_contract_completed may also fire via the contract path)
- if step.status != "running" {
- tracing::info!(
- step_id = %step.id,
- status = %step.status,
- "Skipping on_step_completed: step no longer running"
- );
- return Ok(());
- }
-
- // Get the directive for threshold info
- let directive = repository::get_directive(pool, directive_id)
- .await
- .map_err(|e| format!("Failed to get directive: {}", e))?
- .ok_or("Directive not found")?;
-
- if task.status == "done" {
- // Step task succeeded — dispatch monitoring evaluation
- repository::update_step_status(pool, step.id, "evaluating")
- .await
- .map_err(|e| format!("Failed to update step status: {}", e))?;
-
- let _ = repository::create_directive_event(
- pool,
- directive.id,
- directive.current_chain_id,
- Some(step.id),
- "step_evaluating",
- "info",
- None,
- "system",
- None,
- )
- .await;
-
- tracing::info!(
- directive_id = %directive_id,
- step_id = %step.id,
- step_name = %step.name,
- "Step task done, dispatching monitoring evaluation"
- );
-
- dispatch_monitoring(pool, state, &directive, &step, contract, owner_id).await
- } else {
- // Step task failed — mark step failed and advance
- repository::update_step_status(pool, step.id, "failed")
- .await
- .map_err(|e| format!("Failed to update step status: {}", e))?;
-
- let _ = repository::increment_chain_failed_steps(pool, step.chain_id).await;
-
- tracing::info!(
- directive_id = %directive_id,
- step_id = %step.id,
- step_name = %step.name,
- "Step failed"
- );
-
- advance_chain(pool, state, &directive, owner_id).await
- }
-}
-
-/// Check chain progress and dispatch ready steps or mark directive complete.
-async fn advance_chain(
- pool: &PgPool,
- state: &SharedState,
- directive: &Directive,
- owner_id: Uuid,
-) -> Result<(), String> {
- let Some(chain_id) = directive.current_chain_id else {
- return Ok(());
- };
-
- let steps = repository::list_steps_for_chain(pool, chain_id)
- .await
- .map_err(|e| format!("Failed to list steps: {}", e))?;
-
- // Check if all steps passed
- let all_passed = steps.iter().all(|s| s.status == "passed");
- if all_passed && !steps.is_empty() {
- repository::update_chain_status(pool, chain_id, "completed")
- .await
- .map_err(|e| format!("Failed to update chain status: {}", e))?;
- repository::update_directive_status(pool, directive.id, "completed")
- .await
- .map_err(|e| format!("Failed to update directive status: {}", e))?;
- tracing::info!(directive_id = %directive.id, "Directive completed: all steps passed");
- return Ok(());
- }
-
- // Check if any step failed
- let any_failed = steps.iter().any(|s| s.status == "failed");
- if any_failed {
- repository::update_chain_status(pool, chain_id, "failed")
- .await
- .map_err(|e| format!("Failed to update chain status: {}", e))?;
- repository::update_directive_status(pool, directive.id, "failed")
- .await
- .map_err(|e| format!("Failed to update directive status: {}", e))?;
- tracing::info!(directive_id = %directive.id, "Directive failed: step failure detected");
- return Ok(());
- }
-
- // Find and dispatch ready steps
- let ready_steps = repository::find_ready_steps(pool, chain_id)
- .await
- .map_err(|e| format!("Failed to find ready steps: {}", e))?;
-
- for step in ready_steps {
- if let Err(e) = dispatch_step(pool, state, directive, &step, owner_id).await {
- tracing::error!(
- step_id = %step.id,
- step_name = %step.name,
- error = %e,
- "Failed to dispatch step"
- );
- }
- }
-
- Ok(())
-}
-
-/// Dispatch a single chain step as a new contract with supervisor.
-async fn dispatch_step(
- pool: &PgPool,
- state: &SharedState,
- directive: &Directive,
- step: &crate::db::models::ChainStep,
- owner_id: Uuid,
-) -> Result<(), String> {
- // Mark step as running
- repository::update_step_status(pool, step.id, "running")
- .await
- .map_err(|e| format!("Failed to update step status: {}", e))?;
-
- // Create contract for this step.
- // Step contracts use the directive's repository config — not local_only,
- // so they can branch and merge to share work across steps.
- let has_repo = directive.repository_url.is_some() || directive.local_path.is_some();
- let contract = repository::create_contract_for_owner(
- pool,
- owner_id,
- CreateContractRequest {
- name: step.name.clone(),
- description: step.description.clone(),
- contract_type: Some(step.contract_type.clone()),
- template_id: None,
- initial_phase: step.initial_phase.clone(),
- autonomous_loop: Some(true),
- phase_guard: None,
- local_only: Some(!has_repo),
- auto_merge_local: if has_repo { Some(true) } else { None },
- },
- )
- .await
- .map_err(|e| format!("Failed to create step contract: {}", e))?;
-
- // Set directive_id on contract
- repository::set_contract_directive_fields(pool, contract.id, Some(directive.id), false)
- .await
- .map_err(|e| format!("Failed to set contract directive fields: {}", e))?;
-
- // Build the task plan, prepending rework instructions if this is a rework cycle
- let mut task_plan = step
- .task_plan
- .clone()
- .unwrap_or_else(|| format!("Execute step: {}", step.name));
-
- if let Some(eval_id) = step.last_evaluation_id {
- if let Ok(Some(evaluation)) = repository::get_directive_evaluation(pool, eval_id).await {
- if let Some(ref rework) = evaluation.rework_instructions {
- task_plan = format!(
- "IMPORTANT — REWORK REQUIRED (attempt #{}):\n\
- The previous attempt was evaluated and did NOT pass.\n\
- Feedback: {}\n\
- Rework instructions: {}\n\n\
- ---\n\n\
- Original task plan:\n{}",
- step.rework_count + 1,
- evaluation.summary_feedback,
- rework,
- task_plan,
- );
- }
- }
- }
-
- // Create supervisor task
- let supervisor_task = repository::create_task_for_owner(
- pool,
- owner_id,
- CreateTaskRequest {
- contract_id: Some(contract.id),
- name: format!("{} Supervisor", step.name),
- description: step.description.clone(),
- plan: task_plan,
- parent_task_id: None,
- is_supervisor: true,
- priority: 5,
- repository_url: directive.repository_url.clone(),
- base_branch: directive.base_branch.clone(),
- target_branch: None,
- merge_mode: None,
- target_repo_path: directive.local_path.clone(),
- completion_action: None,
- continue_from_task_id: None,
- copy_files: None,
- checkpoint_sha: None,
- branched_from_task_id: None,
- conversation_history: None,
- supervisor_worktree_task_id: None,
- },
- )
- .await
- .map_err(|e| format!("Failed to create step supervisor task: {}", e))?;
-
- // Link supervisor to contract
- repository::update_contract_for_owner(
- pool,
- contract.id,
- owner_id,
- UpdateContractRequest {
- supervisor_task_id: Some(supervisor_task.id),
- ..Default::default()
- },
- )
- .await
- .map_err(|e| match e {
- crate::db::repository::RepositoryError::Database(e) => {
- format!("Failed to link supervisor to step contract: {}", e)
- }
- other => format!("Failed to link supervisor to step contract: {:?}", other),
- })?;
-
- // Link step to contract/task
- repository::update_step_contract(pool, step.id, contract.id, supervisor_task.id)
- .await
- .map_err(|e| format!("Failed to update step contract link: {}", e))?;
-
- // Copy repo config from directive to step contract
- if let Some(ref repo_url) = directive.repository_url {
- let _ = repository::add_remote_repository(
- pool,
- contract.id,
- "directive-repo",
- repo_url,
- true,
- )
- .await;
- } else if let Some(ref local_path) = directive.local_path {
- let _ = repository::add_local_repository(
- pool,
- contract.id,
- "directive-repo",
- local_path,
- true,
- )
- .await;
- }
-
- tracing::info!(
- directive_id = %directive.id,
- step_id = %step.id,
- step_name = %step.name,
- contract_id = %contract.id,
- task_id = %supervisor_task.id,
- "Step dispatched"
- );
-
- // Dispatch step task to an available daemon immediately
- dispatch_task_to_daemon(
- pool, state, &supervisor_task,
- contract.local_only, contract.auto_merge_local,
- owner_id,
- ).await?;
-
- Ok(())
-}
-
-/// Build the planning supervisor prompt from a directive.
-fn build_planning_prompt(directive: &Directive) -> String {
- format!(
- r#"You are planning the execution of a directive.
-
-DIRECTIVE: {title}
-GOAL: {goal}
-REQUIREMENTS: {requirements}
-ACCEPTANCE CRITERIA: {acceptance_criteria}
-CONSTRAINTS: {constraints}
-
-Your job is to decompose this goal into a sequence of executable steps.
-Each step will become a separate contract with its own supervisor.
-
-The JSON format:
-{{
- "steps": [
- {{
- "name": "Step name",
- "description": "What this step accomplishes",
- "task_plan": "Detailed instructions for the step's supervisor",
- "depends_on": []
- }}
- ]
-}}
-
-Rules:
-- Steps with no dependencies (empty depends_on array) will run in parallel.
-- Steps that depend on other steps will wait until those complete.
-- The depends_on array contains names of steps this step depends on.
-- Each step should be a self-contained unit of work.
-- Be specific in task_plan — include file paths, function names, and acceptance criteria where possible.
-- Keep the number of steps reasonable (3-10 typically).
-
-Submit your plan by piping the JSON to stdin:
- echo '<your_json_plan>' | makima directive submit-plan --directive-id {directive_id}
-
-After submitting the plan, mark the contract as complete:
- makima supervisor complete"#,
- title = directive.title,
- goal = directive.goal,
- requirements = serde_json::to_string_pretty(&directive.requirements).unwrap_or_default(),
- acceptance_criteria = serde_json::to_string_pretty(&directive.acceptance_criteria).unwrap_or_default(),
- constraints = serde_json::to_string_pretty(&directive.constraints).unwrap_or_default(),
- directive_id = directive.id,
- )
-}
-
-/// Extract JSON from file body elements.
-fn extract_plan_json(body: &[crate::db::models::BodyElement]) -> Option<String> {
- use crate::db::models::BodyElement;
-
- for element in body {
- match element {
- BodyElement::Code { content, .. } => {
- // Try to parse as JSON
- let trimmed = content.trim();
- if trimmed.starts_with('{') || trimmed.starts_with('[') {
- if serde_json::from_str::<serde_json::Value>(trimmed).is_ok() {
- return Some(trimmed.to_string());
- }
- }
- }
- BodyElement::Paragraph { text } => {
- let trimmed = text.trim();
- if trimmed.starts_with('{') || trimmed.starts_with('[') {
- if serde_json::from_str::<serde_json::Value>(trimmed).is_ok() {
- return Some(trimmed.to_string());
- }
- }
- }
- BodyElement::Markdown { content } => {
- // Try to find JSON in markdown content
- let trimmed = content.trim();
- if trimmed.starts_with('{') || trimmed.starts_with('[') {
- if serde_json::from_str::<serde_json::Value>(trimmed).is_ok() {
- return Some(trimmed.to_string());
- }
- }
- // Try to find JSON in code blocks within markdown
- if let Some(json_start) = trimmed.find("```json") {
- let after = &trimmed[json_start + 7..];
- if let Some(json_end) = after.find("```") {
- let json_str = after[..json_end].trim();
- if serde_json::from_str::<serde_json::Value>(json_str).is_ok() {
- return Some(json_str.to_string());
- }
- }
- }
- }
- _ => {}
- }
- }
-
- // Fallback: concatenate all text content and try to find JSON
- let all_text: String = body
- .iter()
- .map(|el| match el {
- BodyElement::Code { content, .. } => content.clone(),
- BodyElement::Paragraph { text } => text.clone(),
- BodyElement::Markdown { content } => content.clone(),
- _ => String::new(),
- })
- .collect::<Vec<_>>()
- .join("\n");
-
- let trimmed = all_text.trim();
- if let Some(start) = trimmed.find('{') {
- // Find matching closing brace
- let substr = &trimmed[start..];
- if serde_json::from_str::<serde_json::Value>(substr).is_ok() {
- return Some(substr.to_string());
- }
- }
-
- None
-}
-
-/// Dispatch a monitoring contract to evaluate a completed step.
-async fn dispatch_monitoring(
- pool: &PgPool,
- state: &SharedState,
- directive: &Directive,
- step: &ChainStep,
- step_contract: &crate::db::models::Contract,
- owner_id: Uuid,
-) -> Result<(), String> {
- // Create monitoring contract
- let contract = repository::create_contract_for_owner(
- pool,
- owner_id,
- CreateContractRequest {
- name: format!("{} - Monitor", step.name),
- description: Some(format!("Monitoring evaluation for step: {}", step.name)),
- contract_type: Some("monitoring".to_string()),
- template_id: None,
- initial_phase: Some("plan".to_string()),
- autonomous_loop: Some(true),
- phase_guard: None,
- local_only: Some(true),
- auto_merge_local: None,
- },
- )
- .await
- .map_err(|e| format!("Failed to create monitoring contract: {}", e))?;
-
- // Mark contract as directive-related (not orchestrator)
- repository::set_contract_directive_fields(pool, contract.id, Some(directive.id), false)
- .await
- .map_err(|e| format!("Failed to set monitoring contract directive fields: {}", e))?;
-
- // Build evaluation prompt
- let prompt = build_monitoring_prompt(directive, step, step_contract);
-
- // Create monitoring task (NOT a supervisor — regular task that exits when done,
- // which triggers on_task_completed → on_monitoring_completed automatically)
- let supervisor_task = repository::create_task_for_owner(
- pool,
- owner_id,
- CreateTaskRequest {
- contract_id: Some(contract.id),
- name: format!("{} - Evaluator", step.name),
- description: Some("Evaluate step output against directive criteria".to_string()),
- plan: prompt,
- parent_task_id: None,
- is_supervisor: false,
- priority: 8,
- repository_url: directive.repository_url.clone(),
- base_branch: directive.base_branch.clone(),
- target_branch: None,
- merge_mode: None,
- target_repo_path: directive.local_path.clone(),
- completion_action: None,
- continue_from_task_id: None,
- copy_files: None,
- checkpoint_sha: None,
- branched_from_task_id: None,
- conversation_history: None,
- supervisor_worktree_task_id: None,
- },
- )
- .await
- .map_err(|e| format!("Failed to create monitoring task: {}", e))?;
-
- // Link monitoring task to contract
- repository::update_contract_for_owner(
- pool,
- contract.id,
- owner_id,
- UpdateContractRequest {
- supervisor_task_id: Some(supervisor_task.id),
- ..Default::default()
- },
- )
- .await
- .map_err(|e| match e {
- crate::db::repository::RepositoryError::Database(e) => {
- format!("Failed to link task to monitoring contract: {}", e)
- }
- other => format!("Failed to link task to monitoring contract: {:?}", other),
- })?;
-
- // Link step to monitoring contract/task
- repository::update_step_monitoring_contract(pool, step.id, contract.id, supervisor_task.id)
- .await
- .map_err(|e| format!("Failed to update step monitoring contract link: {}", e))?;
-
- // Copy repo config from directive to monitoring contract
- if let Some(ref repo_url) = directive.repository_url {
- let _ = repository::add_remote_repository(
- pool,
- contract.id,
- "directive-repo",
- repo_url,
- true,
- )
- .await;
- } else if let Some(ref local_path) = directive.local_path {
- let _ = repository::add_local_repository(
- pool,
- contract.id,
- "directive-repo",
- local_path,
- true,
- )
- .await;
- }
-
- tracing::info!(
- directive_id = %directive.id,
- step_id = %step.id,
- step_name = %step.name,
- monitoring_contract_id = %contract.id,
- monitoring_task_id = %supervisor_task.id,
- "Monitoring evaluation dispatched"
- );
-
- // Dispatch monitoring task to an available daemon immediately
- dispatch_task_to_daemon(
- pool, state, &supervisor_task,
- contract.local_only, contract.auto_merge_local,
- owner_id,
- ).await?;
-
- Ok(())
-}
-
-/// Build the monitoring supervisor prompt.
-fn build_monitoring_prompt(
- directive: &Directive,
- step: &ChainStep,
- step_contract: &crate::db::models::Contract,
-) -> String {
- format!(
- r#"You are evaluating the output of a completed step in a directive chain.
-
-DIRECTIVE: {title}
-GOAL: {goal}
-REQUIREMENTS: {requirements}
-ACCEPTANCE CRITERIA: {acceptance_criteria}
-CONSTRAINTS: {constraints}
-
-STEP: {step_name}
-STEP DESCRIPTION: {step_description}
-STEP TASK PLAN: {task_plan}
-STEP CONTRACT ID: {step_contract_id}
-
-CONFIDENCE THRESHOLDS:
-- Green (pass): >= {threshold_green}
-- Yellow (marginal): >= {threshold_yellow}
-- Red (fail): < {threshold_yellow}
-
-INSTRUCTIONS:
-1. Read the step contract's files to understand what was delivered:
- makima contract files --contract-id {step_contract_id}
- makima contract file <file_id> --contract-id {step_contract_id}
-
-2. Evaluate whether the step's output meets the directive's requirements and the step's task plan.
-
-3. Write your evaluation as a JSON file to this monitoring contract. Create a file called
- evaluation.json with the JSON content first, then upload it:
-
- cat > /tmp/eval-result.json << 'EVALEOF'
- {{
- "passed": true,
- "overallScore": 0.85,
- "confidenceLevel": "green",
- "criteriaResults": [
- {{
- "criterion": "Example criterion",
- "passed": true,
- "score": 0.9,
- "evidence": "What was found"
- }}
- ],
- "summaryFeedback": "Summary of evaluation",
- "reworkInstructions": null
- }}
- EVALEOF
- makima contract create-file evaluation-result < /tmp/eval-result.json
-
- Replace the example values with your actual evaluation results.
-
-Scoring guidelines:
-- overallScore >= {threshold_green}: confidenceLevel = "green", passed = true
-- overallScore >= {threshold_yellow} and < {threshold_green}: confidenceLevel = "yellow", use judgment
-- overallScore < {threshold_yellow}: confidenceLevel = "red", passed = false
-- Be specific in reworkInstructions if the step fails — the step will be re-executed with these instructions.
-- Set reworkInstructions to null if the step passed.
-
-You are done after writing the evaluation file."#,
- title = directive.title,
- goal = directive.goal,
- requirements = serde_json::to_string_pretty(&directive.requirements).unwrap_or_default(),
- acceptance_criteria = serde_json::to_string_pretty(&directive.acceptance_criteria).unwrap_or_default(),
- constraints = serde_json::to_string_pretty(&directive.constraints).unwrap_or_default(),
- step_name = step.name,
- step_description = step.description.as_deref().unwrap_or("N/A"),
- task_plan = step.task_plan.as_deref().unwrap_or("N/A"),
- step_contract_id = step_contract.id,
- threshold_green = directive.confidence_threshold_green,
- threshold_yellow = directive.confidence_threshold_yellow,
- )
-}
-
-/// Handle monitoring contract task completion — parse evaluation and decide step outcome.
-async fn on_monitoring_completed(
- pool: &PgPool,
- state: &SharedState,
- contract: &crate::db::models::Contract,
- step: &ChainStep,
- task: &Task,
- owner_id: Uuid,
-) -> Result<(), String> {
- let Some(directive_id) = contract.directive_id else {
- return Ok(());
- };
-
- let directive = repository::get_directive(pool, directive_id)
- .await
- .map_err(|e| format!("Failed to get directive: {}", e))?
- .ok_or("Directive not found")?;
-
- // If monitoring task itself failed, fail-open: mark step as passed
- if task.status == "failed" {
- tracing::warn!(
- directive_id = %directive_id,
- step_id = %step.id,
- "Monitoring task failed, fail-open: marking step as passed"
- );
-
- repository::update_step_status(pool, step.id, "passed")
- .await
- .map_err(|e| format!("Failed to update step status: {}", e))?;
-
- let _ = repository::increment_chain_completed_steps(pool, step.chain_id).await;
-
- let _ = repository::create_directive_event(
- pool,
- directive_id,
- directive.current_chain_id,
- Some(step.id),
- "monitoring_failed_open",
- "warn",
- None,
- "system",
- None,
- )
- .await;
-
- return advance_chain(pool, state, &directive, owner_id).await;
- }
-
- if task.status != "done" {
- return Ok(());
- }
-
- process_monitoring_result(pool, state, contract, step, owner_id).await
-}
-
-/// Core monitoring logic: read evaluation from files, create record, handle pass/fail/rework.
-/// Called from both `on_monitoring_completed` (task path) and `on_contract_completed` (API path).
-async fn process_monitoring_result(
- pool: &PgPool,
- state: &SharedState,
- contract: &crate::db::models::Contract,
- step: &ChainStep,
- owner_id: Uuid,
-) -> Result<(), String> {
- let Some(directive_id) = contract.directive_id else {
- return Ok(());
- };
-
- // Idempotency guard: re-fetch step and only process if still "evaluating".
- let current_step = repository::get_chain_step(pool, step.id)
- .await
- .map_err(|e| format!("Failed to re-fetch step: {}", e))?;
- if let Some(ref s) = current_step {
- if s.status != "evaluating" {
- tracing::info!(
- step_id = %step.id,
- status = %s.status,
- "Skipping process_monitoring_result: step no longer in evaluating status"
- );
- return Ok(());
- }
- }
-
- let directive = repository::get_directive(pool, directive_id)
- .await
- .map_err(|e| format!("Failed to get directive: {}", e))?
- .ok_or("Directive not found")?;
-
- // Read evaluation result from monitoring contract files
- let files = repository::list_files_in_contract(pool, contract.id, owner_id)
- .await
- .map_err(|e| format!("Failed to list monitoring contract files: {}", e))?;
-
- let eval_file = files.iter().find(|f| {
- let name_lower = f.name.to_lowercase();
- name_lower.contains("evaluation") || name_lower.contains("eval")
- });
-
- let eval_file = eval_file.or_else(|| files.first());
-
- let monitoring_result = if let Some(eval_file) = eval_file {
- let full_file = repository::get_file(pool, eval_file.id)
- .await
- .map_err(|e| format!("Failed to get evaluation file: {}", e))?;
-
- if let Some(full_file) = full_file {
- let json_str = extract_plan_json(&full_file.body);
- json_str.and_then(|s| serde_json::from_str::<MonitoringResult>(&s).ok())
- } else {
- None
- }
- } else {
- None
- };
-
- // If we couldn't parse the result, fail-open
- let Some(result) = monitoring_result else {
- tracing::warn!(
- directive_id = %directive_id,
- step_id = %step.id,
- "Could not parse monitoring result, fail-open: marking step as passed"
- );
-
- repository::update_step_status(pool, step.id, "passed")
- .await
- .map_err(|e| format!("Failed to update step status: {}", e))?;
-
- let _ = repository::increment_chain_completed_steps(pool, step.chain_id).await;
-
- let _ = repository::create_directive_event(
- pool,
- directive_id,
- directive.current_chain_id,
- Some(step.id),
- "monitoring_parse_failed_open",
- "warn",
- None,
- "system",
- None,
- )
- .await;
-
- return advance_chain(pool, state, &directive, owner_id).await;
- };
-
- // Create evaluation record
- let chain_id = directive.current_chain_id.unwrap_or(step.chain_id);
- let evaluation = repository::create_directive_evaluation(
- pool,
- directive_id,
- chain_id,
- step.id,
- contract.id,
- "monitoring",
- Some("automated"),
- result.passed,
- result.overall_score,
- result.confidence_level.as_deref(),
- &result.criteria_results,
- &result.summary_feedback,
- result.rework_instructions.as_deref(),
- )
- .await
- .map_err(|e| format!("Failed to create directive evaluation: {}", e))?;
-
- // Update step evaluation fields
- repository::update_step_evaluation_fields(
- pool,
- step.id,
- result.overall_score,
- result.confidence_level.as_deref(),
- evaluation.id,
- )
- .await
- .map_err(|e| format!("Failed to update step evaluation fields: {}", e))?;
-
- // Create event
- let event_data = serde_json::json!({
- "passed": result.passed,
- "overallScore": result.overall_score,
- "confidenceLevel": result.confidence_level,
- "summaryFeedback": result.summary_feedback,
- });
- let _ = repository::create_directive_event(
- pool,
- directive_id,
- Some(chain_id),
- Some(step.id),
- if result.passed { "step_evaluation_passed" } else { "step_evaluation_failed" },
- "info",
- Some(&event_data),
- "system",
- None,
- )
- .await;
-
- if result.passed {
- // Evaluation passed — mark step as passed
- tracing::info!(
- directive_id = %directive_id,
- step_id = %step.id,
- step_name = %step.name,
- score = ?result.overall_score,
- "Step evaluation passed"
- );
-
- repository::update_step_status(pool, step.id, "passed")
- .await
- .map_err(|e| format!("Failed to update step status: {}", e))?;
-
- let _ = repository::increment_chain_completed_steps(pool, step.chain_id).await;
-
- advance_chain(pool, state, &directive, owner_id).await
- } else {
- // Evaluation failed — check rework budget
- let max_rework = directive.max_rework_cycles.unwrap_or(3);
- if step.rework_count >= max_rework {
- tracing::warn!(
- directive_id = %directive_id,
- step_id = %step.id,
- step_name = %step.name,
- rework_count = step.rework_count,
- max_rework = max_rework,
- "Step evaluation failed, max rework cycles exceeded"
- );
-
- repository::update_step_status(pool, step.id, "failed")
- .await
- .map_err(|e| format!("Failed to update step status: {}", e))?;
-
- let _ = repository::increment_chain_failed_steps(pool, step.chain_id).await;
-
- advance_chain(pool, state, &directive, owner_id).await
- } else {
- tracing::info!(
- directive_id = %directive_id,
- step_id = %step.id,
- step_name = %step.name,
- rework_count = step.rework_count,
- "Step evaluation failed, scheduling rework"
- );
-
- repository::increment_step_rework_count(pool, step.id)
- .await
- .map_err(|e| format!("Failed to increment rework count: {}", e))?;
-
- // Set step back to pending so advance_chain re-dispatches it
- repository::update_step_status(pool, step.id, "pending")
- .await
- .map_err(|e| format!("Failed to update step status: {}", e))?;
-
- advance_chain(pool, state, &directive, owner_id).await
- }
- }
-}
-
-/// Trigger a manual evaluation for a step. Public for use by handlers.
-pub async fn trigger_manual_evaluation(
- pool: &PgPool,
- state: &SharedState,
- owner_id: Uuid,
- directive_id: Uuid,
- step_id: Uuid,
-) -> Result<ChainStep, String> {
- let directive = repository::get_directive_for_owner(pool, directive_id, owner_id)
- .await
- .map_err(|e| format!("Failed to get directive: {}", e))?
- .ok_or("Directive not found")?;
-
- // Get the step — find via chain steps
- let chain_id = directive.current_chain_id.ok_or("Directive has no active chain")?;
- let steps = repository::list_steps_for_chain(pool, chain_id)
- .await
- .map_err(|e| format!("Failed to list steps: {}", e))?;
-
- let step = steps
- .into_iter()
- .find(|s| s.id == step_id)
- .ok_or("Step not found in current chain")?;
-
- // Step must have a contract_id (must have been executed)
- let contract_id = step.contract_id.ok_or("Step has no contract — it hasn't been executed yet")?;
-
- let contract = repository::get_contract_for_owner(pool, contract_id, owner_id)
- .await
- .map_err(|e| format!("Failed to get step contract: {}", e))?
- .ok_or("Step contract not found")?;
-
- // Set step to evaluating
- let updated_step = repository::update_step_status(pool, step.id, "evaluating")
- .await
- .map_err(|e| format!("Failed to update step status: {}", e))?
- .ok_or("Step not found after status update")?;
-
- let _ = repository::create_directive_event(
- pool,
- directive.id,
- directive.current_chain_id,
- Some(step.id),
- "manual_evaluation_triggered",
- "info",
- None,
- "user",
- None,
- )
- .await;
-
- dispatch_monitoring(pool, state, &directive, &step, &contract, owner_id).await?;
-
- Ok(updated_step)
-}
diff --git a/makima/src/orchestration/mod.rs b/makima/src/orchestration/mod.rs
index e7ffb70..8b13789 100644
--- a/makima/src/orchestration/mod.rs
+++ b/makima/src/orchestration/mod.rs
@@ -1 +1 @@
-pub mod directive;
+
diff --git a/makima/src/server/handlers/contracts.rs b/makima/src/server/handlers/contracts.rs
index ad0a1ff..dc15923 100644
--- a/makima/src/server/handlers/contracts.rs
+++ b/makima/src/server/handlers/contracts.rs
@@ -575,24 +575,6 @@ pub async fn update_contract(
}),
).await;
- // Directive engine integration — process planning/step/monitoring completion
- if contract.is_directive_orchestrator || contract.directive_id.is_some() {
- let pool_clone = pool.clone();
- let state_clone = state.clone();
- let contract_clone = contract.clone();
- let owner = auth.owner_id;
- tokio::spawn(async move {
- if let Err(e) = crate::orchestration::directive::on_contract_completed(
- &pool_clone, &state_clone, &contract_clone, owner,
- ).await {
- tracing::warn!(
- contract_id = %contract_clone.id,
- error = %e,
- "Failed to process directive contract completion"
- );
- }
- });
- }
}
// Get summary with counts
diff --git a/makima/src/server/handlers/directives.rs b/makima/src/server/handlers/directives.rs
deleted file mode 100644
index 3f62a33..0000000
--- a/makima/src/server/handlers/directives.rs
+++ /dev/null
@@ -1,785 +0,0 @@
-//! HTTP handlers for directive CRUD operations.
-
-use axum::{
- extract::{Path, State},
- http::StatusCode,
- response::IntoResponse,
- Json,
-};
-use uuid::Uuid;
-
-use std::collections::HashMap;
-
-use crate::db::models::{
- ChainStep, ChainStepWithContract, ChainWithSteps, CreateDirectiveRequest, Directive,
- DirectiveChain, DirectiveListResponse, DirectiveWithChains, EvaluationListResponse,
- StepContractSummary, SubmitPlanRequest, UpdateDirectiveRequest,
-};
-use crate::db::repository::{self, RepositoryError};
-use crate::orchestration;
-use crate::server::auth::Authenticated;
-use crate::server::messages::ApiError;
-use crate::server::state::SharedState;
-
-/// List all directives for the authenticated user's owner.
-#[utoipa::path(
- get,
- path = "/api/v1/directives",
- responses(
- (status = 200, description = "List of directives", body = DirectiveListResponse),
- (status = 401, description = "Unauthorized", body = ApiError),
- (status = 503, description = "Database not configured", body = ApiError),
- (status = 500, description = "Internal server error", body = ApiError),
- ),
- security(
- ("bearer_auth" = []),
- ("api_key" = [])
- ),
- tag = "Directives"
-)]
-pub async fn list_directives(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- .into_response();
- };
-
- match repository::list_directives_for_owner(pool, auth.owner_id).await {
- Ok(directives) => {
- let total = directives.len() as i64;
- Json(DirectiveListResponse { directives, total }).into_response()
- }
- Err(e) => {
- tracing::error!("Failed to list directives: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Get a directive by ID with its chains.
-#[utoipa::path(
- get,
- path = "/api/v1/directives/{id}",
- params(
- ("id" = Uuid, Path, description = "Directive ID")
- ),
- responses(
- (status = 200, description = "Directive details with chains", body = DirectiveWithChains),
- (status = 401, description = "Unauthorized", body = ApiError),
- (status = 404, description = "Directive not found", body = ApiError),
- (status = 503, description = "Database not configured", body = ApiError),
- (status = 500, description = "Internal server error", body = ApiError),
- ),
- security(
- ("bearer_auth" = []),
- ("api_key" = [])
- ),
- tag = "Directives"
-)]
-pub async fn get_directive(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path(id): Path<Uuid>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- .into_response();
- };
-
- let directive = match repository::get_directive_for_owner(pool, id, auth.owner_id).await {
- Ok(Some(d)) => d,
- Ok(None) => {
- return (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Directive not found")),
- )
- .into_response();
- }
- Err(e) => {
- tracing::error!("Failed to get directive {}: {}", id, e);
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", e.to_string())),
- )
- .into_response();
- }
- };
-
- let chains = match repository::list_chains_for_directive(pool, id).await {
- Ok(c) => c,
- Err(e) => {
- tracing::warn!("Failed to get chains for directive {}: {}", id, e);
- Vec::new()
- }
- };
-
- // Build chains with steps
- let mut all_steps_by_chain = Vec::new();
- for chain in &chains {
- let steps = match repository::list_steps_for_chain(pool, chain.id).await {
- Ok(s) => s,
- Err(e) => {
- tracing::warn!("Failed to get steps for chain {}: {}", chain.id, e);
- Vec::new()
- }
- };
- all_steps_by_chain.push(steps);
- }
-
- // Collect all contract IDs (from steps + orchestrator)
- let mut contract_ids: Vec<Uuid> = all_steps_by_chain
- .iter()
- .flat_map(|steps| steps.iter().filter_map(|s| s.contract_id))
- .collect();
- if let Some(orch_id) = directive.orchestrator_contract_id {
- contract_ids.push(orch_id);
- }
-
- // Batch fetch contract summaries
- let mut summary_map: HashMap<Uuid, StepContractSummary> = if contract_ids.is_empty() {
- HashMap::new()
- } else {
- match repository::get_contract_summaries_batch(pool, &contract_ids).await {
- Ok(summaries) => summaries.into_iter().map(|s| (s.id, s)).collect(),
- Err(e) => {
- tracing::warn!("Failed to fetch contract summaries: {}", e);
- HashMap::new()
- }
- }
- };
-
- // Build enriched chains
- let chains_with_steps: Vec<ChainWithSteps> = chains
- .into_iter()
- .zip(all_steps_by_chain.into_iter())
- .map(|(chain, steps)| {
- let enriched_steps = steps
- .into_iter()
- .map(|step| {
- let contract_summary =
- step.contract_id.and_then(|id| summary_map.remove(&id));
- ChainStepWithContract {
- step,
- contract_summary,
- }
- })
- .collect();
- ChainWithSteps {
- chain,
- steps: enriched_steps,
- }
- })
- .collect();
-
- let orchestrator_contract_summary = directive
- .orchestrator_contract_id
- .and_then(|id| summary_map.remove(&id));
-
- Json(DirectiveWithChains {
- directive,
- orchestrator_contract_summary,
- chains: chains_with_steps,
- })
- .into_response()
-}
-
-/// Create a new directive.
-#[utoipa::path(
- post,
- path = "/api/v1/directives",
- request_body = CreateDirectiveRequest,
- responses(
- (status = 201, description = "Directive created", body = Directive),
- (status = 400, description = "Invalid request", body = ApiError),
- (status = 401, description = "Unauthorized", body = ApiError),
- (status = 503, description = "Database not configured", body = ApiError),
- (status = 500, description = "Internal server error", body = ApiError),
- ),
- security(
- ("bearer_auth" = []),
- ("api_key" = [])
- ),
- tag = "Directives"
-)]
-pub async fn create_directive(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Json(req): Json<CreateDirectiveRequest>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- .into_response();
- };
-
- match repository::create_directive_for_owner(pool, auth.owner_id, req).await {
- Ok(directive) => (StatusCode::CREATED, Json(directive)).into_response(),
- Err(e) => {
- tracing::error!("Failed to create directive: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Update an existing directive.
-#[utoipa::path(
- put,
- path = "/api/v1/directives/{id}",
- params(
- ("id" = Uuid, Path, description = "Directive ID")
- ),
- request_body = UpdateDirectiveRequest,
- responses(
- (status = 200, description = "Directive updated", body = Directive),
- (status = 401, description = "Unauthorized", body = ApiError),
- (status = 404, description = "Directive not found", body = ApiError),
- (status = 409, description = "Version conflict", body = ApiError),
- (status = 503, description = "Database not configured", body = ApiError),
- (status = 500, description = "Internal server error", body = ApiError),
- ),
- security(
- ("bearer_auth" = []),
- ("api_key" = [])
- ),
- tag = "Directives"
-)]
-pub async fn update_directive(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path(id): Path<Uuid>,
- Json(req): Json<UpdateDirectiveRequest>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- .into_response();
- };
-
- match repository::update_directive_for_owner(pool, id, auth.owner_id, req).await {
- Ok(Some(directive)) => Json(directive).into_response(),
- Ok(None) => (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Directive not found")),
- )
- .into_response(),
- Err(RepositoryError::VersionConflict { expected, actual }) => (
- StatusCode::CONFLICT,
- Json(ApiError::new(
- "VERSION_CONFLICT",
- format!(
- "Version conflict: expected {}, actual {}",
- expected, actual
- ),
- )),
- )
- .into_response(),
- Err(RepositoryError::Database(e)) => {
- tracing::error!("Failed to update directive {}: {}", id, e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Delete a directive.
-#[utoipa::path(
- delete,
- path = "/api/v1/directives/{id}",
- params(
- ("id" = Uuid, Path, description = "Directive ID")
- ),
- responses(
- (status = 204, description = "Directive deleted"),
- (status = 401, description = "Unauthorized", body = ApiError),
- (status = 404, description = "Directive not found", body = ApiError),
- (status = 503, description = "Database not configured", body = ApiError),
- (status = 500, description = "Internal server error", body = ApiError),
- ),
- security(
- ("bearer_auth" = []),
- ("api_key" = [])
- ),
- tag = "Directives"
-)]
-pub async fn delete_directive(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path(id): Path<Uuid>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- .into_response();
- };
-
- match repository::delete_directive_for_owner(pool, id, auth.owner_id).await {
- Ok(true) => StatusCode::NO_CONTENT.into_response(),
- Ok(false) => (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Directive not found")),
- )
- .into_response(),
- Err(e) => {
- tracing::error!("Failed to delete directive {}: {}", id, e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// List chains for a directive.
-#[utoipa::path(
- get,
- path = "/api/v1/directives/{id}/chains",
- params(
- ("id" = Uuid, Path, description = "Directive ID")
- ),
- responses(
- (status = 200, description = "List of chains", body = Vec<DirectiveChain>),
- (status = 401, description = "Unauthorized", body = ApiError),
- (status = 404, description = "Directive not found", body = ApiError),
- (status = 503, description = "Database not configured", body = ApiError),
- (status = 500, description = "Internal server error", body = ApiError),
- ),
- security(
- ("bearer_auth" = []),
- ("api_key" = [])
- ),
- tag = "Directives"
-)]
-pub async fn list_chains(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path(id): Path<Uuid>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- .into_response();
- };
-
- // Verify directive exists and belongs to owner
- match repository::get_directive_for_owner(pool, id, auth.owner_id).await {
- Ok(Some(_)) => {}
- Ok(None) => {
- return (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Directive not found")),
- )
- .into_response();
- }
- Err(e) => {
- tracing::error!("Failed to get directive {}: {}", id, e);
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", e.to_string())),
- )
- .into_response();
- }
- }
-
- match repository::list_chains_for_directive(pool, id).await {
- Ok(chains) => Json(chains).into_response(),
- Err(e) => {
- tracing::error!("Failed to list chains for directive {}: {}", id, e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Get a chain with its steps.
-#[utoipa::path(
- get,
- path = "/api/v1/directives/{id}/chains/{chain_id}",
- params(
- ("id" = Uuid, Path, description = "Directive ID"),
- ("chain_id" = Uuid, Path, description = "Chain ID")
- ),
- responses(
- (status = 200, description = "Chain with steps", body = ChainWithSteps),
- (status = 401, description = "Unauthorized", body = ApiError),
- (status = 404, description = "Chain not found", body = ApiError),
- (status = 503, description = "Database not configured", body = ApiError),
- (status = 500, description = "Internal server error", body = ApiError),
- ),
- security(
- ("bearer_auth" = []),
- ("api_key" = [])
- ),
- tag = "Directives"
-)]
-pub async fn get_chain(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path((id, chain_id)): Path<(Uuid, Uuid)>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- .into_response();
- };
-
- // Verify directive exists and belongs to owner
- match repository::get_directive_for_owner(pool, id, auth.owner_id).await {
- Ok(Some(_)) => {}
- Ok(None) => {
- return (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Directive not found")),
- )
- .into_response();
- }
- Err(e) => {
- tracing::error!("Failed to get directive {}: {}", id, e);
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", e.to_string())),
- )
- .into_response();
- }
- }
-
- // Get the chain and verify it belongs to this directive
- let chains = match repository::list_chains_for_directive(pool, id).await {
- Ok(c) => c,
- Err(e) => {
- tracing::error!("Failed to list chains: {}", e);
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", e.to_string())),
- )
- .into_response();
- }
- };
-
- let chain = match chains.into_iter().find(|c| c.id == chain_id) {
- Some(c) => c,
- None => {
- return (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Chain not found")),
- )
- .into_response();
- }
- };
-
- let steps = match repository::list_steps_for_chain(pool, chain_id).await {
- Ok(s) => s,
- Err(e) => {
- tracing::warn!("Failed to get steps for chain {}: {}", chain_id, e);
- Vec::new()
- }
- };
-
- // Collect contract IDs from steps
- let contract_ids: Vec<Uuid> = steps.iter().filter_map(|s| s.contract_id).collect();
-
- let mut summary_map: HashMap<Uuid, StepContractSummary> = if contract_ids.is_empty() {
- HashMap::new()
- } else {
- match repository::get_contract_summaries_batch(pool, &contract_ids).await {
- Ok(summaries) => summaries.into_iter().map(|s| (s.id, s)).collect(),
- Err(e) => {
- tracing::warn!("Failed to fetch contract summaries: {}", e);
- HashMap::new()
- }
- }
- };
-
- let enriched_steps = steps
- .into_iter()
- .map(|step| {
- let contract_summary = step.contract_id.and_then(|id| summary_map.remove(&id));
- ChainStepWithContract {
- step,
- contract_summary,
- }
- })
- .collect();
-
- Json(ChainWithSteps {
- chain,
- steps: enriched_steps,
- })
- .into_response()
-}
-
-/// Start a directive: create a planning contract and begin orchestration.
-#[utoipa::path(
- post,
- path = "/api/v1/directives/{id}/start",
- params(
- ("id" = Uuid, Path, description = "Directive ID")
- ),
- responses(
- (status = 200, description = "Directive started", body = Directive),
- (status = 400, description = "Directive not in draft status", body = ApiError),
- (status = 401, description = "Unauthorized", body = ApiError),
- (status = 404, description = "Directive not found", body = ApiError),
- (status = 503, description = "Database not configured", body = ApiError),
- (status = 500, description = "Internal server error", body = ApiError),
- ),
- security(
- ("bearer_auth" = []),
- ("api_key" = [])
- ),
- tag = "Directives"
-)]
-pub async fn start_directive(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path(id): Path<Uuid>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- .into_response();
- };
-
- match orchestration::directive::init_directive(pool, &state, auth.owner_id, id).await {
- Ok(directive) => Json(directive).into_response(),
- Err(e) if e.contains("not found") => (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", e)),
- )
- .into_response(),
- Err(e) if e.contains("must be in 'draft'") => (
- StatusCode::BAD_REQUEST,
- Json(ApiError::new("INVALID_STATUS", e)),
- )
- .into_response(),
- Err(e) => {
- tracing::error!("Failed to start directive {}: {}", id, e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("START_FAILED", e)),
- )
- .into_response()
- }
- }
-}
-
-/// Trigger a manual evaluation for a step.
-#[utoipa::path(
- post,
- path = "/api/v1/directives/{id}/steps/{step_id}/evaluate",
- params(
- ("id" = Uuid, Path, description = "Directive ID"),
- ("step_id" = Uuid, Path, description = "Step ID")
- ),
- responses(
- (status = 200, description = "Evaluation triggered", body = ChainStep),
- (status = 400, description = "Step cannot be evaluated", body = ApiError),
- (status = 401, description = "Unauthorized", body = ApiError),
- (status = 404, description = "Not found", body = ApiError),
- (status = 503, description = "Database not configured", body = ApiError),
- (status = 500, description = "Internal server error", body = ApiError),
- ),
- security(
- ("bearer_auth" = []),
- ("api_key" = [])
- ),
- tag = "Directives"
-)]
-pub async fn evaluate_step(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path((id, step_id)): Path<(Uuid, Uuid)>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- .into_response();
- };
-
- match orchestration::directive::trigger_manual_evaluation(pool, &state, auth.owner_id, id, step_id).await {
- Ok(step) => Json(step).into_response(),
- Err(e) if e.contains("not found") || e.contains("Not found") => (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", e)),
- )
- .into_response(),
- Err(e) if e.contains("hasn't been executed") || e.contains("no active chain") => (
- StatusCode::BAD_REQUEST,
- Json(ApiError::new("INVALID_STATE", e)),
- )
- .into_response(),
- Err(e) => {
- tracing::error!("Failed to trigger evaluation for step {}: {}", step_id, e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("EVALUATION_FAILED", e)),
- )
- .into_response()
- }
- }
-}
-
-/// List evaluations for a step.
-#[utoipa::path(
- get,
- path = "/api/v1/directives/{id}/steps/{step_id}/evaluations",
- params(
- ("id" = Uuid, Path, description = "Directive ID"),
- ("step_id" = Uuid, Path, description = "Step ID")
- ),
- responses(
- (status = 200, description = "List of evaluations", body = EvaluationListResponse),
- (status = 401, description = "Unauthorized", body = ApiError),
- (status = 404, description = "Not found", body = ApiError),
- (status = 503, description = "Database not configured", body = ApiError),
- (status = 500, description = "Internal server error", body = ApiError),
- ),
- security(
- ("bearer_auth" = []),
- ("api_key" = [])
- ),
- tag = "Directives"
-)]
-pub async fn list_evaluations(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path((id, step_id)): Path<(Uuid, Uuid)>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- .into_response();
- };
-
- // Verify directive exists and belongs to owner
- match repository::get_directive_for_owner(pool, id, auth.owner_id).await {
- Ok(Some(_)) => {}
- Ok(None) => {
- return (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Directive not found")),
- )
- .into_response();
- }
- Err(e) => {
- tracing::error!("Failed to get directive {}: {}", id, e);
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", e.to_string())),
- )
- .into_response();
- }
- }
-
- match repository::list_evaluations_for_step(pool, step_id).await {
- Ok(evaluations) => {
- let total = evaluations.len() as i64;
- Json(EvaluationListResponse { evaluations, total }).into_response()
- }
- Err(e) => {
- tracing::error!("Failed to list evaluations for step {}: {}", step_id, e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Submit a chain plan for a directive.
-#[utoipa::path(
- post,
- path = "/api/v1/directives/{id}/submit-plan",
- params(
- ("id" = Uuid, Path, description = "Directive ID")
- ),
- request_body = SubmitPlanRequest,
- responses(
- (status = 200, description = "Plan submitted, directive active", body = Directive),
- (status = 400, description = "Invalid request or status", body = ApiError),
- (status = 401, description = "Unauthorized", body = ApiError),
- (status = 404, description = "Directive not found", body = ApiError),
- (status = 503, description = "Database not configured", body = ApiError),
- (status = 500, description = "Internal server error", body = ApiError),
- ),
- security(
- ("bearer_auth" = []),
- ("api_key" = [])
- ),
- tag = "Directives"
-)]
-pub async fn submit_plan(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path(id): Path<Uuid>,
- Json(req): Json<SubmitPlanRequest>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- .into_response();
- };
-
- match orchestration::directive::submit_plan(pool, &state, auth.owner_id, id, &req.plan).await {
- Ok(directive) => Json(directive).into_response(),
- Err(e) if e.contains("not found") || e.contains("Not found") => (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", e)),
- )
- .into_response(),
- Err(e) if e.contains("must be in 'planning'") || e.contains("no steps") => (
- StatusCode::BAD_REQUEST,
- Json(ApiError::new("INVALID_REQUEST", e)),
- )
- .into_response(),
- Err(e) => {
- tracing::error!("Failed to submit plan for directive {}: {}", id, e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("SUBMIT_PLAN_FAILED", e)),
- )
- .into_response()
- }
- }
-}
diff --git a/makima/src/server/handlers/mesh_daemon.rs b/makima/src/server/handlers/mesh_daemon.rs
index 767d059..87b5e44 100644
--- a/makima/src/server/handlers/mesh_daemon.rs
+++ b/makima/src/server/handlers/mesh_daemon.rs
@@ -1303,16 +1303,6 @@ async fn handle_daemon_connection(socket: WebSocket, state: SharedState, auth_re
}),
).await;
- // Directive engine integration
- if let Err(e) = crate::orchestration::directive::on_task_completed(
- &pool, &state, &updated_task, owner_id,
- ).await {
- tracing::warn!(
- task_id = %task_id,
- error = %e,
- "Failed to process directive task completion"
- );
- }
}
Ok(None) => {
tracing::warn!(
diff --git a/makima/src/server/handlers/mod.rs b/makima/src/server/handlers/mod.rs
index 29cd09f..ae370c9 100644
--- a/makima/src/server/handlers/mod.rs
+++ b/makima/src/server/handlers/mod.rs
@@ -6,7 +6,6 @@ pub mod contract_chat;
pub mod contract_daemon;
pub mod contract_discuss;
pub mod contracts;
-pub mod directives;
pub mod file_ws;
pub mod files;
pub mod history;
diff --git a/makima/src/server/mod.rs b/makima/src/server/mod.rs
index 0cad050..b7a4156 100644
--- a/makima/src/server/mod.rs
+++ b/makima/src/server/mod.rs
@@ -18,7 +18,7 @@ use tower_http::trace::TraceLayer;
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
-use crate::server::handlers::{api_keys, chat, contract_chat, contract_daemon, contract_discuss, contracts, directives, file_ws, files, history, listen, mesh, mesh_chat, mesh_daemon, mesh_merge, mesh_supervisor, mesh_ws, repository_history, speak, templates, transcript_analysis, users, versions};
+use crate::server::handlers::{api_keys, chat, contract_chat, contract_daemon, contract_discuss, contracts, file_ws, files, history, listen, mesh, mesh_chat, mesh_daemon, mesh_merge, mesh_supervisor, mesh_ws, repository_history, speak, templates, transcript_analysis, users, versions};
use crate::server::openapi::ApiDoc;
use crate::server::state::SharedState;
@@ -170,23 +170,6 @@ pub fn make_router(state: SharedState) -> Router {
"/contracts/{id}/chat/history",
get(contract_chat::get_contract_chat_history).delete(contract_chat::clear_contract_chat_history),
)
- // Directive endpoints
- .route(
- "/directives",
- get(directives::list_directives).post(directives::create_directive),
- )
- .route(
- "/directives/{id}",
- get(directives::get_directive)
- .put(directives::update_directive)
- .delete(directives::delete_directive),
- )
- .route("/directives/{id}/start", post(directives::start_directive))
- .route("/directives/{id}/chains", get(directives::list_chains))
- .route("/directives/{id}/chains/{chain_id}", get(directives::get_chain))
- .route("/directives/{id}/submit-plan", post(directives::submit_plan))
- .route("/directives/{id}/steps/{step_id}/evaluate", post(directives::evaluate_step))
- .route("/directives/{id}/steps/{step_id}/evaluations", get(directives::list_evaluations))
// Contract supervisor resume endpoints
.route("/contracts/{id}/supervisor/resume", post(mesh_supervisor::resume_supervisor))
.route("/contracts/{id}/supervisor/conversation/rewind", post(mesh_supervisor::rewind_conversation))
diff --git a/makima/src/server/openapi.rs b/makima/src/server/openapi.rs
index 888269f..0b6bfba 100644
--- a/makima/src/server/openapi.rs
+++ b/makima/src/server/openapi.rs
@@ -4,28 +4,27 @@ use utoipa::OpenApi;
use crate::db::models::{
AddLocalRepositoryRequest, AddRemoteRepositoryRequest, BranchInfo, BranchListResponse,
- BranchTaskRequest, BranchTaskResponse, ChainStep, ChainStepWithContract, ChainWithSteps,
+ BranchTaskRequest, BranchTaskResponse,
ChangePhaseRequest,
Contract, ContractChatHistoryResponse, ContractChatMessageRecord, ContractEvent,
ContractListResponse, ContractRepository, ContractSummary, ContractWithRelations,
- CreateContractRequest, CreateDirectiveRequest, CreateFileRequest,
+ CreateContractRequest, CreateFileRequest,
CreateManagedRepositoryRequest, CreateTaskRequest, Daemon, DaemonDirectoriesResponse,
- DaemonDirectory, DaemonListResponse, Directive, DirectiveChain, DirectiveEvaluation,
- DirectiveEvent, DirectiveListResponse, DirectiveSummary, DirectiveWithChains,
- EvaluationListResponse, File, FileListResponse, FileSummary,
+ DaemonDirectory, DaemonListResponse,
+ File, FileListResponse, FileSummary,
MergeCommitRequest, MergeCompleteCheckResponse, MergeResolveRequest, MergeResultResponse,
MergeSkipRequest, MergeStartRequest, MergeStatusResponse, MeshChatConversation,
MeshChatHistoryResponse, MeshChatMessageRecord, RepositoryHistoryEntry,
RepositoryHistoryListResponse, RepositorySuggestionsQuery, SendMessageRequest,
- StepContractSummary, SubmitPlanRequest, Task,
+ Task,
TaskEventListResponse, TaskListResponse, TaskSummary, TaskWithSubtasks, TranscriptEntry,
- UpdateContractRequest, UpdateDirectiveRequest, UpdateFileRequest, UpdateTaskRequest,
+ UpdateContractRequest, UpdateFileRequest, UpdateTaskRequest,
};
use crate::server::auth::{
ApiKey, ApiKeyInfoResponse, CreateApiKeyRequest, CreateApiKeyResponse,
RefreshApiKeyRequest, RefreshApiKeyResponse, RevokeApiKeyResponse,
};
-use crate::server::handlers::{api_keys, contract_chat, contract_discuss, contracts, directives, files, listen, mesh, mesh_chat, mesh_merge, repository_history, users};
+use crate::server::handlers::{api_keys, contract_chat, contract_discuss, contracts, files, listen, mesh, mesh_chat, mesh_merge, repository_history, users};
use crate::server::messages::{ApiError, AudioEncoding, StartMessage, StopMessage, TranscriptMessage};
#[derive(OpenApi)]
@@ -108,18 +107,6 @@ use crate::server::messages::{ApiError, AudioEncoding, StartMessage, StopMessage
repository_history::list_repository_history,
repository_history::get_repository_suggestions,
repository_history::delete_repository_history,
- // Directive endpoints
- directives::list_directives,
- directives::get_directive,
- directives::create_directive,
- directives::update_directive,
- directives::delete_directive,
- directives::start_directive,
- directives::list_chains,
- directives::get_chain,
- directives::evaluate_step,
- directives::list_evaluations,
- directives::submit_plan,
),
components(
schemas(
@@ -204,22 +191,6 @@ use crate::server::messages::{ApiError, AudioEncoding, StartMessage, StopMessage
RepositoryHistoryEntry,
RepositoryHistoryListResponse,
RepositorySuggestionsQuery,
- // Directive schemas
- Directive,
- DirectiveSummary,
- DirectiveListResponse,
- DirectiveWithChains,
- DirectiveChain,
- ChainStep,
- ChainStepWithContract,
- ChainWithSteps,
- StepContractSummary,
- CreateDirectiveRequest,
- UpdateDirectiveRequest,
- SubmitPlanRequest,
- DirectiveEvaluation,
- DirectiveEvent,
- EvaluationListResponse,
)
),
tags(
@@ -230,7 +201,6 @@ use crate::server::messages::{ApiError, AudioEncoding, StartMessage, StopMessage
(name = "API Keys", description = "API key management for programmatic access"),
(name = "Users", description = "User account management"),
(name = "Settings", description = "User settings including repository history"),
- (name = "Directives", description = "Directive management for autonomous goal-driven execution"),
)
)]
pub struct ApiDoc;