summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-07 00:01:50 +0000
committersoryu <soryu@soryu.co>2026-02-07 00:01:50 +0000
commitb8d563d45f14a2b1db1f684aa0a8dcd7e5b6ad56 (patch)
tree95543fd150270018e384fbcf9d3df3dc45f052f6
parentcececbf326e258211ceae7afce716a5d1e46014f (diff)
downloadsoryu-b8d563d45f14a2b1db1f684aa0a8dcd7e5b6ad56.tar.gz
soryu-b8d563d45f14a2b1db1f684aa0a8dcd7e5b6ad56.zip
Remove directives for reimplementation
-rw-r--r--makima/frontend/src/components/NavStrip.tsx1
-rw-r--r--makima/frontend/src/components/directives/ApprovalsTab.tsx77
-rw-r--r--makima/frontend/src/components/directives/ChainTab.tsx212
-rw-r--r--makima/frontend/src/components/directives/CreateDirectiveModal.tsx146
-rw-r--r--makima/frontend/src/components/directives/DirectiveDetail.tsx160
-rw-r--r--makima/frontend/src/components/directives/DirectiveList.tsx85
-rw-r--r--makima/frontend/src/components/directives/DirectiveListItem.tsx83
-rw-r--r--makima/frontend/src/components/directives/EvaluationsTab.tsx12
-rw-r--r--makima/frontend/src/components/directives/EventsTab.tsx77
-rw-r--r--makima/frontend/src/components/directives/OverviewTab.tsx73
-rw-r--r--makima/frontend/src/components/directives/StepNode.tsx87
-rw-r--r--makima/frontend/src/components/directives/VerifiersTab.tsx12
-rw-r--r--makima/frontend/src/components/directives/index.ts11
-rw-r--r--makima/frontend/src/hooks/useDirectiveDetail.ts125
-rw-r--r--makima/frontend/src/hooks/useDirectives.ts298
-rw-r--r--makima/frontend/src/lib/api.ts1283
-rw-r--r--makima/frontend/src/main.tsx17
-rw-r--r--makima/frontend/src/routes/directives.tsx184
-rw-r--r--makima/frontend/tsconfig.tsbuildinfo2
-rw-r--r--makima/migrations/20260207000000_directive_v2_wipe.sql331
-rw-r--r--makima/src/bin/makima.rs151
-rw-r--r--makima/src/daemon/api/directive.rs447
-rw-r--r--makima/src/daemon/api/mod.rs1
-rw-r--r--makima/src/daemon/cli/directive.rs186
-rw-r--r--makima/src/daemon/cli/mod.rs56
-rw-r--r--makima/src/daemon/skills/directive.md303
-rw-r--r--makima/src/daemon/skills/mod.rs4
-rw-r--r--makima/src/db/models.rs666
-rw-r--r--makima/src/db/repository.rs1169
-rw-r--r--makima/src/lib.rs1
-rw-r--r--makima/src/orchestration/engine.rs1335
-rw-r--r--makima/src/orchestration/mod.rs26
-rw-r--r--makima/src/orchestration/planner.rs848
-rw-r--r--makima/src/orchestration/verifier.rs833
-rw-r--r--makima/src/server/handlers/contracts.rs85
-rw-r--r--makima/src/server/handlers/directives.rs2116
-rw-r--r--makima/src/server/handlers/mesh_daemon.rs15
-rw-r--r--makima/src/server/handlers/mod.rs2
-rw-r--r--makima/src/server/mod.rs57
39 files changed, 336 insertions, 11241 deletions
diff --git a/makima/frontend/src/components/NavStrip.tsx b/makima/frontend/src/components/NavStrip.tsx
index 9bb7777..fb95c7f 100644
--- a/makima/frontend/src/components/NavStrip.tsx
+++ b/makima/frontend/src/components/NavStrip.tsx
@@ -10,7 +10,6 @@ interface NavLink {
const NAV_LINKS: NavLink[] = [
{ label: "Listen", href: "/listen" },
- { label: "Directives", href: "/directives", requiresAuth: true },
{ label: "Contracts", href: "/contracts", requiresAuth: true },
{ label: "Board", href: "/workflow", requiresAuth: true },
{ label: "Mesh", href: "/mesh", requiresAuth: true },
diff --git a/makima/frontend/src/components/directives/ApprovalsTab.tsx b/makima/frontend/src/components/directives/ApprovalsTab.tsx
deleted file mode 100644
index dca48df..0000000
--- a/makima/frontend/src/components/directives/ApprovalsTab.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import type { DirectiveWithProgress } from "../../lib/api";
-
-export function ApprovalsTab({ directive, onRefresh }: { directive: DirectiveWithProgress; onRefresh: () => void }) {
- if (directive.pendingApprovals.length === 0) {
- return (
- <div className="text-center py-8">
- <p className="font-mono text-sm text-[#556677]">No pending approvals</p>
- </div>
- );
- }
-
- const handleApprove = async (approvalId: string) => {
- try {
- const { approveDirectiveRequest } = await import("../../lib/api");
- await approveDirectiveRequest(directive.id, approvalId);
- onRefresh();
- } catch (err) {
- console.error("Failed to approve:", err);
- }
- };
-
- const handleDeny = async (approvalId: string) => {
- try {
- const { denyDirectiveRequest } = await import("../../lib/api");
- await denyDirectiveRequest(directive.id, approvalId);
- onRefresh();
- } catch (err) {
- console.error("Failed to deny:", err);
- }
- };
-
- return (
- <div className="space-y-3">
- {directive.pendingApprovals.map((approval) => {
- const urgencyColor = {
- low: "text-[#556677]",
- normal: "text-[#75aafc]",
- high: "text-yellow-400",
- critical: "text-red-400",
- }[approval.urgency] || "text-[#556677]";
-
- return (
- <div
- key={approval.id}
- className="p-4 bg-[rgba(117,170,252,0.05)] border border-[rgba(117,170,252,0.2)]"
- >
- <div className="flex items-start justify-between">
- <div>
- <div className="flex items-center gap-2">
- <span className="font-mono text-sm text-[#dbe7ff]">{approval.approvalType}</span>
- <span className={`font-mono text-[10px] uppercase ${urgencyColor}`}>
- {approval.urgency}
- </span>
- </div>
- <p className="font-mono text-xs text-[#9bc3ff] mt-1">{approval.description}</p>
- </div>
- <div className="flex gap-2">
- <button
- onClick={() => handleApprove(approval.id)}
- className="px-3 py-1 font-mono text-[10px] text-[#dbe7ff] bg-green-700 border border-green-600 hover:bg-green-600 uppercase"
- >
- Approve
- </button>
- <button
- onClick={() => handleDeny(approval.id)}
- className="px-3 py-1 font-mono text-[10px] text-[#dbe7ff] bg-red-700 border border-red-600 hover:bg-red-600 uppercase"
- >
- Deny
- </button>
- </div>
- </div>
- </div>
- );
- })}
- </div>
- );
-}
diff --git a/makima/frontend/src/components/directives/ChainTab.tsx b/makima/frontend/src/components/directives/ChainTab.tsx
deleted file mode 100644
index ccefe81..0000000
--- a/makima/frontend/src/components/directives/ChainTab.tsx
+++ /dev/null
@@ -1,212 +0,0 @@
-import { useState, useMemo } from "react";
-import {
- ReactFlow,
- Edge,
- Controls,
- Background,
- BackgroundVariant,
- MarkerType,
-} from "@xyflow/react";
-import "@xyflow/react/dist/style.css";
-import type { DirectiveWithProgress, DirectiveGraphResponse } from "../../lib/api";
-import { StepNodeComponent, stepStatusStyles } from "./StepNode";
-
-// Node types for React Flow
-const nodeTypes = {
- step: StepNodeComponent,
-};
-
-export function ChainTab({ directive, graph }: { directive: DirectiveWithProgress; graph: DirectiveGraphResponse | null }) {
- const [viewMode, setViewMode] = useState<"dag" | "list">("dag");
-
- // Convert graph to React Flow nodes and edges
- const { nodes, edges } = useMemo(() => {
- if (!graph || !graph.nodes.length) {
- // Fallback: generate positions from directive.steps
- const stepNodes = directive.steps.map((step, index) => ({
- id: step.id,
- type: "step" as const,
- position: {
- x: (index % 3) * 220 + 50,
- y: Math.floor(index / 3) * 120 + 50,
- },
- data: {
- id: step.id,
- name: step.name,
- stepType: step.stepType,
- status: step.status,
- confidenceScore: step.confidenceScore,
- confidenceLevel: step.confidenceLevel,
- contractId: step.contractId,
- editorX: null,
- editorY: null,
- },
- }));
-
- // Build edges from dependencies
- const stepEdges: Edge[] = [];
- directive.steps.forEach((step) => {
- (step.dependsOn ?? []).forEach((depName) => {
- const depStep = directive.steps.find((s) => s.name === depName);
- if (depStep) {
- stepEdges.push({
- id: `${depStep.id}-${step.id}`,
- source: depStep.id,
- target: step.id,
- type: "smoothstep",
- markerEnd: { type: MarkerType.ArrowClosed, color: "#556677" },
- style: { stroke: "#556677", strokeWidth: 2 },
- });
- }
- });
- });
-
- return { nodes: stepNodes, edges: stepEdges };
- }
-
- // Use graph data
- const graphNodes = graph.nodes.map((node) => ({
- id: node.id,
- type: "step" as const,
- position: {
- x: node.editorX ?? 50,
- y: node.editorY ?? 50,
- },
- data: { ...node },
- }));
-
- const graphEdges: Edge[] = graph.edges.map((edge) => ({
- id: `${edge.source}-${edge.target}`,
- source: edge.source,
- target: edge.target,
- type: "smoothstep",
- markerEnd: { type: MarkerType.ArrowClosed, color: "#556677" },
- style: { stroke: "#556677", strokeWidth: 2 },
- }));
-
- return { nodes: graphNodes, edges: graphEdges };
- }, [graph, directive.steps]);
-
- if (!directive.chain) {
- return (
- <div className="text-center py-8">
- <p className="font-mono text-sm text-[#556677]">
- No chain generated yet. Start the directive to generate a chain.
- </p>
- </div>
- );
- }
-
- return (
- <div className="space-y-4">
- {/* Chain info header */}
- <div className="flex items-center justify-between p-3 bg-[rgba(117,170,252,0.05)] border border-[rgba(117,170,252,0.15)]">
- <div>
- <div className="font-mono text-sm text-[#dbe7ff]">{directive.chain.name}</div>
- <div className="font-mono text-[10px] text-[#556677]">Generation {directive.chain.generation}</div>
- </div>
- <div className="flex items-center gap-4">
- <div className="font-mono text-xs text-[#556677]">
- {directive.chain.completedSteps}/{directive.chain.totalSteps} steps
- </div>
- {/* View toggle */}
- <div className="flex border border-[rgba(117,170,252,0.2)]">
- <button
- onClick={() => setViewMode("dag")}
- className={`px-2 py-1 font-mono text-[10px] uppercase ${
- viewMode === "dag"
- ? "bg-[rgba(117,170,252,0.2)] text-[#75aafc]"
- : "text-[#556677] hover:text-[#75aafc]"
- }`}
- >
- DAG
- </button>
- <button
- onClick={() => setViewMode("list")}
- className={`px-2 py-1 font-mono text-[10px] uppercase ${
- viewMode === "list"
- ? "bg-[rgba(117,170,252,0.2)] text-[#75aafc]"
- : "text-[#556677] hover:text-[#75aafc]"
- }`}
- >
- List
- </button>
- </div>
- </div>
- </div>
-
- {viewMode === "dag" ? (
- /* DAG visualization */
- <div className="h-[400px] border border-[rgba(117,170,252,0.1)] rounded bg-[#050d18]">
- {directive.steps.length === 0 ? (
- <div className="flex items-center justify-center h-full">
- <p className="font-mono text-sm text-[#556677]">No steps in chain</p>
- </div>
- ) : (
- <ReactFlow
- nodes={nodes}
- edges={edges}
- nodeTypes={nodeTypes}
- fitView
- fitViewOptions={{ padding: 0.2 }}
- minZoom={0.5}
- maxZoom={1.5}
- defaultEdgeOptions={{
- type: "smoothstep",
- style: { stroke: "#556677", strokeWidth: 2 },
- }}
- >
- <Background variant={BackgroundVariant.Dots} gap={20} size={1} color="#1a2a3a" />
- <Controls className="!bg-[#0a1628] !border-[rgba(117,170,252,0.2)]" />
- </ReactFlow>
- )}
- </div>
- ) : (
- /* List view */
- <div className="space-y-2">
- <h3 className="font-mono text-xs text-[#75aafc] uppercase">Steps</h3>
- {directive.steps.length === 0 ? (
- <p className="font-mono text-sm text-[#556677]">No steps in chain</p>
- ) : (
- directive.steps.map((step) => {
- const styles = stepStatusStyles[step.status] || stepStatusStyles.pending;
-
- return (
- <div
- key={step.id}
- className="p-3 bg-[rgba(117,170,252,0.02)] border border-[rgba(117,170,252,0.1)]"
- >
- <div className="flex items-center justify-between">
- <div className="flex items-center gap-2">
- <span className="font-mono text-sm text-[#dbe7ff]">{step.name}</span>
- <span
- className="px-1.5 py-0.5 font-mono text-[10px] uppercase border"
- style={{ color: styles.text, borderColor: `${styles.border}50` }}
- >
- {step.status}
- </span>
- {step.confidenceScore !== null && (
- <span className="font-mono text-[10px] text-[#556677]">
- ({Math.round(step.confidenceScore * 100)}%)
- </span>
- )}
- </div>
- <div className="font-mono text-[10px] text-[#556677]">{step.stepType}</div>
- </div>
- {step.description && (
- <p className="font-mono text-xs text-[#556677] mt-1">{step.description}</p>
- )}
- {step.dependsOn?.length > 0 && (
- <div className="font-mono text-[10px] text-[#556677] mt-1">
- Depends on: {step.dependsOn.join(", ")}
- </div>
- )}
- </div>
- );
- })
- )}
- </div>
- )}
- </div>
- );
-}
diff --git a/makima/frontend/src/components/directives/CreateDirectiveModal.tsx b/makima/frontend/src/components/directives/CreateDirectiveModal.tsx
deleted file mode 100644
index 7f52a7e..0000000
--- a/makima/frontend/src/components/directives/CreateDirectiveModal.tsx
+++ /dev/null
@@ -1,146 +0,0 @@
-import { useState, useEffect } from "react";
-import type { AutonomyLevel, RepositoryHistoryEntry } from "../../lib/api";
-import { getRepositorySuggestions } from "../../lib/api";
-
-interface CreateDirectiveModalProps {
- onSubmit: (goal: string, repositoryUrl: string | undefined, autonomyLevel: AutonomyLevel) => void;
- onCancel: () => void;
-}
-
-export function CreateDirectiveModal({ onSubmit, onCancel }: CreateDirectiveModalProps) {
- const [goal, setGoal] = useState("");
- const [repositoryUrl, setRepositoryUrl] = useState("");
- const [autonomyLevel, setAutonomyLevel] = useState<AutonomyLevel>("guardrails");
- const [suggestions, setSuggestions] = useState<RepositoryHistoryEntry[]>([]);
- const [showSuggestions, setShowSuggestions] = useState(false);
-
- // Load suggestions
- useEffect(() => {
- getRepositorySuggestions("remote", undefined, 5)
- .then((res) => {
- setSuggestions(res.entries);
- })
- .catch(() => {
- setSuggestions([]);
- });
- }, []);
-
- const handleSubmit = () => {
- if (goal.trim()) {
- onSubmit(goal.trim(), repositoryUrl.trim() || undefined, autonomyLevel);
- }
- };
-
- return (
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
- <div className="w-full max-w-lg p-6 bg-[#0a1628] border border-[rgba(117,170,252,0.3)] max-h-[90vh] overflow-y-auto">
- <h3 className="font-mono text-sm text-[#75aafc] uppercase mb-4">
- Create Directive
- </h3>
-
- <div className="space-y-4">
- {/* Goal */}
- <div>
- <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
- Goal *
- </label>
- <textarea
- value={goal}
- onChange={(e) => setGoal(e.target.value)}
- placeholder="Describe what you want to accomplish..."
- rows={3}
- className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc] resize-none"
- autoFocus
- />
- </div>
-
- {/* Repository URL */}
- <div>
- <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
- Repository URL (optional)
- </label>
- <div className="relative">
- <input
- type="text"
- value={repositoryUrl}
- onChange={(e) => setRepositoryUrl(e.target.value)}
- onFocus={() => suggestions.length > 0 && setShowSuggestions(true)}
- onBlur={() => setTimeout(() => setShowSuggestions(false), 200)}
- placeholder="https://github.com/owner/repo"
- className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]"
- />
- {showSuggestions && suggestions.length > 0 && (
- <div className="absolute top-full left-0 right-0 mt-1 border border-[rgba(117,170,252,0.2)] bg-[#0a1525] max-h-32 overflow-y-auto z-10">
- {suggestions.map((s) => (
- <button
- key={s.id}
- type="button"
- onClick={() => {
- setRepositoryUrl(s.repositoryUrl || "");
- setShowSuggestions(false);
- }}
- 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="text-[#9bc3ff] truncate">{s.name}</div>
- <div className="text-[10px] text-[#556677] truncate">{s.repositoryUrl}</div>
- </button>
- ))}
- </div>
- )}
- </div>
- </div>
-
- {/* Autonomy Level */}
- <div>
- <label className="block font-mono text-xs text-[#8b949e] uppercase mb-2">
- Autonomy Level
- </label>
- <div className="flex gap-2">
- {(["full_auto", "guardrails", "manual"] as const).map((level) => (
- <button
- key={level}
- type="button"
- onClick={() => setAutonomyLevel(level)}
- className={`flex-1 px-3 py-2 font-mono text-xs uppercase ${
- autonomyLevel === level
- ? "text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3]"
- : "text-[#556677] border border-[rgba(117,170,252,0.2)] hover:border-[#3f6fb3]"
- }`}
- >
- {level.replace("_", " ")}
- </button>
- ))}
- </div>
- <p className="font-mono text-[10px] text-[#556677] mt-1">
- {autonomyLevel === "full_auto" && "Automatic progression without approval gates"}
- {autonomyLevel === "guardrails" && "Request approval for yellow/red confidence scores"}
- {autonomyLevel === "manual" && "Request approval for all step completions"}
- </p>
- </div>
-
- <p className="font-mono text-xs text-[#8b949e]">
- A directive is a top-level goal that generates a chain of steps. Each step spawns
- contracts that are verified before progression.
- </p>
-
- {/* Actions */}
- <div className="flex gap-2 justify-end pt-2">
- <button
- onClick={onCancel}
- className="px-4 py-2 font-mono text-xs text-[#9bc3ff] hover:text-[#dbe7ff] transition-colors"
- >
- Cancel
- </button>
- <button
- onClick={handleSubmit}
- disabled={!goal.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>
- );
-}
diff --git a/makima/frontend/src/components/directives/DirectiveDetail.tsx b/makima/frontend/src/components/directives/DirectiveDetail.tsx
deleted file mode 100644
index 06b24bb..0000000
--- a/makima/frontend/src/components/directives/DirectiveDetail.tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-import { useState } from "react";
-import type { DirectiveWithProgress, DirectiveGraphResponse } from "../../lib/api";
-import { OverviewTab } from "./OverviewTab";
-import { ChainTab } from "./ChainTab";
-import { EventsTab } from "./EventsTab";
-import { EvaluationsTab } from "./EvaluationsTab";
-import { ApprovalsTab } from "./ApprovalsTab";
-import { VerifiersTab } from "./VerifiersTab";
-
-interface DirectiveDetailProps {
- directive: DirectiveWithProgress;
- graph: DirectiveGraphResponse | null;
- loading: boolean;
- onBack: () => void;
- onRefresh: () => void;
- onStart: () => void;
- onPause: () => void;
- onResume: () => void;
- onStop: () => void;
-}
-
-export function DirectiveDetail({
- directive,
- graph,
- loading,
- onBack,
- onRefresh,
- onStart,
- onPause,
- onResume,
- onStop,
-}: DirectiveDetailProps) {
- const [activeTab, setActiveTab] = useState<"overview" | "chain" | "events" | "evaluations" | "approvals" | "verifiers">("overview");
-
- if (loading) {
- return (
- <div className="panel h-full flex items-center justify-center">
- <p className="font-mono text-xs text-[#556677]">Loading...</p>
- </div>
- );
- }
-
- const statusColor = {
- draft: "text-[#556677] bg-[#556677]/10 border-[#556677]/30",
- planning: "text-yellow-400 bg-yellow-400/10 border-yellow-400/30",
- active: "text-green-400 bg-green-400/10 border-green-400/30",
- paused: "text-yellow-400 bg-yellow-400/10 border-yellow-400/30",
- completed: "text-[#75aafc] bg-[#75aafc]/10 border-[#75aafc]/30",
- archived: "text-[#556677] bg-[#556677]/10 border-[#556677]/30",
- failed: "text-red-400 bg-red-400/10 border-red-400/30",
- }[directive.status] || "text-[#556677] bg-[#556677]/10 border-[#556677]/30";
-
- return (
- <div className="panel h-full flex flex-col overflow-hidden">
- {/* Header */}
- <div className="p-3 border-b border-[rgba(117,170,252,0.15)]">
- <div className="flex items-center justify-between">
- <div className="flex items-center gap-3">
- <button
- onClick={onBack}
- className="font-mono text-xs text-[#556677] hover:text-[#9bc3ff]"
- >
- &larr; Back
- </button>
- <h2 className="font-mono text-sm text-[#dbe7ff]">
- {directive.title || directive.goal.slice(0, 50)}
- </h2>
- <span className={`px-2 py-0.5 font-mono text-[10px] uppercase border ${statusColor}`}>
- {directive.status}
- </span>
- </div>
- <div className="flex items-center gap-2">
- {directive.status === "draft" && (
- <button
- onClick={onStart}
- className="px-3 py-1 font-mono text-[10px] text-[#dbe7ff] bg-green-700 border border-green-600 hover:bg-green-600 uppercase"
- >
- Start
- </button>
- )}
- {directive.status === "active" && (
- <button
- onClick={onPause}
- className="px-3 py-1 font-mono text-[10px] text-[#dbe7ff] bg-yellow-700 border border-yellow-600 hover:bg-yellow-600 uppercase"
- >
- Pause
- </button>
- )}
- {directive.status === "paused" && (
- <button
- onClick={onResume}
- className="px-3 py-1 font-mono text-[10px] text-[#dbe7ff] bg-green-700 border border-green-600 hover:bg-green-600 uppercase"
- >
- Resume
- </button>
- )}
- {["active", "paused"].includes(directive.status) && (
- <button
- onClick={onStop}
- className="px-3 py-1 font-mono text-[10px] text-[#dbe7ff] bg-red-700 border border-red-600 hover:bg-red-600 uppercase"
- >
- Stop
- </button>
- )}
- <button
- onClick={onRefresh}
- className="px-3 py-1 font-mono text-[10px] text-[#9bc3ff] hover:text-[#dbe7ff]"
- >
- Refresh
- </button>
- </div>
- </div>
- </div>
-
- {/* Tabs */}
- <div className="flex gap-1 p-2 border-b border-[rgba(117,170,252,0.1)]">
- {(["overview", "chain", "events", "evaluations", "approvals", "verifiers"] as const).map((tab) => (
- <button
- key={tab}
- onClick={() => setActiveTab(tab)}
- className={`px-3 py-1.5 font-mono text-[10px] uppercase ${
- activeTab === tab
- ? "text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3]"
- : "text-[#556677] hover:text-[#9bc3ff]"
- }`}
- >
- {tab}
- {tab === "approvals" && directive.pendingApprovals.length > 0 && (
- <span className="ml-1 px-1 bg-yellow-500 text-black rounded text-[8px]">
- {directive.pendingApprovals.length}
- </span>
- )}
- </button>
- ))}
- </div>
-
- {/* Tab Content */}
- <div className="flex-1 overflow-y-auto p-4">
- {activeTab === "overview" && (
- <OverviewTab directive={directive} />
- )}
- {activeTab === "chain" && (
- <ChainTab directive={directive} graph={graph} />
- )}
- {activeTab === "events" && (
- <EventsTab directive={directive} />
- )}
- {activeTab === "evaluations" && (
- <EvaluationsTab directive={directive} />
- )}
- {activeTab === "approvals" && (
- <ApprovalsTab directive={directive} onRefresh={onRefresh} />
- )}
- {activeTab === "verifiers" && (
- <VerifiersTab 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 d0371e0..0000000
--- a/makima/frontend/src/components/directives/DirectiveList.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import { useState } from "react";
-import type { DirectiveSummary } from "../../lib/api";
-import { DirectiveListItem } from "./DirectiveListItem";
-
-interface DirectiveListProps {
- directives: DirectiveSummary[];
- loading: boolean;
- onSelect: (id: string) => void;
- onCreate: () => void;
- selectedId?: string;
- onArchive: (directive: DirectiveSummary) => void;
-}
-
-export function DirectiveList({
- directives,
- loading,
- onSelect,
- onCreate,
- selectedId,
- onArchive,
-}: DirectiveListProps) {
- const [filter, setFilter] = useState<"all" | "active" | "completed" | "failed">("all");
-
- const filteredDirectives = directives.filter((d) => {
- if (filter === "all") return true;
- if (filter === "active") return ["draft", "planning", "active", "paused"].includes(d.status);
- if (filter === "completed") return d.status === "completed";
- if (filter === "failed") return d.status === "failed";
- return true;
- });
-
- return (
- <div className="panel h-full flex flex-col overflow-hidden">
- <div className="flex items-center justify-between p-3 border-b border-[rgba(117,170,252,0.15)]">
- <h2 className="font-mono text-sm text-[#75aafc] uppercase">Directives</h2>
- <button
- onClick={onCreate}
- className="px-3 py-1 font-mono text-[10px] text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase"
- >
- + New
- </button>
- </div>
-
- {/* Filters */}
- <div className="flex gap-1 p-2 border-b border-[rgba(117,170,252,0.1)]">
- {(["all", "active", "completed", "failed"] as const).map((f) => (
- <button
- key={f}
- onClick={() => setFilter(f)}
- className={`px-2 py-1 font-mono text-[10px] uppercase ${
- filter === f
- ? "text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3]"
- : "text-[#556677] hover:text-[#9bc3ff]"
- }`}
- >
- {f}
- </button>
- ))}
- </div>
-
- {/* List */}
- <div className="flex-1 overflow-y-auto">
- {loading ? (
- <div className="p-4 text-center">
- <p className="font-mono text-xs text-[#556677]">Loading...</p>
- </div>
- ) : filteredDirectives.length === 0 ? (
- <div className="p-4 text-center">
- <p className="font-mono text-xs text-[#556677]">No directives found</p>
- </div>
- ) : (
- filteredDirectives.map((d) => (
- <DirectiveListItem
- key={d.id}
- directive={d}
- selected={d.id === selectedId}
- onClick={() => onSelect(d.id)}
- onArchive={() => onArchive(d)}
- />
- ))
- )}
- </div>
- </div>
- );
-}
diff --git a/makima/frontend/src/components/directives/DirectiveListItem.tsx b/makima/frontend/src/components/directives/DirectiveListItem.tsx
deleted file mode 100644
index 6ff82e4..0000000
--- a/makima/frontend/src/components/directives/DirectiveListItem.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import type { DirectiveSummary } from "../../lib/api";
-
-interface DirectiveListItemProps {
- directive: DirectiveSummary;
- selected: boolean;
- onClick: () => void;
- onArchive: () => void;
-}
-
-export function DirectiveListItem({ directive, selected, onClick, onArchive }: DirectiveListItemProps) {
- const progress = directive.totalSteps > 0
- ? Math.round((directive.completedSteps / directive.totalSteps) * 100)
- : 0;
-
- const statusColor = {
- draft: "text-[#556677]",
- planning: "text-yellow-400",
- active: "text-green-400",
- paused: "text-yellow-400",
- completed: "text-[#75aafc]",
- archived: "text-[#556677]",
- failed: "text-red-400",
- }[directive.status] || "text-[#556677]";
-
- const confidenceColor = {
- green: "bg-green-500",
- yellow: "bg-yellow-500",
- red: "bg-red-500",
- }[directive.currentConfidence !== null && directive.currentConfidence >= 0.8
- ? "green"
- : directive.currentConfidence !== null && directive.currentConfidence >= 0.5
- ? "yellow"
- : "red"] || "bg-[#556677]";
-
- return (
- <div
- onClick={onClick}
- className={`p-3 cursor-pointer border-b border-[rgba(117,170,252,0.1)] hover:bg-[rgba(117,170,252,0.05)] ${
- selected ? "bg-[rgba(117,170,252,0.1)]" : ""
- }`}
- >
- <div className="flex items-start justify-between gap-2">
- <div className="flex-1 min-w-0">
- <div className="font-mono text-sm text-[#dbe7ff] truncate">
- {directive.title || directive.goal.slice(0, 50)}
- </div>
- <div className="flex items-center gap-2 mt-1">
- <span className={`font-mono text-[10px] uppercase ${statusColor}`}>
- {directive.status}
- </span>
- <span className="font-mono text-[10px] text-[#556677]">
- {directive.completedSteps}/{directive.totalSteps} steps
- </span>
- </div>
- </div>
- <div className="flex flex-col items-end gap-1">
- {directive.currentConfidence !== null && (
- <div className={`w-2 h-2 rounded-full ${confidenceColor}`} title={`Confidence: ${Math.round(directive.currentConfidence * 100)}%`} />
- )}
- <button
- onClick={(e) => {
- e.stopPropagation();
- onArchive();
- }}
- className="font-mono text-[10px] text-[#556677] hover:text-red-400"
- >
- Archive
- </button>
- </div>
- </div>
-
- {/* Progress bar */}
- {directive.totalSteps > 0 && (
- <div className="mt-2 h-1 bg-[rgba(117,170,252,0.1)] overflow-hidden">
- <div
- className="h-full bg-[#75aafc] transition-all duration-300"
- style={{ width: `${progress}%` }}
- />
- </div>
- )}
- </div>
- );
-}
diff --git a/makima/frontend/src/components/directives/EvaluationsTab.tsx b/makima/frontend/src/components/directives/EvaluationsTab.tsx
deleted file mode 100644
index c1d65db..0000000
--- a/makima/frontend/src/components/directives/EvaluationsTab.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import type { DirectiveWithProgress } from "../../lib/api";
-
-export function EvaluationsTab({ directive: _directive }: { directive: DirectiveWithProgress }) {
- // TODO: Fetch evaluations separately
- return (
- <div className="text-center py-8">
- <p className="font-mono text-sm text-[#556677]">
- Evaluations will be shown here after steps are evaluated
- </p>
- </div>
- );
-}
diff --git a/makima/frontend/src/components/directives/EventsTab.tsx b/makima/frontend/src/components/directives/EventsTab.tsx
deleted file mode 100644
index 4dd739a..0000000
--- a/makima/frontend/src/components/directives/EventsTab.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import { useMemo } from "react";
-import type { DirectiveWithProgress } from "../../lib/api";
-import { useDirectiveEventSubscription } from "../../hooks/useDirectives";
-
-export function EventsTab({ directive }: { directive: DirectiveWithProgress }) {
- // Subscribe to real-time events via SSE
- const { events: streamEvents, isConnected, error: sseError } = useDirectiveEventSubscription(directive.id);
-
- // Combine initial events with streamed events (avoiding duplicates)
- const allEvents = useMemo(() => {
- const eventMap = new Map();
- // Add initial events first
- directive.recentEvents.forEach((e) => eventMap.set(e.id, e));
- // Add streamed events (will override any duplicates)
- streamEvents.forEach((e) => eventMap.set(e.id, e));
- // Sort by created_at descending (most recent first)
- return Array.from(eventMap.values()).sort(
- (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
- );
- }, [directive.recentEvents, streamEvents]);
-
- return (
- <div className="space-y-4">
- {/* Connection status */}
- <div className="flex items-center justify-between text-[10px] font-mono">
- <div className="flex items-center gap-2">
- <span className={isConnected ? "text-green-400" : "text-[#556677]"}>
- {isConnected ? "\u25CF Live" : "\u25CB Connecting..."}
- </span>
- {sseError && <span className="text-red-400">{sseError}</span>}
- </div>
- <span className="text-[#556677]">{allEvents.length} events</span>
- </div>
-
- {/* Event list */}
- {allEvents.length === 0 ? (
- <div className="text-center py-8">
- <p className="font-mono text-sm text-[#556677]">No events yet</p>
- </div>
- ) : (
- <div className="space-y-2">
- {allEvents.map((event) => {
- const severityColors: Record<string, string> = {
- info: "text-[#75aafc]",
- warning: "text-yellow-400",
- error: "text-red-400",
- critical: "text-red-600",
- };
- const severityColor = severityColors[event.severity] || "text-[#556677]";
-
- return (
- <div
- key={event.id}
- className="p-3 bg-[rgba(117,170,252,0.02)] border border-[rgba(117,170,252,0.1)]"
- >
- <div className="flex items-center justify-between">
- <div className="flex items-center gap-2">
- <span className={`font-mono text-xs ${severityColor}`}>{event.eventType}</span>
- <span className="font-mono text-[10px] text-[#556677]">{event.actorType}</span>
- </div>
- <span className="font-mono text-[10px] text-[#556677]">
- {new Date(event.createdAt).toLocaleString()}
- </span>
- </div>
- {event.eventData != null && (
- <pre className="font-mono text-[10px] text-[#556677] mt-1 overflow-x-auto">
- {JSON.stringify(event.eventData, null, 2)}
- </pre>
- )}
- </div>
- );
- })}
- </div>
- )}
- </div>
- );
-}
diff --git a/makima/frontend/src/components/directives/OverviewTab.tsx b/makima/frontend/src/components/directives/OverviewTab.tsx
deleted file mode 100644
index 41cd7dc..0000000
--- a/makima/frontend/src/components/directives/OverviewTab.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import type { DirectiveWithProgress } from "../../lib/api";
-
-export function OverviewTab({ directive }: { directive: DirectiveWithProgress }) {
- return (
- <div className="space-y-6">
- {/* Goal */}
- <div>
- <h3 className="font-mono text-xs text-[#75aafc] uppercase mb-2">Goal</h3>
- <p className="font-mono text-sm text-[#dbe7ff] whitespace-pre-wrap">
- {directive.goal}
- </p>
- </div>
-
- {/* Progress */}
- <div>
- <h3 className="font-mono text-xs text-[#75aafc] uppercase mb-2">Progress</h3>
- <div className="grid grid-cols-3 gap-4">
- <div className="p-3 bg-[rgba(117,170,252,0.05)] border border-[rgba(117,170,252,0.15)]">
- <div className="font-mono text-2xl text-[#dbe7ff]">
- {directive.chain?.completedSteps || 0}
- </div>
- <div className="font-mono text-[10px] text-[#556677] uppercase">Completed Steps</div>
- </div>
- <div className="p-3 bg-[rgba(117,170,252,0.05)] border border-[rgba(117,170,252,0.15)]">
- <div className="font-mono text-2xl text-[#dbe7ff]">
- {directive.chain?.totalSteps || 0}
- </div>
- <div className="font-mono text-[10px] text-[#556677] uppercase">Total Steps</div>
- </div>
- <div className="p-3 bg-[rgba(117,170,252,0.05)] border border-[rgba(117,170,252,0.15)]">
- <div className="font-mono text-2xl text-[#dbe7ff]">
- {directive.chain?.currentConfidence != null
- ? `${Math.round((directive.chain?.currentConfidence ?? 0) * 100)}%`
- : "-"}
- </div>
- <div className="font-mono text-[10px] text-[#556677] uppercase">Confidence</div>
- </div>
- </div>
- </div>
-
- {/* Configuration */}
- <div>
- <h3 className="font-mono text-xs text-[#75aafc] uppercase mb-2">Configuration</h3>
- <div className="grid grid-cols-2 gap-2 text-sm">
- <div className="flex justify-between">
- <span className="font-mono text-[#556677]">Autonomy Level</span>
- <span className="font-mono text-[#dbe7ff]">{directive.autonomyLevel}</span>
- </div>
- <div className="flex justify-between">
- <span className="font-mono text-[#556677]">Max Rework Cycles</span>
- <span className="font-mono text-[#dbe7ff]">{directive.maxReworkCycles}</span>
- </div>
- <div className="flex justify-between">
- <span className="font-mono text-[#556677]">Green Threshold</span>
- <span className="font-mono text-[#dbe7ff]">{directive.confidenceThresholdGreen}</span>
- </div>
- <div className="flex justify-between">
- <span className="font-mono text-[#556677]">Yellow Threshold</span>
- <span className="font-mono text-[#dbe7ff]">{directive.confidenceThresholdYellow}</span>
- </div>
- </div>
- </div>
-
- {/* Repository */}
- {directive.repositoryUrl && (
- <div>
- <h3 className="font-mono text-xs text-[#75aafc] uppercase mb-2">Repository</h3>
- <p className="font-mono text-sm text-[#9bc3ff]">{directive.repositoryUrl}</p>
- </div>
- )}
- </div>
- );
-}
diff --git a/makima/frontend/src/components/directives/StepNode.tsx b/makima/frontend/src/components/directives/StepNode.tsx
deleted file mode 100644
index e54f5eb..0000000
--- a/makima/frontend/src/components/directives/StepNode.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import { Handle, Position } from "@xyflow/react";
-import type { StepStatus, ConfidenceLevel, DirectiveGraphNode } from "../../lib/api";
-
-// Step status colors for both list and DAG views
-export const stepStatusStyles: Record<StepStatus, { border: string; bg: string; text: string }> = {
- pending: { border: "#556677", bg: "#556677", text: "#556677" },
- ready: { border: "#3b82f6", bg: "#3b82f6", text: "#3b82f6" },
- running: { border: "#22c55e", bg: "#22c55e", text: "#22c55e" },
- evaluating: { border: "#eab308", bg: "#eab308", text: "#eab308" },
- passed: { border: "#75aafc", bg: "#75aafc", text: "#75aafc" },
- failed: { border: "#ef4444", bg: "#ef4444", text: "#ef4444" },
- rework: { border: "#f97316", bg: "#f97316", text: "#f97316" },
- skipped: { border: "#556677", bg: "#556677", text: "#556677" },
- blocked: { border: "#ef4444", bg: "#ef4444", text: "#ef4444" },
-};
-
-// Confidence level colors
-export const confidenceColors: Record<ConfidenceLevel, string> = {
- green: "#22c55e",
- yellow: "#eab308",
- red: "#ef4444",
-};
-
-// Node dimensions
-export const NODE_WIDTH = 180;
-export const NODE_HEIGHT = 70;
-
-// Custom node component for steps
-export function StepNodeComponent({ data }: { data: DirectiveGraphNode & { selected?: boolean } }) {
- const styles = stepStatusStyles[data.status] || stepStatusStyles.pending;
- const isRunning = data.status === "running" || data.status === "evaluating";
-
- return (
- <div
- className={`rounded-lg border-2 bg-[#0a1628] overflow-hidden ${
- isRunning ? "animate-pulse" : ""
- }`}
- style={{
- width: NODE_WIDTH,
- height: NODE_HEIGHT,
- borderColor: styles.border,
- borderStyle: data.status === "pending" ? "dashed" : "solid",
- }}
- >
- <Handle
- type="target"
- position={Position.Top}
- className="!bg-[#75aafc] !w-3 !h-3 !border-2 !border-[#0a1628]"
- />
-
- {/* Status indicator bar */}
- <div className="h-1.5" style={{ backgroundColor: styles.bg }} />
-
- {/* Content */}
- <div className="p-2">
- <div className="flex items-center justify-between mb-1">
- <span className="font-mono text-xs text-[#dbe7ff] truncate flex-1">{data.name}</span>
- {data.confidenceScore !== null && data.confidenceLevel && (
- <div
- className="w-2 h-2 rounded-full flex-shrink-0 ml-1"
- style={{ backgroundColor: confidenceColors[data.confidenceLevel] }}
- title={`Confidence: ${Math.round(data.confidenceScore * 100)}%`}
- />
- )}
- </div>
- <div className="flex items-center justify-between">
- <span
- className="font-mono text-[10px] uppercase px-1.5 py-0.5 rounded"
- style={{
- color: styles.text,
- backgroundColor: `${styles.bg}20`,
- }}
- >
- {data.status}
- </span>
- <span className="font-mono text-[10px] text-[#8b949e]">{data.stepType}</span>
- </div>
- </div>
-
- <Handle
- type="source"
- position={Position.Bottom}
- className="!bg-[#f59e0b] !w-3 !h-3 !border-2 !border-[#0a1628]"
- />
- </div>
- );
-}
diff --git a/makima/frontend/src/components/directives/VerifiersTab.tsx b/makima/frontend/src/components/directives/VerifiersTab.tsx
deleted file mode 100644
index cfcfdd8..0000000
--- a/makima/frontend/src/components/directives/VerifiersTab.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import type { DirectiveWithProgress } from "../../lib/api";
-
-export function VerifiersTab({ directive: _directive }: { directive: DirectiveWithProgress }) {
- // TODO: Fetch verifiers separately
- return (
- <div className="text-center py-8">
- <p className="font-mono text-sm text-[#556677]">
- Verifiers will be shown here. Use auto-detect to find available verifiers.
- </p>
- </div>
- );
-}
diff --git a/makima/frontend/src/components/directives/index.ts b/makima/frontend/src/components/directives/index.ts
deleted file mode 100644
index 718b1f2..0000000
--- a/makima/frontend/src/components/directives/index.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-export { DirectiveList } from "./DirectiveList";
-export { DirectiveListItem } from "./DirectiveListItem";
-export { DirectiveDetail } from "./DirectiveDetail";
-export { OverviewTab } from "./OverviewTab";
-export { ChainTab } from "./ChainTab";
-export { EventsTab } from "./EventsTab";
-export { EvaluationsTab } from "./EvaluationsTab";
-export { ApprovalsTab } from "./ApprovalsTab";
-export { VerifiersTab } from "./VerifiersTab";
-export { CreateDirectiveModal } from "./CreateDirectiveModal";
-export { StepNodeComponent, stepStatusStyles, confidenceColors, NODE_WIDTH, NODE_HEIGHT } from "./StepNode";
diff --git a/makima/frontend/src/hooks/useDirectiveDetail.ts b/makima/frontend/src/hooks/useDirectiveDetail.ts
deleted file mode 100644
index 1167242..0000000
--- a/makima/frontend/src/hooks/useDirectiveDetail.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import { useState, useCallback, useEffect } from "react";
-import {
- getDirective,
- getDirectiveGraph,
- startDirective,
- pauseDirective,
- resumeDirective,
- stopDirective,
- type DirectiveWithProgress,
- type DirectiveGraphResponse,
- type StartDirectiveResponse,
-} from "../lib/api";
-
-interface UseDirectiveDetailResult {
- directive: DirectiveWithProgress | null;
- graph: DirectiveGraphResponse | null;
- loading: boolean;
- error: string | null;
- refresh: () => Promise<void>;
- start: () => Promise<StartDirectiveResponse | null>;
- pause: () => Promise<boolean>;
- resume: () => Promise<boolean>;
- stop: () => Promise<boolean>;
-}
-
-export function useDirectiveDetail(directiveId: string | undefined): UseDirectiveDetailResult {
- const [directive, setDirective] = useState<DirectiveWithProgress | null>(null);
- const [graph, setGraph] = useState<DirectiveGraphResponse | null>(null);
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState<string | null>(null);
-
- const fetchDetail = useCallback(async () => {
- if (!directiveId) {
- setDirective(null);
- setGraph(null);
- return;
- }
-
- setLoading(true);
- setError(null);
- try {
- const [d, g] = await Promise.all([
- getDirective(directiveId),
- getDirectiveGraph(directiveId).catch(() => null),
- ]);
- setDirective(d);
- setGraph(g);
- } catch (err) {
- console.error("Failed to fetch directive detail:", err);
- setError(err instanceof Error ? err.message : "Failed to fetch directive");
- setDirective(null);
- setGraph(null);
- } finally {
- setLoading(false);
- }
- }, [directiveId]);
-
- useEffect(() => {
- fetchDetail();
- }, [fetchDetail]);
-
- const start = useCallback(async (): Promise<StartDirectiveResponse | null> => {
- if (!directiveId) return null;
- try {
- const response = await startDirective(directiveId);
- await fetchDetail();
- return response;
- } catch (err) {
- console.error("Failed to start directive:", err);
- setError(err instanceof Error ? err.message : "Failed to start directive");
- return null;
- }
- }, [directiveId, fetchDetail]);
-
- const pause = useCallback(async (): Promise<boolean> => {
- if (!directiveId) return false;
- try {
- await pauseDirective(directiveId);
- await fetchDetail();
- return true;
- } catch (err) {
- console.error("Failed to pause directive:", err);
- setError(err instanceof Error ? err.message : "Failed to pause directive");
- return false;
- }
- }, [directiveId, fetchDetail]);
-
- const resume = useCallback(async (): Promise<boolean> => {
- if (!directiveId) return false;
- try {
- await resumeDirective(directiveId);
- await fetchDetail();
- return true;
- } catch (err) {
- console.error("Failed to resume directive:", err);
- setError(err instanceof Error ? err.message : "Failed to resume directive");
- return false;
- }
- }, [directiveId, fetchDetail]);
-
- const stop = useCallback(async (): Promise<boolean> => {
- if (!directiveId) return false;
- try {
- await stopDirective(directiveId);
- await fetchDetail();
- return true;
- } catch (err) {
- console.error("Failed to stop directive:", err);
- setError(err instanceof Error ? err.message : "Failed to stop directive");
- return false;
- }
- }, [directiveId, fetchDetail]);
-
- return {
- directive,
- graph,
- loading,
- error,
- refresh: fetchDetail,
- start,
- pause,
- resume,
- stop,
- };
-}
diff --git a/makima/frontend/src/hooks/useDirectives.ts b/makima/frontend/src/hooks/useDirectives.ts
deleted file mode 100644
index 7ae24a5..0000000
--- a/makima/frontend/src/hooks/useDirectives.ts
+++ /dev/null
@@ -1,298 +0,0 @@
-import { useState, useCallback, useEffect, useRef } from "react";
-import {
- listDirectives,
- getDirective,
- createDirective,
- updateDirective,
- archiveDirective,
- startDirective,
- pauseDirective,
- resumeDirective,
- stopDirective,
- getDirectiveGraph,
- subscribeToDirectiveEvents,
- type DirectiveSummary,
- type DirectiveWithProgress,
- type DirectiveGraphResponse,
- type DirectiveStatus,
- type DirectiveEvent,
- type CreateDirectiveRequest,
- type UpdateDirectiveRequest,
- type StartDirectiveResponse,
-} from "../lib/api";
-
-interface UseDirectivesResult {
- directives: DirectiveSummary[];
- loading: boolean;
- error: string | null;
- refresh: () => Promise<void>;
- createNewDirective: (req: CreateDirectiveRequest) => Promise<DirectiveWithProgress | null>;
- updateExistingDirective: (
- directiveId: string,
- req: UpdateDirectiveRequest
- ) => Promise<DirectiveWithProgress | null>;
- archiveExistingDirective: (directiveId: string) => Promise<boolean>;
- getDirectiveById: (directiveId: string) => Promise<DirectiveWithProgress | null>;
- getGraph: (directiveId: string) => Promise<DirectiveGraphResponse | null>;
- start: (directiveId: string) => Promise<StartDirectiveResponse | null>;
- pause: (directiveId: string) => Promise<boolean>;
- resume: (directiveId: string) => Promise<boolean>;
- stop: (directiveId: string) => Promise<boolean>;
-}
-
-export function useDirectives(statusFilter?: DirectiveStatus): UseDirectivesResult {
- const [directives, setDirectives] = useState<DirectiveSummary[]>([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState<string | null>(null);
-
- const fetchDirectives = useCallback(async () => {
- setLoading(true);
- setError(null);
- try {
- const response = await listDirectives(statusFilter);
- setDirectives(response.directives ?? []);
- } catch (err) {
- console.error("Failed to fetch directives:", err);
- setError(err instanceof Error ? err.message : "Failed to fetch directives");
- } finally {
- setLoading(false);
- }
- }, [statusFilter]);
-
- useEffect(() => {
- fetchDirectives();
- }, [fetchDirectives]);
-
- const createNewDirective = useCallback(
- async (req: CreateDirectiveRequest): Promise<DirectiveWithProgress | null> => {
- try {
- const directive = await createDirective(req);
- // Refresh the list
- await fetchDirectives();
- // Return the full directive with progress
- return await getDirective(directive.id);
- } catch (err) {
- console.error("Failed to create directive:", err);
- setError(err instanceof Error ? err.message : "Failed to create directive");
- return null;
- }
- },
- [fetchDirectives]
- );
-
- const updateExistingDirective = useCallback(
- async (
- directiveId: string,
- req: UpdateDirectiveRequest
- ): Promise<DirectiveWithProgress | null> => {
- try {
- await updateDirective(directiveId, req);
- // Refresh the list
- await fetchDirectives();
- // Return the updated directive
- return await getDirective(directiveId);
- } catch (err) {
- console.error("Failed to update directive:", err);
- setError(err instanceof Error ? err.message : "Failed to update directive");
- return null;
- }
- },
- [fetchDirectives]
- );
-
- const archiveExistingDirective = useCallback(
- async (directiveId: string): Promise<boolean> => {
- try {
- await archiveDirective(directiveId);
- // Refresh the list
- await fetchDirectives();
- return true;
- } catch (err) {
- console.error("Failed to archive directive:", err);
- setError(err instanceof Error ? err.message : "Failed to archive directive");
- return false;
- }
- },
- [fetchDirectives]
- );
-
- const getDirectiveById = useCallback(
- async (directiveId: string): Promise<DirectiveWithProgress | null> => {
- try {
- return await getDirective(directiveId);
- } catch (err) {
- console.error("Failed to get directive:", err);
- setError(err instanceof Error ? err.message : "Failed to get directive");
- return null;
- }
- },
- []
- );
-
- const getGraph = useCallback(
- async (directiveId: string): Promise<DirectiveGraphResponse | null> => {
- try {
- return await getDirectiveGraph(directiveId);
- } catch (err) {
- console.error("Failed to get directive graph:", err);
- setError(err instanceof Error ? err.message : "Failed to get directive graph");
- return null;
- }
- },
- []
- );
-
- const start = useCallback(
- async (directiveId: string): Promise<StartDirectiveResponse | null> => {
- try {
- const response = await startDirective(directiveId);
- await fetchDirectives();
- return response;
- } catch (err) {
- console.error("Failed to start directive:", err);
- setError(err instanceof Error ? err.message : "Failed to start directive");
- return null;
- }
- },
- [fetchDirectives]
- );
-
- const pause = useCallback(
- async (directiveId: string): Promise<boolean> => {
- try {
- await pauseDirective(directiveId);
- await fetchDirectives();
- return true;
- } catch (err) {
- console.error("Failed to pause directive:", err);
- setError(err instanceof Error ? err.message : "Failed to pause directive");
- return false;
- }
- },
- [fetchDirectives]
- );
-
- const resume = useCallback(
- async (directiveId: string): Promise<boolean> => {
- try {
- await resumeDirective(directiveId);
- await fetchDirectives();
- return true;
- } catch (err) {
- console.error("Failed to resume directive:", err);
- setError(err instanceof Error ? err.message : "Failed to resume directive");
- return false;
- }
- },
- [fetchDirectives]
- );
-
- const stop = useCallback(
- async (directiveId: string): Promise<boolean> => {
- try {
- await stopDirective(directiveId);
- await fetchDirectives();
- return true;
- } catch (err) {
- console.error("Failed to stop directive:", err);
- setError(err instanceof Error ? err.message : "Failed to stop directive");
- return false;
- }
- },
- [fetchDirectives]
- );
-
- return {
- directives,
- loading,
- error,
- refresh: fetchDirectives,
- createNewDirective,
- updateExistingDirective,
- archiveExistingDirective,
- getDirectiveById,
- getGraph,
- start,
- pause,
- resume,
- stop,
- };
-}
-
-/** Hook for subscribing to real-time directive events via SSE */
-export function useDirectiveEventSubscription(
- directiveId: string | null,
- onEvent?: (event: DirectiveEvent) => void
-): {
- events: DirectiveEvent[];
- isConnected: boolean;
- error: string | null;
-} {
- const [events, setEvents] = useState<DirectiveEvent[]>([]);
- const [isConnected, setIsConnected] = useState(false);
- const [error, setError] = useState<string | null>(null);
- const cleanupRef = useRef<(() => void) | null>(null);
-
- useEffect(() => {
- // Clean up any existing subscription
- if (cleanupRef.current) {
- cleanupRef.current();
- cleanupRef.current = null;
- }
-
- if (!directiveId) {
- setIsConnected(false);
- setEvents([]);
- return;
- }
-
- // Subscribe to events
- let mounted = true;
-
- const setupSubscription = async () => {
- try {
- const cleanup = await subscribeToDirectiveEvents(
- directiveId,
- (event) => {
- if (mounted) {
- setEvents((prev) => [...prev, event]);
- onEvent?.(event);
- }
- },
- (err) => {
- if (mounted) {
- setError(err.message);
- setIsConnected(false);
- }
- }
- );
-
- if (mounted) {
- cleanupRef.current = cleanup;
- setIsConnected(true);
- setError(null);
- } else {
- // Component unmounted during setup, clean up immediately
- cleanup();
- }
- } catch (err) {
- if (mounted) {
- setError(err instanceof Error ? err.message : "Failed to subscribe to events");
- setIsConnected(false);
- }
- }
- };
-
- setupSubscription();
-
- return () => {
- mounted = false;
- if (cleanupRef.current) {
- cleanupRef.current();
- cleanupRef.current = null;
- }
- };
- }, [directiveId, onEvent]);
-
- return { events, isConnected, error };
-}
diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts
index 466a794..9f5ff88 100644
--- a/makima/frontend/src/lib/api.ts
+++ b/makima/frontend/src/lib/api.ts
@@ -3003,1286 +3003,3 @@ export async function listTaskPatches(taskId: string, contractId: string): Promi
return res.json();
}
-// =============================================================================
-// Chain Types and API
-// =============================================================================
-
-/** Chain status */
-export type ChainStatus = "pending" | "active" | "completed" | "archived";
-
-/** Chain summary for list view */
-export interface ChainSummary {
- id: string;
- name: string;
- description: string | null;
- status: ChainStatus;
- contractCount: number;
- completedContractCount: number;
- loopEnabled: boolean;
- loopCurrentIteration: number | null;
- loopMaxIterations: number | null;
- createdAt: string;
- updatedAt: string;
-}
-
-/** Chain repository */
-export interface ChainRepository {
- id: string;
- chainId: string;
- name: string;
- repositoryUrl: string | null;
- localPath: string | null;
- sourceType: string;
- status: string;
- isPrimary: boolean;
- createdAt: string;
- updatedAt: string;
-}
-
-/** Full chain with contracts */
-export interface Chain {
- id: string;
- ownerId: string;
- name: string;
- description: string | null;
- status: ChainStatus;
- loopEnabled: boolean;
- loopMaxIterations: number | null;
- loopCurrentIteration: number | null;
- loopProgressCheck: string | null;
- version: number;
- createdAt: string;
- updatedAt: string;
-}
-
-/** Contract detail within a chain */
-export interface ChainContractDetail {
- id: string;
- chainId: string;
- contractId: string;
- contractName: string;
- contractStatus: string;
- contractPhase: string;
- dependsOn: string[];
- orderIndex: number;
- editorX: number | null;
- editorY: number | null;
- createdAt: string;
-}
-
-/** Chain with contracts (chain fields are flattened via serde(flatten)) */
-export interface ChainWithContracts extends Chain {
- contracts: ChainContractDetail[];
- repositories: ChainRepository[];
-}
-
-/** Node in chain graph visualization */
-export interface ChainGraphNode {
- id: string;
- contractId: string;
- name: string;
- status: string;
- phase: string;
- x: number;
- y: number;
-}
-
-/** Edge in chain graph */
-export interface ChainGraphEdge {
- from: string;
- to: string;
-}
-
-/** Chain graph response */
-export interface ChainGraphResponse {
- chainId: string;
- chainName: string;
- chainStatus: string;
- nodes: ChainGraphNode[];
- edges: ChainGraphEdge[];
-}
-
-/** Chain event */
-export interface ChainEvent {
- id: string;
- chainId: string;
- eventType: string;
- contractId: string | null;
- eventData: Record<string, unknown> | null;
- createdAt: string;
-}
-
-/** Chain list response */
-export interface ChainListResponse {
- chains: ChainSummary[];
- total: number;
-}
-
-/** Add chain repository request */
-export interface AddChainRepositoryRequest {
- name: string;
- repositoryUrl?: string;
- localPath?: string;
- sourceType?: string;
- isPrimary?: boolean;
-}
-
-/** Create chain request */
-export interface CreateChainRequest {
- name: string;
- description?: string;
- repositoryUrl?: string; // Legacy field for backwards compatibility
- repositories?: AddChainRepositoryRequest[];
- loopEnabled?: boolean;
- loopMaxIterations?: number;
- loopProgressCheck?: string;
- contracts?: CreateChainContractRequest[];
-}
-
-/** Create chain contract request */
-export interface CreateChainContractRequest {
- name: string;
- description?: string;
- contractType?: string;
- initialPhase?: string;
- phases?: string[];
- dependsOn?: string[];
- tasks?: { name: string; plan: string }[];
- deliverables?: { id: string; name: string; priority?: string }[];
- editorX?: number;
- editorY?: number;
-}
-
-/** Update chain request */
-export interface UpdateChainRequest {
- name?: string;
- description?: string;
- status?: ChainStatus;
- loopEnabled?: boolean;
- loopMaxIterations?: number;
- loopProgressCheck?: string;
- version?: number;
-}
-
-/** List chains */
-export async function listChains(
- status?: ChainStatus,
- limit = 50,
- offset = 0
-): Promise<ChainListResponse> {
- const params = new URLSearchParams();
- if (status) params.set("status", status);
- params.set("limit", String(limit));
- params.set("offset", String(offset));
-
- const res = await authFetch(`${API_BASE}/api/v1/chains?${params}`);
- if (!res.ok) {
- throw new Error(`Failed to list chains: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Get chain by ID */
-export async function getChain(chainId: string): Promise<ChainWithContracts> {
- const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}`);
- if (!res.ok) {
- throw new Error(`Failed to get chain: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Create a new chain */
-export async function createChain(req: CreateChainRequest): Promise<Chain> {
- const res = await authFetch(`${API_BASE}/api/v1/chains`, {
- method: "POST",
- body: JSON.stringify(req),
- });
- if (!res.ok) {
- const errorText = await res.text();
- throw new Error(`Failed to create chain: ${errorText || res.statusText}`);
- }
- return res.json();
-}
-
-/** Update a chain */
-export async function updateChain(
- chainId: string,
- req: UpdateChainRequest
-): Promise<Chain> {
- const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}`, {
- method: "PUT",
- body: JSON.stringify(req),
- });
- if (!res.ok) {
- const errorText = await res.text();
- throw new Error(`Failed to update chain: ${errorText || res.statusText}`);
- }
- return res.json();
-}
-
-/** Archive a chain */
-export async function archiveChain(chainId: string): Promise<{ archived: boolean }> {
- const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}`, {
- method: "DELETE",
- });
- if (!res.ok) {
- throw new Error(`Failed to archive chain: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Get chain contracts */
-export async function getChainContracts(
- chainId: string
-): Promise<ChainContractDetail[]> {
- const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/contracts`);
- if (!res.ok) {
- throw new Error(`Failed to get chain contracts: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Get chain graph for visualization */
-export async function getChainGraph(chainId: string): Promise<ChainGraphResponse> {
- const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/graph`);
- if (!res.ok) {
- throw new Error(`Failed to get chain graph: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Get chain events */
-export async function getChainEvents(chainId: string): Promise<ChainEvent[]> {
- const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/events`);
- if (!res.ok) {
- throw new Error(`Failed to get chain events: ${res.statusText}`);
- }
- return res.json();
-}
-
-// =============================================================================
-// Chain Contract Definitions
-// =============================================================================
-
-/** Task definition for chain contract definitions */
-export interface ChainTaskDefinition {
- name: string;
- plan: string;
-}
-
-/** Deliverable definition for chain contract definitions (optional priority) */
-export interface ChainDeliverableDefinition {
- id: string;
- name: string;
- priority?: string;
-}
-
-/** Validation configuration for checkpoint contracts */
-export interface CheckpointValidation {
- /** Check that all required deliverables from upstream contracts exist */
- checkDeliverables?: boolean;
- /** Run tests in the repository */
- runTests?: boolean;
- /** Custom validation instructions for Claude */
- checkContent?: string;
- /** Action on failure: "block", "retry", "warn" */
- onFailure?: "block" | "retry" | "warn";
- /** Max retry attempts for upstream contracts */
- maxRetries?: number;
-}
-
-/** Contract definition stored in chain (before actual contract is created) */
-export interface ChainContractDefinition {
- id: string;
- chainId: string;
- name: string;
- description: string | null;
- contractType: string;
- initialPhase: string | null;
- dependsOnNames: string[];
- tasks: ChainTaskDefinition[] | null;
- deliverables: ChainDeliverableDefinition[] | null;
- /** Validation config for checkpoint contracts */
- validation: CheckpointValidation | null;
- editorX: number | null;
- editorY: number | null;
- orderIndex: number;
- createdAt: string;
-}
-
-/** Request to add a contract definition to a chain */
-export interface AddContractDefinitionRequest {
- name: string;
- description?: string;
- contractType?: string;
- initialPhase?: string;
- dependsOn?: string[];
- tasks?: ChainTaskDefinition[];
- deliverables?: ChainDeliverableDefinition[];
- /** Validation config (for checkpoint contracts) */
- validation?: CheckpointValidation;
- editorX?: number;
- editorY?: number;
- orderIndex?: number;
-}
-
-/** Request to update a contract definition */
-export interface UpdateContractDefinitionRequest {
- name?: string;
- description?: string;
- contractType?: string;
- initialPhase?: string;
- dependsOn?: string[];
- tasks?: ChainTaskDefinition[];
- deliverables?: ChainDeliverableDefinition[];
- /** Validation config (for checkpoint contracts) */
- validation?: CheckpointValidation;
- editorX?: number;
- editorY?: number;
- orderIndex?: number;
-}
-
-/** Response when starting a chain */
-export interface StartChainResponse {
- chainId: string;
- contractsCreated: string[];
- status: string;
-}
-
-/** Node in definition graph (shows definitions + instantiation status) */
-export interface ChainDefinitionGraphNode {
- id: string;
- name: string;
- contractType: string;
- x: number;
- y: number;
- isInstantiated: boolean;
- contractId: string | null;
- contractStatus: string | null;
-}
-
-/** Definition graph response */
-export interface ChainDefinitionGraphResponse {
- chainId: string;
- chainName: string;
- chainStatus: string;
- nodes: ChainDefinitionGraphNode[];
- edges: ChainGraphEdge[];
-}
-
-/** List contract definitions for a chain */
-export async function listChainDefinitions(
- chainId: string
-): Promise<ChainContractDefinition[]> {
- const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/definitions`);
- if (!res.ok) {
- throw new Error(`Failed to list chain definitions: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Create a contract definition for a chain */
-export async function createChainDefinition(
- chainId: string,
- req: AddContractDefinitionRequest
-): Promise<ChainContractDefinition> {
- const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/definitions`, {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(req),
- });
- if (!res.ok) {
- throw new Error(`Failed to create chain definition: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Update a contract definition */
-export async function updateChainDefinition(
- chainId: string,
- definitionId: string,
- req: UpdateContractDefinitionRequest
-): Promise<ChainContractDefinition> {
- const res = await authFetch(
- `${API_BASE}/api/v1/chains/${chainId}/definitions/${definitionId}`,
- {
- method: "PUT",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(req),
- }
- );
- if (!res.ok) {
- throw new Error(`Failed to update chain definition: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Delete a contract definition */
-export async function deleteChainDefinition(
- chainId: string,
- definitionId: string
-): Promise<{ deleted: boolean }> {
- const res = await authFetch(
- `${API_BASE}/api/v1/chains/${chainId}/definitions/${definitionId}`,
- { method: "DELETE" }
- );
- if (!res.ok) {
- throw new Error(`Failed to delete chain definition: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Get definition graph for a chain */
-export async function getChainDefinitionGraph(
- chainId: string
-): Promise<ChainDefinitionGraphResponse> {
- const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/definitions/graph`);
- if (!res.ok) {
- throw new Error(`Failed to get chain definition graph: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Start a chain (creates root contracts based on DAG) */
-export async function startChain(chainId: string): Promise<StartChainResponse> {
- const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/start`, {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({}),
- });
- if (!res.ok) {
- const error = await res.json().catch(() => ({ message: res.statusText }));
- throw new Error(error.message || `Failed to start chain: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Stop a chain (marks as archived) */
-export async function stopChain(chainId: string): Promise<{ stopped: boolean; status: string }> {
- const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/stop`, {
- method: "POST",
- });
- if (!res.ok) {
- const error = await res.json().catch(() => ({ message: res.statusText }));
- throw new Error(error.message || `Failed to stop chain: ${res.statusText}`);
- }
- return res.json();
-}
-
-// ============================================================================
-// Chain Repository Operations
-// ============================================================================
-
-/** List repositories for a chain */
-export async function listChainRepositories(chainId: string): Promise<ChainRepository[]> {
- const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/repositories`);
- if (!res.ok) {
- throw new Error(`Failed to list chain repositories: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Add a repository to a chain */
-export async function addChainRepository(
- chainId: string,
- req: AddChainRepositoryRequest
-): Promise<ChainRepository> {
- const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/repositories`, {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(req),
- });
- if (!res.ok) {
- const error = await res.json().catch(() => ({ message: res.statusText }));
- throw new Error(error.message || `Failed to add chain repository: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Delete a repository from a chain */
-export async function deleteChainRepository(
- chainId: string,
- repositoryId: string
-): Promise<{ deleted: boolean }> {
- const res = await authFetch(
- `${API_BASE}/api/v1/chains/${chainId}/repositories/${repositoryId}`,
- { method: "DELETE" }
- );
- if (!res.ok) {
- throw new Error(`Failed to delete chain repository: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Set a repository as primary for a chain */
-export async function setChainRepositoryPrimary(
- chainId: string,
- repositoryId: string
-): Promise<ChainRepository> {
- const res = await authFetch(
- `${API_BASE}/api/v1/chains/${chainId}/repositories/${repositoryId}/primary`,
- { method: "PUT" }
- );
- if (!res.ok) {
- throw new Error(`Failed to set chain repository as primary: ${res.statusText}`);
- }
- return res.json();
-}
-
-// =============================================================================
-// Directive Types and API
-// =============================================================================
-
-/** Directive status */
-export type DirectiveStatus =
- | "draft"
- | "planning"
- | "active"
- | "paused"
- | "completed"
- | "archived"
- | "failed";
-
-/** Autonomy level */
-export type AutonomyLevel = "full_auto" | "guardrails" | "manual";
-
-/** Confidence level (traffic light) */
-export type ConfidenceLevel = "green" | "yellow" | "red";
-
-/** Step status */
-export type StepStatus =
- | "pending"
- | "ready"
- | "running"
- | "evaluating"
- | "passed"
- | "failed"
- | "rework"
- | "skipped"
- | "blocked";
-
-/** Evaluation type */
-export type EvaluationType = "programmatic" | "llm" | "composite" | "manual";
-
-/** Directive summary for list view */
-export interface DirectiveSummary {
- id: string;
- title: string;
- goal: string;
- status: DirectiveStatus;
- autonomyLevel: AutonomyLevel;
- repositoryUrl: string | null;
- currentChainId: string | null;
- currentChainGeneration: number | null;
- totalSteps: number;
- completedSteps: number;
- failedSteps: number;
- currentConfidence: number | null;
- totalCostUsd: number;
- createdAt: string;
- updatedAt: string;
-}
-
-/** Full directive */
-export interface Directive {
- id: string;
- ownerId: string;
- title: string;
- goal: string;
- status: DirectiveStatus;
- autonomyLevel: AutonomyLevel;
- repositoryUrl: string | null;
- localPath: string | null;
- requirements: unknown | null;
- acceptanceCriteria: unknown | null;
- constraints: unknown | null;
- confidenceThresholdGreen: number;
- confidenceThresholdYellow: number;
- maxReworkCycles: number;
- maxTotalCostUsd: number | null;
- maxWallTimeMinutes: number | null;
- maxChainRegenerations: number;
- totalCostUsd: number;
- currentChainId: string | null;
- version: number;
- createdAt: string;
- updatedAt: string;
- startedAt: string | null;
- completedAt: string | null;
-}
-
-/** Directive chain */
-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;
-}
-
-/** Chain step */
-export interface ChainStep {
- id: string;
- chainId: string;
- name: string;
- description: string | null;
- stepType: string;
- contractType: string;
- initialPhase: string | null;
- taskPlan: string | null;
- phases: string[];
- dependsOn: string[];
- parallelGroup: string | null;
- requirementIds: string[];
- acceptanceCriteriaIds: string[];
- verifierConfig: unknown;
- status: StepStatus;
- contractId: string | null;
- supervisorTaskId: string | null;
- confidenceScore: number | null;
- confidenceLevel: ConfidenceLevel | null;
- evaluationCount: number;
- reworkCount: number;
- lastEvaluationId: string | null;
- editorX: number | null;
- editorY: number | null;
- startedAt: string | null;
- completedAt: string | null;
- createdAt: string;
- updatedAt: string;
-}
-
-/** Directive with progress info */
-export interface DirectiveWithProgress extends Directive {
- chain: DirectiveChain | null;
- steps: ChainStep[];
- recentEvents: DirectiveEvent[];
- pendingApprovals: DirectiveApproval[];
-}
-
-/** Directive evaluation */
-export interface DirectiveEvaluation {
- id: string;
- directiveId: string;
- chainId: string | null;
- stepId: string | null;
- evaluationType: EvaluationType;
- passed: boolean;
- overallScore: number;
- confidenceLevel: ConfidenceLevel;
- programmaticResults: unknown | null;
- llmResults: unknown | null;
- compositeBreakdown: unknown | null;
- feedback: string | null;
- reworkInstructions: string | null;
- verifierIds: string[];
- evaluatedBy: string | null;
- createdAt: string;
-}
-
-/** Directive event */
-export interface DirectiveEvent {
- id: string;
- directiveId: string;
- chainId: string | null;
- stepId: string | null;
- eventType: string;
- severity: string;
- eventData: unknown | null;
- actorType: string;
- actorId: string | null;
- createdAt: string;
-}
-
-/** Directive approval */
-export interface DirectiveApproval {
- id: string;
- directiveId: string;
- chainId: string | null;
- stepId: string | null;
- approvalType: string;
- description: string;
- context: unknown | null;
- urgency: string;
- status: string;
- requestedAt: string;
- resolvedAt: string | null;
- resolvedBy: string | null;
- response: string | null;
-}
-
-/** Directive verifier */
-export interface DirectiveVerifier {
- id: string;
- directiveId: string;
- name: string;
- verifierType: string;
- command: string | null;
- workingDirectory: string | null;
- timeoutSeconds: number;
- environment: unknown;
- autoDetect: boolean;
- detectFiles: string[];
- weight: number;
- required: boolean;
- enabled: boolean;
- lastRunAt: string | null;
- lastResult: unknown | null;
- createdAt: string;
- updatedAt: string;
-}
-
-/** Directive graph node */
-export interface DirectiveGraphNode {
- id: string;
- name: string;
- stepType: string;
- status: StepStatus;
- confidenceScore: number | null;
- confidenceLevel: ConfidenceLevel | null;
- contractId: string | null;
- editorX: number | null;
- editorY: number | null;
-}
-
-/** Directive graph edge */
-export interface DirectiveGraphEdge {
- source: string;
- target: string;
-}
-
-/** Directive graph response */
-export interface DirectiveGraphResponse {
- chainId: string;
- directiveId: string;
- nodes: DirectiveGraphNode[];
- edges: DirectiveGraphEdge[];
-}
-
-/** Create directive request */
-export interface CreateDirectiveRequest {
- goal: string;
- repositoryUrl?: string;
- localPath?: string;
- autonomyLevel?: AutonomyLevel;
- confidenceThresholdGreen?: number;
- confidenceThresholdYellow?: number;
- maxReworkCycles?: number;
- maxTotalCostUsd?: number;
- maxWallTimeMinutes?: number;
-}
-
-/** Update directive request */
-export interface UpdateDirectiveRequest {
- title?: string;
- goal?: string;
- requirements?: unknown;
- acceptanceCriteria?: unknown;
- constraints?: unknown;
- autonomyLevel?: AutonomyLevel;
- confidenceThresholdGreen?: number;
- confidenceThresholdYellow?: number;
- maxReworkCycles?: number;
- maxTotalCostUsd?: number;
- maxWallTimeMinutes?: number;
- version?: number;
-}
-
-/** Add step request */
-export interface AddStepRequest {
- name: string;
- description?: string;
- stepType?: string;
- contractType?: string;
- initialPhase?: string;
- taskPlan?: string;
- phases?: string[];
- dependsOn?: string[];
- parallelGroup?: string;
- requirementIds?: string[];
- acceptanceCriteriaIds?: string[];
- verifierConfig?: unknown;
- editorX?: number;
- editorY?: number;
-}
-
-/** Update step request */
-export interface UpdateStepRequest {
- name?: string;
- description?: string;
- initialPhase?: string;
- taskPlan?: string;
- phases?: string[];
- dependsOn?: string[];
- parallelGroup?: string;
- requirementIds?: string[];
- acceptanceCriteriaIds?: string[];
- verifierConfig?: unknown;
- editorX?: number;
- editorY?: number;
-}
-
-/** Create verifier request */
-export interface CreateVerifierRequest {
- name: string;
- verifierType: string;
- command?: string;
- workingDirectory?: string;
- timeoutSeconds?: number;
- weight?: number;
- required?: boolean;
- enabled?: boolean;
-}
-
-/** Update verifier request */
-export interface UpdateVerifierRequest {
- command?: string;
- weight?: number;
- required?: boolean;
- enabled?: boolean;
-}
-
-/** Approval action request */
-export interface ApprovalActionRequest {
- response?: string;
-}
-
-/** Start directive response */
-export interface StartDirectiveResponse {
- directiveId: string;
- chainId: string;
- chainGeneration: number;
- steps: ChainStep[];
- status: string;
-}
-
-/** Directive list response */
-export interface DirectiveListResponse {
- directives: DirectiveSummary[];
- total: number;
-}
-
-// =============================================================================
-// Directive API Functions
-// =============================================================================
-
-/** List directives */
-export async function listDirectives(
- status?: DirectiveStatus,
- limit = 50,
- offset = 0
-): Promise<DirectiveListResponse> {
- const params = new URLSearchParams();
- if (status) params.set("status", status);
- params.set("limit", String(limit));
- params.set("offset", String(offset));
-
- const res = await authFetch(`${API_BASE}/api/v1/directives?${params}`);
- if (!res.ok) {
- throw new Error(`Failed to list directives: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Get directive by ID */
-export async function getDirective(directiveId: string): Promise<DirectiveWithProgress> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}`);
- if (!res.ok) {
- throw new Error(`Failed to get directive: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Create a new directive */
-export async function createDirective(req: CreateDirectiveRequest): Promise<Directive> {
- const res = await authFetch(`${API_BASE}/api/v1/directives`, {
- method: "POST",
- body: JSON.stringify(req),
- });
- if (!res.ok) {
- const errorText = await res.text();
- throw new Error(`Failed to create directive: ${errorText || res.statusText}`);
- }
- return res.json();
-}
-
-/** Update directive */
-export async function updateDirective(
- directiveId: string,
- req: UpdateDirectiveRequest
-): Promise<Directive> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}`, {
- method: "PUT",
- body: JSON.stringify(req),
- });
- if (!res.ok) {
- throw new Error(`Failed to update directive: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Archive directive */
-export async function archiveDirective(directiveId: string): Promise<{ archived: boolean }> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}`, {
- method: "DELETE",
- });
- if (!res.ok) {
- throw new Error(`Failed to archive directive: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Start directive */
-export async function startDirective(directiveId: string): Promise<StartDirectiveResponse> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/start`, {
- method: "POST",
- });
- if (!res.ok) {
- throw new Error(`Failed to start directive: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Pause directive */
-export async function pauseDirective(directiveId: string): Promise<Directive> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/pause`, {
- method: "POST",
- });
- if (!res.ok) {
- throw new Error(`Failed to pause directive: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Resume directive */
-export async function resumeDirective(directiveId: string): Promise<Directive> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/resume`, {
- method: "POST",
- });
- if (!res.ok) {
- throw new Error(`Failed to resume directive: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Stop directive */
-export async function stopDirective(directiveId: string): Promise<Directive> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/stop`, {
- method: "POST",
- });
- if (!res.ok) {
- throw new Error(`Failed to stop directive: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Get directive chain */
-export async function getDirectiveChain(
- directiveId: string
-): Promise<{ chain: DirectiveChain | null; steps: ChainStep[] }> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/chain`);
- if (!res.ok) {
- throw new Error(`Failed to get directive chain: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Get directive chain graph */
-export async function getDirectiveGraph(directiveId: string): Promise<DirectiveGraphResponse> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/chain/graph`);
- if (!res.ok) {
- throw new Error(`Failed to get directive graph: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Replan directive chain */
-export async function replanDirectiveChain(directiveId: string): Promise<DirectiveChain> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/chain/replan`, {
- method: "POST",
- });
- if (!res.ok) {
- throw new Error(`Failed to replan directive chain: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Add step to directive chain */
-export async function addDirectiveStep(
- directiveId: string,
- req: AddStepRequest
-): Promise<ChainStep> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/chain/steps`, {
- method: "POST",
- body: JSON.stringify(req),
- });
- if (!res.ok) {
- throw new Error(`Failed to add step: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Get step details */
-export async function getDirectiveStep(
- directiveId: string,
- stepId: string
-): Promise<ChainStep> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/steps/${stepId}`);
- if (!res.ok) {
- throw new Error(`Failed to get step: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Update step */
-export async function updateDirectiveStep(
- directiveId: string,
- stepId: string,
- req: UpdateStepRequest
-): Promise<ChainStep> {
- const res = await authFetch(
- `${API_BASE}/api/v1/directives/${directiveId}/chain/steps/${stepId}`,
- {
- method: "PUT",
- body: JSON.stringify(req),
- }
- );
- if (!res.ok) {
- throw new Error(`Failed to update step: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Delete step */
-export async function deleteDirectiveStep(
- directiveId: string,
- stepId: string
-): Promise<{ deleted: boolean }> {
- const res = await authFetch(
- `${API_BASE}/api/v1/directives/${directiveId}/chain/steps/${stepId}`,
- {
- method: "DELETE",
- }
- );
- if (!res.ok) {
- throw new Error(`Failed to delete step: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Skip step */
-export async function skipDirectiveStep(
- directiveId: string,
- stepId: string
-): Promise<ChainStep> {
- const res = await authFetch(
- `${API_BASE}/api/v1/directives/${directiveId}/steps/${stepId}/skip`,
- {
- method: "POST",
- }
- );
- if (!res.ok) {
- throw new Error(`Failed to skip step: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** List directive evaluations */
-export async function listDirectiveEvaluations(
- directiveId: string,
- limit = 50
-): Promise<DirectiveEvaluation[]> {
- const res = await authFetch(
- `${API_BASE}/api/v1/directives/${directiveId}/evaluations?limit=${limit}`
- );
- if (!res.ok) {
- throw new Error(`Failed to list evaluations: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** List directive events */
-export async function listDirectiveEvents(
- directiveId: string,
- limit = 50
-): Promise<DirectiveEvent[]> {
- const res = await authFetch(
- `${API_BASE}/api/v1/directives/${directiveId}/events?limit=${limit}`
- );
- if (!res.ok) {
- throw new Error(`Failed to list events: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** List directive verifiers */
-export async function listDirectiveVerifiers(
- directiveId: string
-): Promise<DirectiveVerifier[]> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/verifiers`);
- if (!res.ok) {
- throw new Error(`Failed to list verifiers: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Add verifier */
-export async function addDirectiveVerifier(
- directiveId: string,
- req: CreateVerifierRequest
-): Promise<DirectiveVerifier> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/verifiers`, {
- method: "POST",
- body: JSON.stringify(req),
- });
- if (!res.ok) {
- throw new Error(`Failed to add verifier: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Update verifier */
-export async function updateDirectiveVerifier(
- directiveId: string,
- verifierId: string,
- req: UpdateVerifierRequest
-): Promise<DirectiveVerifier> {
- const res = await authFetch(
- `${API_BASE}/api/v1/directives/${directiveId}/verifiers/${verifierId}`,
- {
- method: "PUT",
- body: JSON.stringify(req),
- }
- );
- if (!res.ok) {
- throw new Error(`Failed to update verifier: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Auto-detect verifiers */
-export async function autoDetectVerifiers(
- directiveId: string
-): Promise<DirectiveVerifier[]> {
- const res = await authFetch(
- `${API_BASE}/api/v1/directives/${directiveId}/verifiers/auto-detect`,
- {
- method: "POST",
- }
- );
- if (!res.ok) {
- throw new Error(`Failed to auto-detect verifiers: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** List pending approvals */
-export async function listDirectiveApprovals(
- directiveId: string
-): Promise<DirectiveApproval[]> {
- const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/approvals`);
- if (!res.ok) {
- throw new Error(`Failed to list approvals: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Approve request */
-export async function approveDirectiveRequest(
- directiveId: string,
- approvalId: string,
- req?: ApprovalActionRequest
-): Promise<DirectiveApproval> {
- const res = await authFetch(
- `${API_BASE}/api/v1/directives/${directiveId}/approvals/${approvalId}/approve`,
- {
- method: "POST",
- body: JSON.stringify(req || {}),
- }
- );
- if (!res.ok) {
- throw new Error(`Failed to approve request: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Deny request */
-export async function denyDirectiveRequest(
- directiveId: string,
- approvalId: string,
- req?: ApprovalActionRequest
-): Promise<DirectiveApproval> {
- const res = await authFetch(
- `${API_BASE}/api/v1/directives/${directiveId}/approvals/${approvalId}/deny`,
- {
- method: "POST",
- body: JSON.stringify(req || {}),
- }
- );
- if (!res.ok) {
- throw new Error(`Failed to deny request: ${res.statusText}`);
- }
- return res.json();
-}
-
-/** Subscribe to directive events via SSE */
-export async function subscribeToDirectiveEvents(
- directiveId: string,
- onEvent: (event: DirectiveEvent) => void,
- onError?: (error: Error) => void
-): Promise<() => void> {
- // Get auth token for the request
- let authToken: string | null = null;
- if (supabase) {
- const { data: { session } } = await supabase.auth.getSession();
- if (session?.access_token) {
- authToken = session.access_token;
- }
- }
-
- // Build URL with auth token as query param (since EventSource doesn't support headers)
- const url = new URL(`${API_BASE}/api/v1/directives/${directiveId}/events/stream`);
- if (authToken) {
- url.searchParams.set("token", authToken);
- } else {
- const apiKey = getStoredApiKey();
- if (apiKey) {
- url.searchParams.set("api_key", apiKey);
- }
- }
-
- // Create EventSource connection
- const eventSource = new EventSource(url.toString());
-
- eventSource.onmessage = (e) => {
- try {
- const event = JSON.parse(e.data) as DirectiveEvent;
- onEvent(event);
- } catch (err) {
- console.error("Failed to parse SSE event:", err);
- }
- };
-
- eventSource.onerror = (_e) => {
- if (onError) {
- onError(new Error("SSE connection error"));
- }
- };
-
- // Return cleanup function
- return () => {
- eventSource.close();
- };
-}
diff --git a/makima/frontend/src/main.tsx b/makima/frontend/src/main.tsx
index c90d292..50fffe4 100644
--- a/makima/frontend/src/main.tsx
+++ b/makima/frontend/src/main.tsx
@@ -12,7 +12,6 @@ import HomePage from "./routes/_index";
import ListenPage from "./routes/listen";
import FilesPage from "./routes/files";
import ContractsPage from "./routes/contracts";
-import DirectivesPage from "./routes/directives";
import WorkflowPage from "./routes/workflow";
import MeshPage from "./routes/mesh";
import HistoryPage from "./routes/history";
@@ -73,22 +72,6 @@ createRoot(document.getElementById("root")!).render(
}
/>
<Route
- path="/directives"
- element={
- <ProtectedRoute>
- <DirectivesPage />
- </ProtectedRoute>
- }
- />
- <Route
- path="/directives/:id"
- element={
- <ProtectedRoute>
- <DirectivesPage />
- </ProtectedRoute>
- }
- />
- <Route
path="/contracts/:id/files/:fileId"
element={
<ProtectedRoute>
diff --git a/makima/frontend/src/routes/directives.tsx b/makima/frontend/src/routes/directives.tsx
deleted file mode 100644
index 90f0854..0000000
--- a/makima/frontend/src/routes/directives.tsx
+++ /dev/null
@@ -1,184 +0,0 @@
-import { useState, useCallback, useEffect } from "react";
-import { useParams, useNavigate } from "react-router";
-import { Masthead } from "../components/Masthead";
-import { useDirectives } from "../hooks/useDirectives";
-import { useDirectiveDetail } from "../hooks/useDirectiveDetail";
-import { useAuth } from "../contexts/AuthContext";
-import type {
- DirectiveSummary,
- CreateDirectiveRequest,
- AutonomyLevel,
-} from "../lib/api";
-import { DirectiveList } from "../components/directives/DirectiveList";
-import { DirectiveDetail } from "../components/directives/DirectiveDetail";
-import { CreateDirectiveModal } from "../components/directives/CreateDirectiveModal";
-
-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]);
-
- // Show loading while checking auth
- 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>
- );
- }
-
- // Don't render if not authenticated (will redirect)
- if (isAuthConfigured && !isAuthenticated) {
- return null;
- }
-
- return <DirectivesPageContent />;
-}
-
-function DirectivesPageContent() {
- const { id } = useParams<{ id: string }>();
- const navigate = useNavigate();
- const {
- directives,
- loading,
- error,
- createNewDirective,
- archiveExistingDirective,
- } = useDirectives();
-
- const {
- directive: directiveDetail,
- graph: directiveGraph,
- loading: detailLoading,
- refresh: refreshDetail,
- start: handleStart,
- pause: handlePause,
- resume: handleResume,
- stop: handleStop,
- } = useDirectiveDetail(id);
-
- const [isCreating, setIsCreating] = useState(false);
-
- const handleSelect = useCallback(
- (directiveId: string) => {
- navigate(`/directives/${directiveId}`);
- },
- [navigate]
- );
-
- const handleBack = useCallback(() => {
- navigate("/directives");
- }, [navigate]);
-
- const handleCreate = useCallback(() => {
- setIsCreating(true);
- }, []);
-
- const handleCreateSubmit = useCallback(
- async (goal: string, repositoryUrl: string | undefined, autonomyLevel: AutonomyLevel) => {
- const data: CreateDirectiveRequest = {
- goal: goal.trim(),
- repositoryUrl: repositoryUrl?.trim() || undefined,
- autonomyLevel,
- };
-
- try {
- const result = await createNewDirective(data);
- if (result) {
- setIsCreating(false);
- navigate(`/directives/${result.id}`);
- }
- } catch (err) {
- console.error("Failed to create directive:", err);
- }
- },
- [createNewDirective, navigate]
- );
-
- const handleCreateCancel = useCallback(() => {
- setIsCreating(false);
- }, []);
-
- const handleArchive = useCallback(
- async (directive: DirectiveSummary) => {
- if (confirm(`Are you sure you want to archive this directive?`)) {
- const success = await archiveExistingDirective(directive.id);
- if (success && directive.id === id) {
- navigate("/directives");
- }
- }
- },
- [archiveExistingDirective, id, navigate]
- );
-
- 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 */}
- {isCreating && (
- <CreateDirectiveModal
- onSubmit={handleCreateSubmit}
- onCancel={handleCreateCancel}
- />
- )}
-
- <div className="flex-1 grid grid-cols-[350px_1fr] gap-4 min-h-0">
- {/* Directive list */}
- <DirectiveList
- directives={directives}
- loading={loading}
- onSelect={handleSelect}
- onCreate={handleCreate}
- selectedId={id}
- onArchive={handleArchive}
- />
-
- {/* Directive detail or empty state */}
- {directiveDetail ? (
- <DirectiveDetail
- directive={directiveDetail}
- graph={directiveGraph}
- loading={detailLoading}
- onBack={handleBack}
- onRefresh={refreshDetail}
- onStart={handleStart}
- onPause={handlePause}
- onResume={handleResume}
- onStop={handleStop}
- />
- ) : (
- <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={handleCreate}
- 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/frontend/tsconfig.tsbuildinfo b/makima/frontend/tsconfig.tsbuildinfo
index cbaa81f..0fbecaa 100644
--- a/makima/frontend/tsconfig.tsbuildinfo
+++ b/makima/frontend/tsconfig.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/commandmodepanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/directives/approvalstab.tsx","./src/components/directives/chaintab.tsx","./src/components/directives/createdirectivemodal.tsx","./src/components/directives/directivedetail.tsx","./src/components/directives/directivelist.tsx","./src/components/directives/directivelistitem.tsx","./src/components/directives/evaluationstab.tsx","./src/components/directives/eventstab.tsx","./src/components/directives/overviewtab.tsx","./src/components/directives/stepnode.tsx","./src/components/directives/verifierstab.tsx","./src/components/directives/index.ts","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/discusscontractmodal.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/mesh/worktreefilespanel.tsx","./src/components/workflow/phasecolumn.tsx","./src/components/workflow/workflowboard.tsx","./src/components/workflow/workflowcontractcard.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usedirectivedetail.ts","./src/hooks/usedirectives.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usespeakwebsocket.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/directives.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/settings.tsx","./src/routes/speak.tsx","./src/routes/workflow.tsx","./src/types/messages.ts"],"version":"5.9.3"} \ No newline at end of file
+{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/commandmodepanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/discusscontractmodal.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/mesh/worktreefilespanel.tsx","./src/components/workflow/phasecolumn.tsx","./src/components/workflow/workflowboard.tsx","./src/components/workflow/workflowcontractcard.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usespeakwebsocket.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/settings.tsx","./src/routes/speak.tsx","./src/routes/workflow.tsx","./src/types/messages.ts"],"version":"5.9.3"} \ No newline at end of file
diff --git a/makima/migrations/20260207000000_directive_v2_wipe.sql b/makima/migrations/20260207000000_directive_v2_wipe.sql
new file mode 100644
index 0000000..605b4b4
--- /dev/null
+++ b/makima/migrations/20260207000000_directive_v2_wipe.sql
@@ -0,0 +1,331 @@
+-- ============================================================================
+-- Migration: directive_v2_wipe.sql
+-- Wipes all directive system tables and recreates them clean.
+-- This is a destructive migration — all directive data will be lost.
+-- ============================================================================
+
+-- Drop 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 directive columns from contracts BEFORE dropping directives table
+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;
+
+DROP TABLE IF EXISTS directives CASCADE;
+
+-- ============================================================================
+-- 1. DIRECTIVES
+-- ============================================================================
+CREATE TABLE directives (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ owner_id UUID NOT NULL REFERENCES owners(id) ON DELETE CASCADE,
+
+ -- Goal specification
+ title VARCHAR(500) NOT NULL,
+ goal TEXT NOT NULL,
+
+ -- Structured specification (JSONB arrays)
+ requirements JSONB NOT NULL DEFAULT '[]',
+ acceptance_criteria JSONB NOT NULL DEFAULT '[]',
+ constraints JSONB NOT NULL DEFAULT '[]',
+ external_dependencies JSONB NOT NULL DEFAULT '[]',
+
+ -- State
+ status VARCHAR(32) NOT NULL DEFAULT 'draft'
+ CHECK (status IN ('draft', 'planning', 'active', 'paused', 'completed', 'archived', 'failed')),
+
+ -- Autonomy configuration
+ autonomy_level VARCHAR(32) NOT NULL DEFAULT 'guardrails'
+ CHECK (autonomy_level IN ('full_auto', 'guardrails', 'manual')),
+ confidence_threshold_green FLOAT NOT NULL DEFAULT 0.85,
+ confidence_threshold_yellow FLOAT NOT NULL DEFAULT 0.60,
+
+ -- Circuit breaker limits
+ max_total_cost_usd FLOAT,
+ max_wall_time_minutes INTEGER,
+ max_rework_cycles INTEGER DEFAULT 3,
+ max_chain_regenerations INTEGER DEFAULT 2,
+
+ -- Repository configuration (inherited by all steps)
+ repository_url VARCHAR(512),
+ local_path VARCHAR(512),
+ base_branch VARCHAR(255),
+
+ -- Orchestrator contract reference
+ orchestrator_contract_id UUID REFERENCES contracts(id) ON DELETE SET NULL,
+
+ -- Tracking
+ current_chain_id UUID, -- FK added after directive_chains table
+ chain_generation_count INTEGER NOT NULL DEFAULT 0,
+ total_cost_usd FLOAT NOT NULL DEFAULT 0.0,
+ started_at TIMESTAMPTZ,
+ completed_at TIMESTAMPTZ,
+
+ version INTEGER NOT NULL DEFAULT 1,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_directives_owner_id ON directives(owner_id);
+CREATE INDEX idx_directives_status ON directives(status);
+CREATE INDEX idx_directives_orchestrator_contract
+ ON directives(orchestrator_contract_id)
+ WHERE orchestrator_contract_id IS NOT NULL;
+
+-- Add directive reference to contracts
+ALTER TABLE contracts ADD COLUMN directive_id UUID REFERENCES directives(id) ON DELETE SET NULL;
+ALTER TABLE contracts ADD COLUMN is_directive_orchestrator BOOLEAN NOT NULL DEFAULT false;
+ALTER TABLE contracts ADD COLUMN spawned_directive_id UUID REFERENCES directives(id) ON DELETE SET NULL;
+
+-- ============================================================================
+-- 2. DIRECTIVE CHAINS
+-- ============================================================================
+CREATE TABLE directive_chains (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ directive_id UUID NOT NULL REFERENCES directives(id) ON DELETE CASCADE,
+ generation INTEGER NOT NULL DEFAULT 1,
+
+ -- Plan metadata
+ name VARCHAR(255) NOT NULL,
+ description TEXT,
+ rationale TEXT,
+ planning_model VARCHAR(100),
+
+ -- State
+ status VARCHAR(32) NOT NULL DEFAULT 'pending'
+ CHECK (status IN ('pending', 'running', 'completed', 'failed', 'superseded')),
+
+ -- Execution tracking
+ total_steps INTEGER NOT NULL DEFAULT 0,
+ completed_steps INTEGER NOT NULL DEFAULT 0,
+ failed_steps INTEGER NOT NULL DEFAULT 0,
+ current_confidence FLOAT,
+
+ -- Timestamps
+ started_at TIMESTAMPTZ,
+ completed_at TIMESTAMPTZ,
+ version INTEGER NOT NULL DEFAULT 1,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_directive_chains_directive ON directive_chains(directive_id);
+CREATE INDEX idx_directive_chains_status ON directive_chains(status);
+
+-- Add FK from directives to chains
+ALTER TABLE directives
+ ADD CONSTRAINT fk_directives_current_chain
+ FOREIGN KEY (current_chain_id) REFERENCES directive_chains(id)
+ ON DELETE SET NULL;
+
+-- ============================================================================
+-- 3. CHAIN STEPS
+-- ============================================================================
+CREATE TABLE chain_steps (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ chain_id UUID NOT NULL REFERENCES directive_chains(id) ON DELETE CASCADE,
+
+ -- Step definition
+ name VARCHAR(255) NOT NULL,
+ description TEXT,
+ step_type VARCHAR(32) NOT NULL DEFAULT 'execute',
+
+ -- Contract template
+ contract_type VARCHAR(32) NOT NULL DEFAULT 'simple',
+ initial_phase VARCHAR(32) DEFAULT 'plan',
+ task_plan TEXT,
+ phases TEXT[] DEFAULT '{}',
+
+ -- DAG edges
+ depends_on UUID[] DEFAULT '{}',
+ parallel_group VARCHAR(100),
+
+ -- Requirement traceability
+ requirement_ids TEXT[] DEFAULT '{}',
+ acceptance_criteria_ids TEXT[] DEFAULT '{}',
+
+ -- Verification configuration
+ verifier_config JSONB DEFAULT '{}',
+
+ -- State
+ status VARCHAR(32) NOT NULL DEFAULT 'pending'
+ CHECK (status IN ('pending', 'ready', 'running', 'evaluating', 'passed', 'failed', 'rework', 'skipped', 'blocked')),
+
+ -- Instantiated references
+ contract_id UUID REFERENCES contracts(id) ON DELETE SET NULL,
+ supervisor_task_id UUID,
+
+ -- Evaluation tracking
+ confidence_score FLOAT,
+ confidence_level VARCHAR(10),
+ evaluation_count INTEGER NOT NULL DEFAULT 0,
+ rework_count INTEGER NOT NULL DEFAULT 0,
+ last_evaluation_id UUID,
+
+ -- Editor layout
+ editor_x FLOAT DEFAULT 0,
+ editor_y FLOAT DEFAULT 0,
+ order_index INTEGER NOT NULL DEFAULT 0,
+
+ -- Timestamps
+ started_at TIMESTAMPTZ,
+ completed_at TIMESTAMPTZ,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_chain_steps_chain ON chain_steps(chain_id);
+CREATE INDEX idx_chain_steps_status ON chain_steps(status);
+CREATE INDEX idx_chain_steps_contract ON chain_steps(contract_id) WHERE contract_id IS NOT NULL;
+CREATE INDEX idx_chain_steps_supervisor_task
+ ON chain_steps(supervisor_task_id)
+ WHERE supervisor_task_id IS NOT NULL;
+
+-- ============================================================================
+-- 4. EVALUATIONS
+-- ============================================================================
+CREATE TABLE directive_evaluations (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ directive_id UUID NOT NULL REFERENCES directives(id) ON DELETE CASCADE,
+ chain_id UUID REFERENCES directive_chains(id) ON DELETE SET NULL,
+ step_id UUID REFERENCES chain_steps(id) ON DELETE SET NULL,
+ contract_id UUID REFERENCES contracts(id) ON DELETE SET NULL,
+
+ -- Evaluation metadata
+ evaluation_type VARCHAR(32) NOT NULL,
+ evaluation_number INTEGER NOT NULL DEFAULT 1,
+ evaluator VARCHAR(100),
+
+ -- Results
+ passed BOOLEAN NOT NULL,
+ overall_score FLOAT,
+ confidence_level VARCHAR(10),
+
+ -- Programmatic results
+ programmatic_results JSONB DEFAULT '[]',
+
+ -- LLM evaluation results
+ llm_results JSONB DEFAULT '{}',
+
+ -- Composite results
+ criteria_results JSONB NOT NULL DEFAULT '[]',
+ summary_feedback TEXT NOT NULL DEFAULT '',
+ rework_instructions TEXT,
+
+ -- Snapshots
+ directive_snapshot JSONB,
+ deliverables_snapshot JSONB,
+
+ -- Timing
+ started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ completed_at TIMESTAMPTZ,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_evaluations_directive ON directive_evaluations(directive_id);
+CREATE INDEX idx_evaluations_step ON directive_evaluations(step_id);
+CREATE INDEX idx_evaluations_chain ON directive_evaluations(chain_id);
+
+-- Add FK from chain_steps to evaluations
+ALTER TABLE chain_steps
+ ADD CONSTRAINT fk_steps_last_evaluation
+ FOREIGN KEY (last_evaluation_id) REFERENCES directive_evaluations(id)
+ ON DELETE SET NULL;
+
+-- ============================================================================
+-- 5. EVENTS
+-- ============================================================================
+CREATE TABLE directive_events (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ directive_id UUID NOT NULL REFERENCES directives(id) ON DELETE CASCADE,
+ chain_id UUID REFERENCES directive_chains(id) ON DELETE SET NULL,
+ step_id UUID REFERENCES chain_steps(id) ON DELETE SET NULL,
+
+ -- Event classification
+ event_type VARCHAR(64) NOT NULL,
+ severity VARCHAR(16) NOT NULL DEFAULT 'info',
+
+ -- Payload
+ event_data JSONB,
+
+ -- Actor
+ actor_type VARCHAR(32) NOT NULL DEFAULT 'system',
+ actor_id UUID,
+
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_events_directive ON directive_events(directive_id);
+CREATE INDEX idx_events_chain ON directive_events(chain_id);
+CREATE INDEX idx_events_step ON directive_events(step_id);
+CREATE INDEX idx_events_type ON directive_events(event_type);
+CREATE INDEX idx_events_created ON directive_events(created_at);
+
+-- ============================================================================
+-- 6. VERIFIERS
+-- ============================================================================
+CREATE TABLE directive_verifiers (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ directive_id UUID NOT NULL REFERENCES directives(id) ON DELETE CASCADE,
+
+ -- Definition
+ name VARCHAR(100) NOT NULL,
+ verifier_type VARCHAR(32) NOT NULL,
+
+ -- Configuration
+ command VARCHAR(1000),
+ working_directory VARCHAR(500),
+ timeout_seconds INTEGER DEFAULT 300,
+ environment JSONB DEFAULT '{}',
+
+ -- Detection
+ auto_detect BOOLEAN NOT NULL DEFAULT true,
+ detect_files TEXT[] DEFAULT '{}',
+
+ -- Scoring
+ weight FLOAT NOT NULL DEFAULT 1.0,
+ required BOOLEAN NOT NULL DEFAULT false,
+
+ -- State
+ enabled BOOLEAN NOT NULL DEFAULT true,
+ last_run_at TIMESTAMPTZ,
+ last_result JSONB,
+
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_verifiers_directive ON directive_verifiers(directive_id);
+
+-- ============================================================================
+-- 7. APPROVALS
+-- ============================================================================
+CREATE TABLE directive_approvals (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ directive_id UUID NOT NULL REFERENCES directives(id) ON DELETE CASCADE,
+ step_id UUID REFERENCES chain_steps(id) ON DELETE SET NULL,
+
+ -- Request
+ approval_type VARCHAR(64) NOT NULL,
+ description TEXT NOT NULL,
+ context JSONB,
+ urgency VARCHAR(16) NOT NULL DEFAULT 'normal',
+
+ -- Response
+ status VARCHAR(32) NOT NULL DEFAULT 'pending'
+ CHECK (status IN ('pending', 'approved', 'denied', 'expired')),
+ response TEXT,
+ responded_by UUID,
+ responded_at TIMESTAMPTZ,
+
+ expires_at TIMESTAMPTZ,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX idx_approvals_directive ON directive_approvals(directive_id);
+CREATE INDEX idx_approvals_status ON directive_approvals(status);
diff --git a/makima/src/bin/makima.rs b/makima/src/bin/makima.rs
index d7646c2..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};
@@ -31,7 +31,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Commands::Contract(cmd) => run_contract(cmd).await,
Commands::View(args) => run_view(args).await,
Commands::Config(cmd) => run_config(cmd).await,
- Commands::Directive(cmd) => run_directive(cmd).await,
}
}
@@ -802,154 +801,6 @@ async fn run_config(cmd: ConfigCommand) -> Result<(), Box<dyn std::error::Error
}
}
-/// Run directive commands.
-async fn run_directive(
- cmd: DirectiveCommand,
-) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
- match cmd {
- DirectiveCommand::Create(args) => {
- let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
- let result = client
- .create_directive(&args.goal, args.repository.as_deref(), &args.autonomy)
- .await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- DirectiveCommand::Status(args) => {
- let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
- let result = client.get_directive(args.directive_id).await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- DirectiveCommand::List(args) => {
- let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
- let result = client
- .list_directives(args.status.as_deref(), args.limit)
- .await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- DirectiveCommand::Steps(args) => {
- let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
- let result = client.get_directive_chain(args.directive_id).await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- DirectiveCommand::Graph(args) => {
- let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
- let result = client.get_directive_graph(args.directive_id).await?;
-
- if args.with_status {
- // Enhanced ASCII visualization with status
- if let Some(nodes) = result.0.get("nodes").and_then(|v| v.as_array()) {
- let mut by_depth: std::collections::HashMap<i32, Vec<(&str, &str)>> =
- std::collections::HashMap::new();
-
- for node in nodes {
- let name = node.get("name").and_then(|v| v.as_str()).unwrap_or("?");
- let status = node
- .get("status")
- .and_then(|v| v.as_str())
- .unwrap_or("pending");
- let depth = node.get("depth").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
- by_depth.entry(depth).or_default().push((name, status));
- }
-
- let directive_name = result
- .0
- .get("name")
- .and_then(|v| v.as_str())
- .unwrap_or("Directive");
- println!("Directive: {}", directive_name);
- println!();
-
- let max_depth = by_depth.keys().max().copied().unwrap_or(0);
- for depth in 0..=max_depth {
- if let Some(steps) = by_depth.get(&depth) {
- let indent = " ".repeat(depth as usize);
- for (name, status) in steps {
- let status_icon = match *status {
- "passed" | "completed" => "\u{2713}",
- "running" | "evaluating" => "\u{21bb}",
- "failed" | "blocked" => "\u{2717}",
- "rework" => "\u{21ba}",
- "skipped" => "\u{2212}",
- "ready" => "\u{25b7}",
- _ => "\u{25cb}",
- };
- println!("{}[{}] {} {}", indent, name, status_icon, status);
- }
- if depth < max_depth {
- println!("{} |", indent);
- println!("{} v", indent);
- }
- }
- }
- }
- } else {
- println!("{}", serde_json::to_string_pretty(&result.0)?);
- }
- }
- DirectiveCommand::Events(args) => {
- let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
- let result = client
- .list_directive_events(args.directive_id, args.limit)
- .await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- DirectiveCommand::Approve(args) => {
- let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
- let result = client
- .approve_directive_request(
- args.directive_id,
- args.approval_id,
- args.response.as_deref(),
- )
- .await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- DirectiveCommand::Deny(args) => {
- let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
- let result = client
- .deny_directive_request(
- args.directive_id,
- args.approval_id,
- args.reason.as_deref(),
- )
- .await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- DirectiveCommand::Start(args) => {
- let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
- eprintln!("Starting directive {}...", args.directive_id);
- let result = client.start_directive(args.directive_id).await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- DirectiveCommand::Pause(args) => {
- let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
- eprintln!("Pausing directive {}...", args.directive_id);
- let result = client.pause_directive(args.directive_id).await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- DirectiveCommand::Resume(args) => {
- let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
- eprintln!("Resuming directive {}...", args.directive_id);
- let result = client.resume_directive(args.directive_id).await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- DirectiveCommand::Stop(args) => {
- let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
- eprintln!("Stopping directive {}...", args.directive_id);
- let result = client.stop_directive(args.directive_id).await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- DirectiveCommand::Archive(args) => {
- let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
- eprintln!("Archiving directive {}...", args.directive_id);
- let result = client.archive_directive(args.directive_id).await?;
- println!("{}", serde_json::to_string(&result.0)?);
- }
- }
-
- Ok(())
-}
-
/// Load contracts from API
async fn load_contracts(client: &ApiClient) -> Result<Vec<ListItem>, Box<dyn std::error::Error + Send + Sync>> {
let result = client.list_contracts().await?;
diff --git a/makima/src/daemon/api/directive.rs b/makima/src/daemon/api/directive.rs
deleted file mode 100644
index 48762d6..0000000
--- a/makima/src/daemon/api/directive.rs
+++ /dev/null
@@ -1,447 +0,0 @@
-//! Directive API methods.
-
-use uuid::Uuid;
-
-use super::client::{ApiClient, ApiError};
-use super::supervisor::JsonValue;
-
-impl ApiClient {
- /// Create a new directive.
- pub async fn create_directive(
- &self,
- goal: &str,
- repository_url: Option<&str>,
- autonomy_level: &str,
- ) -> Result<JsonValue, ApiError> {
- #[derive(serde::Serialize)]
- #[serde(rename_all = "camelCase")]
- struct CreateRequest<'a> {
- goal: &'a str,
- repository_url: Option<&'a str>,
- autonomy_level: &'a str,
- }
- let req = CreateRequest {
- goal,
- repository_url,
- autonomy_level,
- };
- self.post("/api/v1/directives", &req).await
- }
-
- /// List all directives for the authenticated user.
- pub async fn list_directives(
- &self,
- status: Option<&str>,
- limit: i32,
- ) -> Result<JsonValue, ApiError> {
- let mut params = Vec::new();
- if let Some(s) = status {
- params.push(format!("status={}", s));
- }
- params.push(format!("limit={}", limit));
- let query_string = format!("?{}", params.join("&"));
- self.get(&format!("/api/v1/directives{}", query_string))
- .await
- }
-
- /// Get a directive by ID (includes progress info).
- pub async fn get_directive(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> {
- self.get(&format!("/api/v1/directives/{}", directive_id))
- .await
- }
-
- /// Archive a directive.
- pub async fn archive_directive(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> {
- self.delete_with_response(&format!("/api/v1/directives/{}", directive_id))
- .await
- }
-
- /// Start a directive (plans and begins execution).
- pub async fn start_directive(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> {
- self.post_empty(&format!("/api/v1/directives/{}/start", directive_id))
- .await
- }
-
- /// Pause a directive.
- pub async fn pause_directive(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> {
- self.post_empty(&format!("/api/v1/directives/{}/pause", directive_id))
- .await
- }
-
- /// Resume a paused directive.
- pub async fn resume_directive(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> {
- self.post_empty(&format!("/api/v1/directives/{}/resume", directive_id))
- .await
- }
-
- /// Stop a directive.
- pub async fn stop_directive(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> {
- self.post_empty(&format!("/api/v1/directives/{}/stop", directive_id))
- .await
- }
-
- /// Get the current chain and steps for a directive.
- pub async fn get_directive_chain(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> {
- self.get(&format!("/api/v1/directives/{}/chain", directive_id))
- .await
- }
-
- /// Get directive DAG structure for visualization.
- pub async fn get_directive_graph(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> {
- self.get(&format!("/api/v1/directives/{}/chain/graph", directive_id))
- .await
- }
-
- /// List events for a directive.
- pub async fn list_directive_events(
- &self,
- directive_id: Uuid,
- limit: i32,
- ) -> Result<JsonValue, ApiError> {
- self.get(&format!(
- "/api/v1/directives/{}/events?limit={}",
- directive_id, limit
- ))
- .await
- }
-
- /// List pending approvals for a directive.
- pub async fn list_directive_approvals(
- &self,
- directive_id: Uuid,
- ) -> Result<JsonValue, ApiError> {
- self.get(&format!("/api/v1/directives/{}/approvals", directive_id))
- .await
- }
-
- /// Approve an approval request.
- pub async fn approve_directive_request(
- &self,
- directive_id: Uuid,
- approval_id: Uuid,
- response: Option<&str>,
- ) -> Result<JsonValue, ApiError> {
- #[derive(serde::Serialize)]
- #[serde(rename_all = "camelCase")]
- struct ApprovalRequest<'a> {
- response: Option<&'a str>,
- }
- let req = ApprovalRequest { response };
- self.post(
- &format!(
- "/api/v1/directives/{}/approvals/{}/approve",
- directive_id, approval_id
- ),
- &req,
- )
- .await
- }
-
- /// Deny an approval request.
- pub async fn deny_directive_request(
- &self,
- directive_id: Uuid,
- approval_id: Uuid,
- response: Option<&str>,
- ) -> Result<JsonValue, ApiError> {
- #[derive(serde::Serialize)]
- #[serde(rename_all = "camelCase")]
- struct ApprovalRequest<'a> {
- response: Option<&'a str>,
- }
- let req = ApprovalRequest { response };
- self.post(
- &format!(
- "/api/v1/directives/{}/approvals/{}/deny",
- directive_id, approval_id
- ),
- &req,
- )
- .await
- }
-
- // =========================================================================
- // Chain operations
- // =========================================================================
-
- /// Force chain regeneration (replan).
- pub async fn replan_directive_chain(
- &self,
- directive_id: Uuid,
- ) -> Result<JsonValue, ApiError> {
- self.post_empty(&format!(
- "/api/v1/directives/{}/chain/replan",
- directive_id
- ))
- .await
- }
-
- // =========================================================================
- // Step management
- // =========================================================================
-
- /// Add a step to a directive's chain.
- pub async fn add_directive_step(
- &self,
- directive_id: Uuid,
- name: &str,
- description: Option<&str>,
- step_type: Option<&str>,
- depends_on: Option<Vec<Uuid>>,
- ) -> Result<JsonValue, ApiError> {
- #[derive(serde::Serialize)]
- #[serde(rename_all = "camelCase")]
- struct AddStepReq<'a> {
- name: &'a str,
- #[serde(skip_serializing_if = "Option::is_none")]
- description: Option<&'a str>,
- #[serde(skip_serializing_if = "Option::is_none")]
- step_type: Option<&'a str>,
- #[serde(skip_serializing_if = "Option::is_none")]
- depends_on: Option<Vec<Uuid>>,
- }
- let req = AddStepReq {
- name,
- description,
- step_type,
- depends_on,
- };
- self.post(
- &format!("/api/v1/directives/{}/chain/steps", directive_id),
- &req,
- )
- .await
- }
-
- /// Get a step by ID.
- pub async fn get_directive_step(
- &self,
- directive_id: Uuid,
- step_id: Uuid,
- ) -> Result<JsonValue, ApiError> {
- self.get(&format!(
- "/api/v1/directives/{}/chain/steps/{}",
- directive_id, step_id
- ))
- .await
- }
-
- /// Update a step.
- pub async fn update_directive_step(
- &self,
- directive_id: Uuid,
- step_id: Uuid,
- update: serde_json::Value,
- ) -> Result<JsonValue, ApiError> {
- self.put(
- &format!(
- "/api/v1/directives/{}/chain/steps/{}",
- directive_id, step_id
- ),
- &update,
- )
- .await
- }
-
- /// Delete a step.
- pub async fn delete_directive_step(
- &self,
- directive_id: Uuid,
- step_id: Uuid,
- ) -> Result<(), ApiError> {
- self.delete(&format!(
- "/api/v1/directives/{}/chain/steps/{}",
- directive_id, step_id
- ))
- .await
- }
-
- /// Skip a step.
- pub async fn skip_directive_step(
- &self,
- directive_id: Uuid,
- step_id: Uuid,
- ) -> Result<JsonValue, ApiError> {
- self.post_empty(&format!(
- "/api/v1/directives/{}/chain/steps/{}/skip",
- directive_id, step_id
- ))
- .await
- }
-
- /// Force re-evaluation of a step.
- pub async fn evaluate_directive_step(
- &self,
- directive_id: Uuid,
- step_id: Uuid,
- ) -> Result<JsonValue, ApiError> {
- self.post_empty(&format!(
- "/api/v1/directives/{}/chain/steps/{}/evaluate",
- directive_id, step_id
- ))
- .await
- }
-
- /// Trigger manual rework for a step.
- pub async fn rework_directive_step(
- &self,
- directive_id: Uuid,
- step_id: Uuid,
- instructions: Option<&str>,
- ) -> Result<JsonValue, ApiError> {
- #[derive(serde::Serialize)]
- #[serde(rename_all = "camelCase")]
- struct ReworkReq<'a> {
- instructions: Option<&'a str>,
- }
- let req = ReworkReq { instructions };
- self.post(
- &format!(
- "/api/v1/directives/{}/chain/steps/{}/rework",
- directive_id, step_id
- ),
- &req,
- )
- .await
- }
-
- // =========================================================================
- // Evaluations
- // =========================================================================
-
- /// List evaluations for a directive.
- pub async fn list_directive_evaluations(
- &self,
- directive_id: Uuid,
- ) -> Result<JsonValue, ApiError> {
- self.get(&format!(
- "/api/v1/directives/{}/evaluations",
- directive_id
- ))
- .await
- }
-
- // =========================================================================
- // Verifiers
- // =========================================================================
-
- /// List verifiers for a directive.
- pub async fn list_directive_verifiers(
- &self,
- directive_id: Uuid,
- ) -> Result<JsonValue, ApiError> {
- self.get(&format!("/api/v1/directives/{}/verifiers", directive_id))
- .await
- }
-
- /// Add a verifier to a directive.
- pub async fn add_directive_verifier(
- &self,
- directive_id: Uuid,
- name: &str,
- verifier_type: &str,
- command: Option<&str>,
- ) -> Result<JsonValue, ApiError> {
- #[derive(serde::Serialize)]
- #[serde(rename_all = "camelCase")]
- struct CreateVerifierReq<'a> {
- name: &'a str,
- verifier_type: &'a str,
- #[serde(skip_serializing_if = "Option::is_none")]
- command: Option<&'a str>,
- }
- let req = CreateVerifierReq {
- name,
- verifier_type,
- command,
- };
- self.post(
- &format!("/api/v1/directives/{}/verifiers", directive_id),
- &req,
- )
- .await
- }
-
- /// Update a verifier.
- pub async fn update_directive_verifier(
- &self,
- directive_id: Uuid,
- verifier_id: Uuid,
- update: serde_json::Value,
- ) -> Result<JsonValue, ApiError> {
- self.put(
- &format!(
- "/api/v1/directives/{}/verifiers/{}",
- directive_id, verifier_id
- ),
- &update,
- )
- .await
- }
-
- /// Auto-detect verifiers based on repository content.
- pub async fn auto_detect_directive_verifiers(
- &self,
- directive_id: Uuid,
- ) -> Result<JsonValue, ApiError> {
- self.post_empty(&format!(
- "/api/v1/directives/{}/verifiers/auto-detect",
- directive_id
- ))
- .await
- }
-
- // =========================================================================
- // Requirements & Spec
- // =========================================================================
-
- /// Update directive requirements.
- pub async fn update_directive_requirements(
- &self,
- directive_id: Uuid,
- requirements: serde_json::Value,
- ) -> Result<JsonValue, ApiError> {
- #[derive(serde::Serialize)]
- #[serde(rename_all = "camelCase")]
- struct UpdateReq {
- requirements: serde_json::Value,
- }
- let req = UpdateReq { requirements };
- self.put(
- &format!("/api/v1/directives/{}/requirements", directive_id),
- &req,
- )
- .await
- }
-
- /// Update directive acceptance criteria.
- pub async fn update_directive_criteria(
- &self,
- directive_id: Uuid,
- acceptance_criteria: serde_json::Value,
- ) -> Result<JsonValue, ApiError> {
- #[derive(serde::Serialize)]
- #[serde(rename_all = "camelCase")]
- struct UpdateReq {
- acceptance_criteria: serde_json::Value,
- }
- let req = UpdateReq { acceptance_criteria };
- self.put(
- &format!("/api/v1/directives/{}/criteria", directive_id),
- &req,
- )
- .await
- }
-
- /// Generate a specification from the directive's goal.
- pub async fn generate_directive_spec(
- &self,
- directive_id: Uuid,
- ) -> Result<JsonValue, ApiError> {
- self.post_empty(&format!(
- "/api/v1/directives/{}/generate-spec",
- directive_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 a2bb34b..0000000
--- a/makima/src/daemon/cli/directive.rs
+++ /dev/null
@@ -1,186 +0,0 @@
-//! Directive CLI commands for autonomous goal-driven orchestration.
-//!
-//! Directives are top-level goals that the system works toward. Each directive
-//! generates a chain of steps that are executed autonomously with configurable
-//! guardrails.
-
-use clap::Args;
-use uuid::Uuid;
-
-/// Common arguments for directive commands requiring API access.
-#[derive(Args, Debug, Clone)]
-pub struct DirectiveArgs {
- /// API URL
- #[arg(long, env = "MAKIMA_API_URL", default_value = "https://api.makima.jp", global = true)]
- pub api_url: String,
-
- /// API key for authentication
- #[arg(long, env = "MAKIMA_API_KEY", global = true)]
- pub api_key: String,
-}
-
-/// Arguments for the `create` command.
-#[derive(Args, Debug)]
-pub struct CreateArgs {
- #[command(flatten)]
- pub common: DirectiveArgs,
-
- /// The goal for the directive
- #[arg(short, long)]
- pub goal: String,
-
- /// Repository URL (optional)
- #[arg(short, long)]
- pub repository: Option<String>,
-
- /// Autonomy level: full_auto, guardrails, or manual
- #[arg(short, long, default_value = "guardrails")]
- pub autonomy: String,
-}
-
-/// Arguments for the `status` command.
-#[derive(Args, Debug)]
-pub struct StatusArgs {
- #[command(flatten)]
- pub common: DirectiveArgs,
-
- /// Directive ID
- pub directive_id: Uuid,
-}
-
-/// Arguments for the `list` command.
-#[derive(Args, Debug)]
-pub struct ListArgs {
- #[command(flatten)]
- pub common: DirectiveArgs,
-
- /// Filter by status (draft, planning, active, paused, completed, archived, failed)
- #[arg(long)]
- pub status: Option<String>,
-
- /// Limit number of results
- #[arg(long, default_value = "50")]
- pub limit: i32,
-}
-
-/// Arguments for the `steps` command.
-#[derive(Args, Debug)]
-pub struct StepsArgs {
- #[command(flatten)]
- pub common: DirectiveArgs,
-
- /// Directive ID
- pub directive_id: Uuid,
-}
-
-/// Arguments for the `events` command.
-#[derive(Args, Debug)]
-pub struct EventsArgs {
- #[command(flatten)]
- pub common: DirectiveArgs,
-
- /// Directive ID
- pub directive_id: Uuid,
-
- /// Limit number of events
- #[arg(long, default_value = "50")]
- pub limit: i32,
-}
-
-/// Arguments for the `approve` command.
-#[derive(Args, Debug)]
-pub struct ApproveArgs {
- #[command(flatten)]
- pub common: DirectiveArgs,
-
- /// Directive ID
- pub directive_id: Uuid,
-
- /// Approval ID
- pub approval_id: Uuid,
-
- /// Response message (optional)
- #[arg(short, long)]
- pub response: Option<String>,
-}
-
-/// Arguments for the `deny` command.
-#[derive(Args, Debug)]
-pub struct DenyArgs {
- #[command(flatten)]
- pub common: DirectiveArgs,
-
- /// Directive ID
- pub directive_id: Uuid,
-
- /// Approval ID
- pub approval_id: Uuid,
-
- /// Reason for denial (optional)
- #[arg(short, long)]
- pub reason: Option<String>,
-}
-
-/// Arguments for the `start` command.
-#[derive(Args, Debug)]
-pub struct StartArgs {
- #[command(flatten)]
- pub common: DirectiveArgs,
-
- /// Directive ID
- pub directive_id: Uuid,
-}
-
-/// Arguments for the `pause` command.
-#[derive(Args, Debug)]
-pub struct PauseArgs {
- #[command(flatten)]
- pub common: DirectiveArgs,
-
- /// Directive ID
- pub directive_id: Uuid,
-}
-
-/// Arguments for the `resume` command.
-#[derive(Args, Debug)]
-pub struct ResumeArgs {
- #[command(flatten)]
- pub common: DirectiveArgs,
-
- /// Directive ID
- pub directive_id: Uuid,
-}
-
-/// Arguments for the `stop` command.
-#[derive(Args, Debug)]
-pub struct StopArgs {
- #[command(flatten)]
- pub common: DirectiveArgs,
-
- /// Directive ID
- pub directive_id: Uuid,
-}
-
-/// Arguments for the `archive` command.
-#[derive(Args, Debug)]
-pub struct ArchiveArgs {
- #[command(flatten)]
- pub common: DirectiveArgs,
-
- /// Directive ID
- pub directive_id: Uuid,
-}
-
-/// Arguments for the `graph` command (ASCII DAG visualization).
-#[derive(Args, Debug)]
-pub struct GraphArgs {
- #[command(flatten)]
- pub common: DirectiveArgs,
-
- /// Directive ID
- pub directive_id: Uuid,
-
- /// Show step status in nodes
- #[arg(long)]
- pub with_status: bool,
-}
diff --git a/makima/src/daemon/cli/mod.rs b/makima/src/daemon/cli/mod.rs
index 77eee80..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;
@@ -60,14 +58,6 @@ pub enum Commands {
/// Saves configuration to ~/.makima/config.toml for use by CLI commands.
#[command(subcommand)]
Config(ConfigCommand),
-
- /// Directive commands for autonomous goal-driven orchestration
- ///
- /// Directives are top-level goals that generate chains of steps executed
- /// autonomously with configurable guardrails. Steps spawn contracts with
- /// supervisors and are verified with programmatic and LLM evaluation.
- #[command(subcommand)]
- Directive(DirectiveCommand),
}
/// Config subcommands for CLI configuration.
@@ -206,52 +196,6 @@ pub enum ContractCommand {
CreateFile(contract::CreateFileArgs),
}
-/// Directive subcommands for autonomous goal-driven orchestration.
-#[derive(Subcommand, Debug)]
-pub enum DirectiveCommand {
- /// Create a new directive from a goal
- Create(directive::CreateArgs),
-
- /// Get directive status and progress
- Status(directive::StatusArgs),
-
- /// List all directives
- List(directive::ListArgs),
-
- /// List steps in the directive's chain
- Steps(directive::StepsArgs),
-
- /// Display ASCII DAG visualization
- ///
- /// Shows the directive's chain structure as an ASCII graph with
- /// steps as nodes and dependencies as edges.
- Graph(directive::GraphArgs),
-
- /// Show recent events for a directive
- Events(directive::EventsArgs),
-
- /// Approve a pending approval request
- Approve(directive::ApproveArgs),
-
- /// Deny a pending approval request
- Deny(directive::DenyArgs),
-
- /// Start a directive (generates chain and begins execution)
- Start(directive::StartArgs),
-
- /// Pause a running directive
- Pause(directive::PauseArgs),
-
- /// Resume a paused directive
- Resume(directive::ResumeArgs),
-
- /// Stop a directive
- Stop(directive::StopArgs),
-
- /// Archive a directive
- Archive(directive::ArchiveArgs),
-}
-
impl Cli {
/// Parse command-line arguments
pub fn parse_args() -> Self {
diff --git a/makima/src/daemon/skills/directive.md b/makima/src/daemon/skills/directive.md
deleted file mode 100644
index 97e8e20..0000000
--- a/makima/src/daemon/skills/directive.md
+++ /dev/null
@@ -1,303 +0,0 @@
----
-name: makima-directive
-description: Directive orchestration tools for autonomous goal-driven execution. Use when working with directives, chains, steps, verifiers, and approvals.
----
-
-# Directive Orchestration Tools
-
-Directives are top-level goals that drive autonomous execution with configurable guardrails. Each directive generates a chain of steps that spawn contracts with supervisors, verified by programmatic checks and LLM evaluation.
-
-## Architecture
-
-```
-Directive (goal + requirements + acceptance criteria)
- |
- +-- Chain (generated DAG execution plan)
- | +-- Step 1 (pending -> ready -> running -> evaluating -> passed)
- | | +-- Contract (spawned when step reaches 'ready')
- | | +-- Supervisor Task
- | +-- Step 2 (depends_on: [Step 1])
- | +-- Step 3 (depends_on: [Step 1], parallel with Step 2)
- |
- +-- Verifiers (test runner, linter, build, type checker)
- +-- Evaluations (programmatic + LLM composite scores)
- +-- Events (audit stream)
- +-- Approvals (human-in-the-loop gates)
-```
-
-## Status Flow
-
-### Directive Status
-- `draft` - Created but not started
-- `planning` - Generating chain from requirements
-- `active` - Executing steps
-- `paused` - Temporarily stopped
-- `completed` - All steps passed
-- `archived` - No longer active
-- `failed` - Execution failed
-
-### Step Status
-- `pending` - Waiting for dependencies
-- `ready` - Dependencies met, ready to start
-- `running` - Contract executing
-- `evaluating` - Running verifiers
-- `passed` - Evaluation succeeded
-- `failed` - Evaluation failed, exceeded retries
-- `rework` - Sent back for corrections
-- `skipped` - Manually skipped
-- `blocked` - Blocked by failed dependency
-
-## Autonomy Levels
-
-- `full_auto` - No approval gates, automatic progression
-- `guardrails` - Request approval for yellow/red confidence scores
-- `manual` - Request approval for all step completions
-
-## Confidence Scoring
-
-Each step evaluation produces a composite confidence score:
-
-1. **Programmatic verifiers** run first (tests, lint, build)
- - Weight: 1.0 each
- - If any required verifier fails: automatic RED
-
-2. **LLM evaluation** runs second
- - Weight: 2.0
- - Evaluates against acceptance criteria
-
-3. **Composite score** computed from weighted average
- - GREEN: >= configured threshold (default 0.8)
- - YELLOW: >= yellow threshold (default 0.5)
- - RED: below yellow threshold
-
-## CLI Commands
-
-```bash
-# Create a new directive
-makima directive create --goal "Add OAuth2 authentication" --repository https://github.com/org/repo
-
-# List directives
-makima directive list [--status active]
-
-# Get directive status with progress
-makima directive status <directive-id>
-
-# Start execution (generates chain and begins)
-makima directive start <directive-id>
-
-# View chain steps
-makima directive steps <directive-id>
-
-# View DAG visualization
-makima directive graph <directive-id> --with-status
-
-# View recent events
-makima directive events <directive-id> --limit 20
-
-# Approve a pending request
-makima directive approve <directive-id> <approval-id> [--response "Looks good"]
-
-# Deny a pending request
-makima directive deny <directive-id> <approval-id> [--reason "Need more testing"]
-
-# Lifecycle commands
-makima directive pause <directive-id>
-makima directive resume <directive-id>
-makima directive stop <directive-id>
-makima directive archive <directive-id>
-```
-
-## API Endpoints
-
-### Directive CRUD
-```
-POST /api/v1/directives # Create from goal
-GET /api/v1/directives # List
-GET /api/v1/directives/:id # Get with progress
-PUT /api/v1/directives/:id # Update
-DELETE /api/v1/directives/:id # Archive
-```
-
-### Lifecycle
-```
-POST /api/v1/directives/:id/start # Plan + execute
-POST /api/v1/directives/:id/pause # Pause
-POST /api/v1/directives/:id/resume # Resume
-POST /api/v1/directives/:id/stop # Stop
-```
-
-### Chain & Steps
-```
-GET /api/v1/directives/:id/chain # Current chain + steps
-GET /api/v1/directives/:id/chain/graph # DAG for visualization
-POST /api/v1/directives/:id/chain/replan # Force regeneration
-POST /api/v1/directives/:id/chain/steps # Add step
-PUT /api/v1/directives/:id/chain/steps/:sid # Modify step
-DELETE /api/v1/directives/:id/chain/steps/:sid # Remove step
-```
-
-### Step Operations
-```
-GET /api/v1/directives/:id/steps/:sid # Step detail
-POST /api/v1/directives/:id/steps/:sid/evaluate # Force re-evaluation
-POST /api/v1/directives/:id/steps/:sid/skip # Skip step
-POST /api/v1/directives/:id/steps/:sid/rework # Manual rework
-```
-
-### Monitoring
-```
-GET /api/v1/directives/:id/evaluations # List evaluations
-GET /api/v1/directives/:id/events # Event log (polling)
-GET /api/v1/directives/:id/events/stream # Event stream (SSE)
-```
-
-### Verifiers
-```
-GET /api/v1/directives/:id/verifiers # List verifiers
-POST /api/v1/directives/:id/verifiers # Add verifier
-PUT /api/v1/directives/:id/verifiers/:vid # Update verifier
-POST /api/v1/directives/:id/verifiers/auto-detect # Auto-detect
-```
-
-### Approvals
-```
-GET /api/v1/directives/:id/approvals # Pending approvals
-POST /api/v1/directives/:id/approvals/:aid/approve # Approve
-POST /api/v1/directives/:id/approvals/:aid/deny # Deny
-```
-
-## Creating a Directive
-
-### Request
-```json
-POST /api/v1/directives
-{
- "goal": "Implement user authentication with OAuth2",
- "repositoryUrl": "https://github.com/org/repo",
- "autonomyLevel": "guardrails",
- "confidenceThresholdGreen": 0.8,
- "confidenceThresholdYellow": 0.5,
- "maxReworkCycles": 3,
- "maxTotalCostUsd": 100.0,
- "maxWallTimeMinutes": 480
-}
-```
-
-### Response
-```json
-{
- "id": "uuid",
- "title": "Implement user authentication with OAuth2",
- "goal": "Implement user authentication with OAuth2",
- "status": "draft",
- "autonomyLevel": "guardrails",
- "createdAt": "2026-02-05T12:00:00Z"
-}
-```
-
-## Starting a Directive
-
-When you start a directive:
-1. System generates requirements from the goal
-2. Chain planner creates a DAG of steps
-3. Root steps (no dependencies) transition to `ready`
-4. Contracts spawn for ready steps with supervisors
-5. Verifiers auto-detect from repository
-
-## Evaluation Flow
-
-When a contract completes:
-
-1. Step transitions to `evaluating`
-2. **Programmatic verifiers** run (tests, lint, build)
- - Each produces pass/fail + output
-3. **LLM evaluation** runs
- - Reviews code against acceptance criteria
- - Provides feedback and score
-4. **Composite score** computed
-5. Based on confidence level and autonomy:
- - GREEN: Step passes, downstream unblocks
- - YELLOW (guardrails): Request approval
- - RED: Initiate rework or request approval
-
-## Rework Flow
-
-When a step needs rework:
-
-1. Contract phase reset to editing
-2. Supervisor receives rework instructions
-3. Rework count incremented
-4. If max reworks exceeded: escalate or fail
-
-## Event Types
-
-Events are logged for audit and monitoring:
-
-- `directive_created`, `directive_started`, `directive_paused`, `directive_completed`
-- `chain_generated`, `chain_regenerated`
-- `step_ready`, `step_started`, `step_evaluating`, `step_passed`, `step_failed`
-- `rework_initiated`, `rework_completed`
-- `approval_requested`, `approval_granted`, `approval_denied`
-- `verifier_run`, `evaluation_completed`
-- `circuit_breaker_triggered`
-
-## Verifier Configuration
-
-Verifiers can be auto-detected or manually configured:
-
-```json
-POST /api/v1/directives/:id/verifiers
-{
- "name": "Test Runner",
- "verifierType": "test_runner",
- "command": "npm test",
- "workingDirectory": ".",
- "timeoutSeconds": 300,
- "weight": 1.0,
- "required": true,
- "enabled": true
-}
-```
-
-### Auto-Detection
-
-The system detects verifiers from:
-- `package.json` - npm test, npm run lint, npm run build
-- `Cargo.toml` - cargo test, cargo clippy, cargo build
-- `pyproject.toml` - pytest, ruff, mypy
-
-## Circuit Breakers
-
-Directives have built-in circuit breakers:
-
-- `maxTotalCostUsd` - Stop if cumulative cost exceeds limit
-- `maxWallTimeMinutes` - Stop if elapsed time exceeds limit
-- `maxReworkCycles` - Fail step after N rework attempts
-- `maxChainRegenerations` - Fail if chain regenerated too many times
-
-## Example Workflow
-
-```bash
-# 1. Create a directive
-makima directive create \
- --goal "Add dark mode to the application" \
- --repository https://github.com/myorg/myapp \
- --autonomy guardrails
-
-# Returns directive ID: 123e4567-e89b-12d3-a456-426614174000
-
-# 2. Start execution
-makima directive start 123e4567-e89b-12d3-a456-426614174000
-
-# 3. Monitor progress
-makima directive status 123e4567-e89b-12d3-a456-426614174000
-
-# 4. View the execution graph
-makima directive graph 123e4567-e89b-12d3-a456-426614174000 --with-status
-
-# 5. Watch events
-makima directive events 123e4567-e89b-12d3-a456-426614174000
-
-# 6. If approval needed, approve or deny
-makima directive approve 123e4567-e89b-12d3-a456-426614174000 <approval-id>
-```
diff --git a/makima/src/daemon/skills/mod.rs b/makima/src/daemon/skills/mod.rs
index c32f550..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 - autonomous goal-driven orchestration
-pub const DIRECTIVE_SKILL: &str = include_str!("directive.md");
-
/// All skills as (name, content) pairs for installation
pub const ALL_SKILLS: &[(&str, &str)] = &[
("makima-supervisor", SUPERVISOR_SKILL),
("makima-contract", CONTRACT_SKILL),
- ("makima-directive", DIRECTIVE_SKILL),
];
diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs
index f951751..3b10cb5 100644
--- a/makima/src/db/models.rs
+++ b/makima/src/db/models.rs
@@ -2596,672 +2596,6 @@ pub struct HeartbeatHistoryQuery {
}
// =============================================================================
-// Directives (Goal-driven orchestration with chains of steps)
-// =============================================================================
-
-/// Directive status
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum DirectiveStatus {
- Draft,
- Planning,
- Active,
- Paused,
- Completed,
- Archived,
- Failed,
-}
-
-impl Default for DirectiveStatus {
- fn default() -> Self {
- DirectiveStatus::Draft
- }
-}
-
-impl std::fmt::Display for DirectiveStatus {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- DirectiveStatus::Draft => write!(f, "draft"),
- DirectiveStatus::Planning => write!(f, "planning"),
- DirectiveStatus::Active => write!(f, "active"),
- DirectiveStatus::Paused => write!(f, "paused"),
- DirectiveStatus::Completed => write!(f, "completed"),
- DirectiveStatus::Archived => write!(f, "archived"),
- DirectiveStatus::Failed => write!(f, "failed"),
- }
- }
-}
-
-impl std::str::FromStr for DirectiveStatus {
- type Err = String;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- match s.to_lowercase().as_str() {
- "draft" => Ok(DirectiveStatus::Draft),
- "planning" => Ok(DirectiveStatus::Planning),
- "active" => Ok(DirectiveStatus::Active),
- "paused" => Ok(DirectiveStatus::Paused),
- "completed" => Ok(DirectiveStatus::Completed),
- "archived" => Ok(DirectiveStatus::Archived),
- "failed" => Ok(DirectiveStatus::Failed),
- _ => Err(format!("Invalid directive status: {}", s)),
- }
- }
-}
-
-/// Directive - the top-level goal-driven orchestration entity
-#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct Directive {
- pub id: Uuid,
- pub owner_id: Uuid,
- pub title: String,
- pub goal: String,
- /// Structured requirements: [{ id, title, description, priority, category }]
- #[sqlx(json)]
- pub requirements: serde_json::Value,
- /// Acceptance criteria: [{ id, requirementIds, description, testable, verificationMethod }]
- #[sqlx(json)]
- pub acceptance_criteria: serde_json::Value,
- /// Constraints: [{ id, type, description, impact }]
- #[sqlx(json)]
- pub constraints: serde_json::Value,
- /// External dependencies: [{ id, name, type, status, requiredBy }]
- #[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>,
-}
-
-impl Directive {
- /// Parse status string to DirectiveStatus enum
- pub fn status_enum(&self) -> Result<DirectiveStatus, String> {
- self.status.parse()
- }
-}
-
-/// Directive chain - a generated execution plan (DAG) for a directive
-#[derive(Debug, Clone, FromRow, Serialize, Deserialize, 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>,
-}
-
-/// Chain step status
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum StepStatus {
- Pending,
- Ready,
- Running,
- Evaluating,
- Passed,
- Failed,
- Rework,
- Skipped,
- Blocked,
-}
-
-impl Default for StepStatus {
- fn default() -> Self {
- StepStatus::Pending
- }
-}
-
-impl std::fmt::Display for StepStatus {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- StepStatus::Pending => write!(f, "pending"),
- StepStatus::Ready => write!(f, "ready"),
- StepStatus::Running => write!(f, "running"),
- StepStatus::Evaluating => write!(f, "evaluating"),
- StepStatus::Passed => write!(f, "passed"),
- StepStatus::Failed => write!(f, "failed"),
- StepStatus::Rework => write!(f, "rework"),
- StepStatus::Skipped => write!(f, "skipped"),
- StepStatus::Blocked => write!(f, "blocked"),
- }
- }
-}
-
-impl std::str::FromStr for StepStatus {
- type Err = String;
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- match s.to_lowercase().as_str() {
- "pending" => Ok(StepStatus::Pending),
- "ready" => Ok(StepStatus::Ready),
- "running" => Ok(StepStatus::Running),
- "evaluating" => Ok(StepStatus::Evaluating),
- "passed" => Ok(StepStatus::Passed),
- "failed" => Ok(StepStatus::Failed),
- "rework" => Ok(StepStatus::Rework),
- "skipped" => Ok(StepStatus::Skipped),
- "blocked" => Ok(StepStatus::Blocked),
- _ => Err(format!("Invalid step status: {}", s)),
- }
- }
-}
-
-/// Chain step - a node in the DAG execution plan
-#[derive(Debug, Clone, FromRow, Serialize, Deserialize, 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>,
- #[sqlx(default)]
- pub phases: Vec<String>,
- #[sqlx(default)]
- pub depends_on: Vec<Uuid>,
- pub parallel_group: Option<String>,
- #[sqlx(default)]
- pub requirement_ids: Vec<String>,
- #[sqlx(default)]
- pub acceptance_criteria_ids: Vec<String>,
- #[sqlx(json)]
- #[serde(default)]
- pub verifier_config: serde_json::Value,
- pub status: String,
- pub contract_id: Option<Uuid>,
- pub supervisor_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>,
-}
-
-impl ChainStep {
- /// Parse status string to StepStatus enum
- pub fn status_enum(&self) -> Result<StepStatus, String> {
- self.status.parse()
- }
-}
-
-/// Confidence level (traffic light)
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum ConfidenceLevel {
- Green,
- Yellow,
- Red,
-}
-
-impl ConfidenceLevel {
- pub fn from_score(score: f64, green_threshold: f64, yellow_threshold: f64) -> Self {
- if score >= green_threshold {
- Self::Green
- } else if score >= yellow_threshold {
- Self::Yellow
- } else {
- Self::Red
- }
- }
-}
-
-impl std::fmt::Display for ConfidenceLevel {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- ConfidenceLevel::Green => write!(f, "green"),
- ConfidenceLevel::Yellow => write!(f, "yellow"),
- ConfidenceLevel::Red => write!(f, "red"),
- }
- }
-}
-
-/// Directive evaluation - composite programmatic + LLM evaluation result
-#[derive(Debug, Clone, FromRow, Serialize, Deserialize, 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)]
- #[serde(default)]
- pub programmatic_results: serde_json::Value,
- #[sqlx(json)]
- #[serde(default)]
- pub llm_results: serde_json::Value,
- #[sqlx(json)]
- #[serde(default)]
- 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>,
-}
-
-/// Directive event - audit stream entry
-#[derive(Debug, Clone, FromRow, Serialize, Deserialize, 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>,
-}
-
-/// Directive verifier - pluggable verification configuration
-#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct DirectiveVerifier {
- pub id: Uuid,
- pub directive_id: Uuid,
- pub name: String,
- pub verifier_type: String,
- pub command: Option<String>,
- pub working_directory: Option<String>,
- pub timeout_seconds: Option<i32>,
- #[sqlx(json)]
- #[serde(default)]
- pub environment: serde_json::Value,
- pub auto_detect: bool,
- #[sqlx(default)]
- pub detect_files: Vec<String>,
- pub weight: f64,
- pub required: bool,
- pub enabled: bool,
- pub last_run_at: Option<DateTime<Utc>>,
- #[sqlx(json)]
- pub last_result: Option<serde_json::Value>,
- pub created_at: DateTime<Utc>,
- pub updated_at: DateTime<Utc>,
-}
-
-/// Directive approval - human-in-the-loop gate
-#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct DirectiveApproval {
- pub id: Uuid,
- pub directive_id: Uuid,
- pub step_id: Option<Uuid>,
- pub approval_type: String,
- pub description: String,
- #[sqlx(json)]
- pub context: Option<serde_json::Value>,
- pub urgency: String,
- pub status: String,
- pub response: Option<String>,
- pub responded_by: Option<Uuid>,
- pub responded_at: Option<DateTime<Utc>>,
- pub expires_at: Option<DateTime<Utc>>,
- pub created_at: DateTime<Utc>,
-}
-
-// =============================================================================
-// Directive Request/Response Types
-// =============================================================================
-
-/// Request to create a directive from a goal
-#[derive(Debug, Deserialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct CreateDirectiveRequest {
- pub goal: String,
- pub title: Option<String>,
- pub repository_url: Option<String>,
- pub local_path: Option<String>,
- pub base_branch: Option<String>,
- pub autonomy_level: Option<String>,
- pub requirements: Option<serde_json::Value>,
- pub acceptance_criteria: Option<serde_json::Value>,
- 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>,
-}
-
-/// Request to update a directive
-#[derive(Debug, 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 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 version: i32,
-}
-
-/// Directive summary for list views
-#[derive(Debug, Clone, Serialize, FromRow, 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 current_confidence: Option<f64>,
- pub completed_steps: i32,
- pub total_steps: i32,
- pub chain_generation_count: i32,
- pub started_at: Option<DateTime<Utc>>,
- pub created_at: DateTime<Utc>,
-}
-
-/// Directive with progress, chain, events, and approvals
-#[derive(Debug, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct DirectiveWithProgress {
- #[serde(flatten)]
- pub directive: Directive,
- pub chain: Option<DirectiveChain>,
- pub steps: Vec<ChainStep>,
- pub recent_events: Vec<DirectiveEvent>,
- pub pending_approvals: Vec<DirectiveApproval>,
-}
-
-/// Request to add a step to a chain
-#[derive(Debug, Deserialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct AddStepRequest {
- pub name: String,
- pub description: Option<String>,
- pub step_type: Option<String>,
- pub contract_type: Option<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>>,
- pub verifier_config: Option<serde_json::Value>,
- pub editor_x: Option<f64>,
- pub editor_y: Option<f64>,
-}
-
-/// Request to update a step
-#[derive(Debug, Deserialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct UpdateStepRequest {
- pub name: Option<String>,
- pub description: Option<String>,
- pub task_plan: Option<String>,
- pub depends_on: Option<Vec<Uuid>>,
- pub requirement_ids: Option<Vec<String>>,
- pub acceptance_criteria_ids: Option<Vec<String>>,
- pub verifier_config: Option<serde_json::Value>,
- pub editor_x: Option<f64>,
- pub editor_y: Option<f64>,
-}
-
-/// Chain graph response for DAG visualization
-#[derive(Debug, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct DirectiveChainGraphResponse {
- pub chain_id: Uuid,
- pub directive_id: Uuid,
- pub nodes: Vec<DirectiveChainGraphNode>,
- pub edges: Vec<DirectiveChainGraphEdge>,
-}
-
-/// Node in directive chain graph
-#[derive(Debug, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct DirectiveChainGraphNode {
- pub id: Uuid,
- pub name: String,
- pub step_type: String,
- pub status: String,
- pub confidence_score: Option<f64>,
- pub confidence_level: Option<String>,
- pub contract_id: Option<Uuid>,
- pub editor_x: Option<f64>,
- pub editor_y: Option<f64>,
-}
-
-/// Edge in directive chain graph
-#[derive(Debug, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct DirectiveChainGraphEdge {
- pub source: Uuid,
- pub target: Uuid,
-}
-
-/// Start directive response
-#[derive(Debug, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct StartDirectiveResponse {
- pub directive_id: Uuid,
- pub chain_id: Uuid,
- pub chain_generation: i32,
- pub steps: Vec<ChainStep>,
- pub status: String,
-}
-
-/// Request to create a verifier
-#[derive(Debug, Deserialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct CreateVerifierRequest {
- pub name: String,
- pub verifier_type: String,
- pub command: Option<String>,
- pub working_directory: Option<String>,
- pub timeout_seconds: Option<i32>,
- pub environment: Option<serde_json::Value>,
- pub weight: Option<f64>,
- pub required: Option<bool>,
-}
-
-/// Request to update a verifier
-#[derive(Debug, Deserialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct UpdateVerifierRequest {
- pub name: Option<String>,
- pub command: Option<String>,
- pub working_directory: Option<String>,
- pub timeout_seconds: Option<i32>,
- pub weight: Option<f64>,
- pub required: Option<bool>,
- pub enabled: Option<bool>,
-}
-
-/// Approval action request
-#[derive(Debug, Deserialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct ApprovalActionRequest {
- pub response: Option<String>,
-}
-
-/// Request to update directive requirements
-#[derive(Debug, Deserialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct UpdateRequirementsRequest {
- pub requirements: Vec<DirectiveRequirement>,
-}
-
-/// Request to update directive acceptance criteria
-#[derive(Debug, Deserialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct UpdateCriteriaRequest {
- pub acceptance_criteria: Vec<DirectiveAcceptanceCriterion>,
-}
-
-/// Request to trigger step rework
-#[derive(Debug, Deserialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct ReworkStepRequest {
- pub instructions: Option<String>,
-}
-
-/// Directive requirement (shared type used in directive specification)
-#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct DirectiveRequirement {
- pub id: String,
- pub title: String,
- pub description: String,
- pub priority: String,
- pub category: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub parent_id: Option<String>,
-}
-
-/// Directive acceptance criterion
-#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct DirectiveAcceptanceCriterion {
- pub id: String,
- #[serde(default)]
- pub requirement_ids: Vec<String>,
- pub description: String,
- #[serde(default = "default_true")]
- pub testable: bool,
- pub verification_method: Option<String>,
-}
-
-fn default_true() -> bool {
- true
-}
-
-// Old chain types (Chain, ChainContract, ChainContractDefinition, ChainDirective,
-// ContractEvaluation, ChainEvent, ChainRepository, etc.) have been replaced by
-// the directive system above: Directive, DirectiveChain, ChainStep,
-// DirectiveEvaluation, DirectiveEvent, DirectiveVerifier, DirectiveApproval.
-
-// Legacy types kept temporarily for chain runner/parser compatibility during migration.
-// These will be removed once the chain daemon module is replaced.
-
-/// Request payload for creating a new chain (legacy - used by chain runner)
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct CreateChainRequest {
- pub name: String,
- pub description: Option<String>,
- pub repository_url: Option<String>,
- pub repositories: Option<Vec<AddChainRepositoryRequest>>,
- pub loop_enabled: Option<bool>,
- pub loop_max_iterations: Option<i32>,
- pub loop_progress_check: Option<String>,
- pub contracts: Option<Vec<CreateChainContractRequest>>,
-}
-
-/// Request to add a repository to a chain (legacy - used by chain runner)
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct AddChainRepositoryRequest {
- pub name: String,
- pub repository_url: Option<String>,
- pub local_path: Option<String>,
- #[serde(default = "default_source_type")]
- pub source_type: String,
- #[serde(default)]
- pub is_primary: bool,
-}
-
-fn default_source_type() -> String {
- "remote".to_string()
-}
-
-/// Request to create a contract within a chain (legacy - used by chain runner)
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct CreateChainContractRequest {
- pub name: String,
- pub description: Option<String>,
- #[serde(default)]
- pub contract_type: Option<String>,
- pub initial_phase: Option<String>,
- pub phases: Option<Vec<String>>,
- pub depends_on: Option<Vec<String>>,
- pub tasks: Option<Vec<CreateChainTaskRequest>>,
- pub deliverables: Option<Vec<CreateChainDeliverableRequest>>,
- pub editor_x: Option<f64>,
- pub editor_y: Option<f64>,
-}
-
-/// Task definition within a chain contract (legacy - used by chain runner)
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct CreateChainTaskRequest {
- pub name: String,
- pub plan: String,
-}
-
-/// Deliverable definition within a chain contract (legacy - used by chain runner)
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct CreateChainDeliverableRequest {
- pub id: String,
- pub name: String,
- pub priority: Option<String>,
-}
-
-// =============================================================================
// Unit Tests
// =============================================================================
diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs
index cd806f0..863d927 100644
--- a/makima/src/db/repository.rs
+++ b/makima/src/db/repository.rs
@@ -6,7 +6,6 @@ use sqlx::PgPool;
use uuid::Uuid;
use super::models::{
- // Core types
CheckpointPatch, CheckpointPatchInfo, Contract, ContractChatConversation,
ContractChatMessageRecord, ContractEvent, ContractRepository, ContractSummary,
ContractTypeTemplateRecord, ConversationMessage, ConversationSnapshot,
@@ -17,11 +16,6 @@ use super::models::{
PhaseDefinition, SupervisorHeartbeatRecord, SupervisorState, Task, TaskCheckpoint,
TaskEvent, TaskSummary, UpdateContractRequest, UpdateFileRequest, UpdateTaskRequest,
UpdateTemplateRequest,
- // Directive types
- AddStepRequest, ChainStep, CreateDirectiveRequest, Directive, DirectiveApproval,
- DirectiveChain, DirectiveChainGraphEdge, DirectiveChainGraphNode, DirectiveChainGraphResponse,
- DirectiveEvaluation, DirectiveEvent, DirectiveSummary, DirectiveVerifier,
- DirectiveWithProgress, UpdateDirectiveRequest, UpdateStepRequest,
};
/// Repository error types.
@@ -4905,1169 +4899,6 @@ pub async fn sync_supervisor_state(
}
// =============================================================================
-// Directive Operations (top-level orchestration entity)
-// =============================================================================
-// TODO: Implement directive CRUD functions
-// - create_directive_for_owner
-// - get_directive_for_owner
-// - list_directives_for_owner
-// - update_directive_for_owner
-// - archive_directive_for_owner
-// - update_directive_status
-
-// =============================================================================
-// Directive Chain Operations (generated execution plans)
-// =============================================================================
-// TODO: Implement chain CRUD functions
-// - create_directive_chain
-// - get_current_chain
-// - supersede_chain
-
-// =============================================================================
-// Chain Step Operations (nodes in the DAG)
-// =============================================================================
-// TODO: Implement step CRUD functions
-// - create_chain_step
-// - update_chain_step
-// - delete_chain_step
-// - find_ready_steps
-// - update_step_status
-// - update_step_contract
-// - update_step_confidence
-// - increment_step_rework_count
-
-// =============================================================================
-// Directive Evaluation Operations
-// =============================================================================
-// TODO: Implement evaluation functions
-// - create_directive_evaluation
-// - list_step_evaluations
-// - list_directive_evaluations
-
-// =============================================================================
-// Directive Event Operations (audit stream)
-// =============================================================================
-// TODO: Implement event functions
-// - emit_directive_event
-// - list_directive_events
-
-// =============================================================================
-// Directive Verifier Operations
-// =============================================================================
-// TODO: Implement verifier CRUD functions
-// - create_directive_verifier
-// - list_directive_verifiers
-// - update_directive_verifier
-
-// =============================================================================
-// Directive Approval Operations (human-in-the-loop)
-// =============================================================================
-// TODO: Implement approval functions
-// - create_approval_request
-// - resolve_approval
-// - list_pending_approvals
-
-// NOTE: Old chain functions removed. See git history for reference.
-// Old functions included: create_chain_for_owner, get_chain_for_owner,
-// list_chains_for_owner, update_chain_for_owner, delete_chain_for_owner,
-// add_contract_to_chain, remove_contract_from_chain, list_chain_contracts,
-// get_chain_with_contracts, list_chain_repositories, add_chain_repository,
-// delete_chain_repository, set_chain_repository_primary, get_chain_graph,
-// record_chain_event, list_chain_events, increment_chain_loop, complete_chain,
-// get_ready_chain_contracts, is_chain_complete, get_chain_editor_data,
-// create_chain_contract_definition, list_chain_contract_definitions,
-// update_chain_contract_definition, delete_chain_contract_definition,
-// get_chain_definition_graph, update_chain_status, progress_chain,
-// create_chain_directive, get_chain_directive, update_chain_directive,
-// delete_chain_directive, create_contract_evaluation, get_contract_evaluation,
-// list_chain_evaluations, update_chain_contract_evaluation_status,
-// mark_chain_contract_original_completion, get_chain_contract_by_contract_id,
-// init_chain_for_owner.
-
-// =============================================================================
-// Directive Operations
-// =============================================================================
-
-/// Create a new directive for an owner.
-pub async fn create_directive_for_owner(
- pool: &PgPool,
- owner_id: Uuid,
- req: CreateDirectiveRequest,
-) -> Result<Directive, sqlx::Error> {
- let title = req.title.unwrap_or_else(|| truncate_string(&req.goal, 100));
- let autonomy_level = req.autonomy_level.unwrap_or_else(|| "guardrails".to_string());
- let green_threshold = req.confidence_threshold_green.unwrap_or(0.85);
- let yellow_threshold = req.confidence_threshold_yellow.unwrap_or(0.60);
- let requirements = req.requirements.unwrap_or(serde_json::json!([]));
- let acceptance_criteria = req.acceptance_criteria.unwrap_or(serde_json::json!([]));
-
- sqlx::query_as::<_, Directive>(
- r#"
- INSERT INTO directives (
- owner_id, title, goal, requirements, acceptance_criteria,
- autonomy_level, confidence_threshold_green, confidence_threshold_yellow,
- repository_url, local_path, base_branch,
- max_total_cost_usd, max_wall_time_minutes
- )
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
- RETURNING *
- "#,
- )
- .bind(owner_id)
- .bind(&title)
- .bind(&req.goal)
- .bind(&requirements)
- .bind(&acceptance_criteria)
- .bind(&autonomy_level)
- .bind(green_threshold)
- .bind(yellow_threshold)
- .bind(&req.repository_url)
- .bind(&req.local_path)
- .bind(&req.base_branch)
- .bind(req.max_total_cost_usd)
- .bind(req.max_wall_time_minutes)
- .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
-}
-
-/// Get a directive by ID (no owner check - 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
-}
-
-/// List directives for an owner.
-pub async fn list_directives_for_owner(
- pool: &PgPool,
- owner_id: Uuid,
- status_filter: Option<&str>,
-) -> Result<Vec<DirectiveSummary>, sqlx::Error> {
- let query = if let Some(status) = status_filter {
- sqlx::query_as::<_, DirectiveSummary>(
- r#"
- SELECT
- d.id, d.title, d.goal, d.status, d.autonomy_level,
- dc.current_confidence,
- COALESCE(dc.completed_steps, 0) as completed_steps,
- COALESCE(dc.total_steps, 0) as total_steps,
- d.chain_generation_count, d.started_at, d.created_at
- FROM directives d
- LEFT JOIN directive_chains dc ON dc.id = d.current_chain_id
- WHERE d.owner_id = $1 AND d.status = $2
- ORDER BY d.created_at DESC
- "#,
- )
- .bind(owner_id)
- .bind(status)
- } else {
- sqlx::query_as::<_, DirectiveSummary>(
- r#"
- SELECT
- d.id, d.title, d.goal, d.status, d.autonomy_level,
- dc.current_confidence,
- COALESCE(dc.completed_steps, 0) as completed_steps,
- COALESCE(dc.total_steps, 0) as total_steps,
- d.chain_generation_count, d.started_at, d.created_at
- FROM directives d
- LEFT JOIN directive_chains dc ON dc.id = d.current_chain_id
- WHERE d.owner_id = $1
- ORDER BY d.created_at DESC
- "#,
- )
- .bind(owner_id)
- };
- query.fetch_all(pool).await
-}
-
-/// Update a directive with optimistic locking.
-pub async fn update_directive_for_owner(
- pool: &PgPool,
- id: Uuid,
- owner_id: Uuid,
- req: UpdateDirectiveRequest,
-) -> Result<Directive, RepositoryError> {
- // First get current version
- let current = sqlx::query_scalar::<_, i32>(
- "SELECT version FROM directives WHERE id = $1 AND owner_id = $2"
- )
- .bind(id)
- .bind(owner_id)
- .fetch_optional(pool)
- .await?
- .ok_or_else(|| RepositoryError::Database(sqlx::Error::RowNotFound))?;
-
- if current != req.version {
- return Err(RepositoryError::VersionConflict {
- expected: req.version,
- actual: current,
- });
- }
-
- let directive = sqlx::query_as::<_, Directive>(
- r#"
- UPDATE directives SET
- title = COALESCE($3, title),
- goal = COALESCE($4, goal),
- requirements = COALESCE($5, requirements),
- acceptance_criteria = COALESCE($6, acceptance_criteria),
- constraints = COALESCE($7, constraints),
- external_dependencies = COALESCE($8, external_dependencies),
- autonomy_level = COALESCE($9, autonomy_level),
- confidence_threshold_green = COALESCE($10, confidence_threshold_green),
- confidence_threshold_yellow = COALESCE($11, confidence_threshold_yellow),
- max_total_cost_usd = COALESCE($12, max_total_cost_usd),
- max_wall_time_minutes = COALESCE($13, max_wall_time_minutes),
- max_rework_cycles = COALESCE($14, max_rework_cycles),
- max_chain_regenerations = COALESCE($15, max_chain_regenerations),
- version = version + 1,
- updated_at = NOW()
- WHERE id = $1 AND owner_id = $2 AND version = $16
- RETURNING *
- "#,
- )
- .bind(id)
- .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.version)
- .fetch_one(pool)
- .await?;
-
- Ok(directive)
-}
-
-/// Update directive status.
-pub async fn update_directive_status(
- pool: &PgPool,
- id: Uuid,
- status: &str,
-) -> Result<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', 'archived') THEN NOW() ELSE completed_at END,
- updated_at = NOW()
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(id)
- .bind(status)
- .fetch_one(pool)
- .await
-}
-
-/// Set the orchestrator contract ID for a directive.
-pub async fn set_directive_orchestrator_contract(
- pool: &PgPool,
- directive_id: Uuid,
- contract_id: Uuid,
-) -> Result<(), sqlx::Error> {
- sqlx::query(
- r#"
- UPDATE directives SET orchestrator_contract_id = $2, updated_at = NOW()
- WHERE id = $1
- "#,
- )
- .bind(directive_id)
- .bind(contract_id)
- .execute(pool)
- .await?;
- Ok(())
-}
-
-/// Find a directive by its orchestrator contract ID.
-pub async fn get_directive_by_orchestrator_contract_id(
- 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
-}
-
-/// Archive a directive (soft delete).
-pub async fn archive_directive_for_owner(
- pool: &PgPool,
- id: Uuid,
- owner_id: Uuid,
-) -> Result<bool, sqlx::Error> {
- let result = sqlx::query(
- r#"
- UPDATE directives SET status = 'archived', updated_at = NOW()
- WHERE id = $1 AND owner_id = $2
- "#,
- )
- .bind(id)
- .bind(owner_id)
- .execute(pool)
- .await?;
- Ok(result.rows_affected() > 0)
-}
-
-/// Get directive with full progress info.
-pub async fn get_directive_with_progress(
- pool: &PgPool,
- id: Uuid,
- owner_id: Uuid,
-) -> Result<Option<DirectiveWithProgress>, sqlx::Error> {
- let directive = match get_directive_for_owner(pool, id, owner_id).await? {
- Some(d) => d,
- None => return Ok(None),
- };
-
- let chain = if let Some(chain_id) = directive.current_chain_id {
- get_directive_chain(pool, chain_id).await?
- } else {
- None
- };
-
- let steps = if let Some(ref c) = chain {
- list_chain_steps(pool, c.id).await?
- } else {
- vec![]
- };
-
- let recent_events = list_directive_events(pool, id, Some(20)).await?;
- let pending_approvals = list_pending_approvals(pool, id).await?;
-
- Ok(Some(DirectiveWithProgress {
- directive,
- chain,
- steps,
- recent_events,
- pending_approvals,
- }))
-}
-
-// =============================================================================
-// Directive Chain Operations
-// =============================================================================
-
-/// Create a new chain generation for a directive.
-pub async fn create_directive_chain(
- pool: &PgPool,
- directive_id: Uuid,
- name: &str,
- description: Option<&str>,
- rationale: Option<&str>,
- planning_model: Option<&str>,
-) -> Result<DirectiveChain, sqlx::Error> {
- // Get next generation number
- let generation = sqlx::query_scalar::<_, i32>(
- "SELECT COALESCE(MAX(generation), 0) + 1 FROM directive_chains WHERE directive_id = $1"
- )
- .bind(directive_id)
- .fetch_one(pool)
- .await?;
-
- let chain = sqlx::query_as::<_, DirectiveChain>(
- r#"
- INSERT INTO directive_chains (directive_id, generation, name, description, rationale, planning_model)
- VALUES ($1, $2, $3, $4, $5, $6)
- RETURNING *
- "#,
- )
- .bind(directive_id)
- .bind(generation)
- .bind(name)
- .bind(description)
- .bind(rationale)
- .bind(planning_model)
- .fetch_one(pool)
- .await?;
-
- // Update directive to point to new chain and increment generation count
- sqlx::query(
- r#"
- UPDATE directives SET
- current_chain_id = $2,
- chain_generation_count = chain_generation_count + 1,
- updated_at = NOW()
- WHERE id = $1
- "#,
- )
- .bind(directive_id)
- .bind(chain.id)
- .execute(pool)
- .await?;
-
- Ok(chain)
-}
-
-/// Get a directive chain by ID.
-pub async fn get_directive_chain(pool: &PgPool, id: Uuid) -> Result<Option<DirectiveChain>, sqlx::Error> {
- sqlx::query_as::<_, DirectiveChain>(
- "SELECT * FROM directive_chains WHERE id = $1"
- )
- .bind(id)
- .fetch_optional(pool)
- .await
-}
-
-/// Get the current chain for a directive.
-pub async fn get_current_chain(pool: &PgPool, directive_id: Uuid) -> Result<Option<DirectiveChain>, sqlx::Error> {
- sqlx::query_as::<_, DirectiveChain>(
- r#"
- SELECT dc.* FROM directive_chains dc
- JOIN directives d ON d.current_chain_id = dc.id
- WHERE d.id = $1
- "#,
- )
- .bind(directive_id)
- .fetch_optional(pool)
- .await
-}
-
-/// Update chain status.
-pub async fn update_chain_status(
- pool: &PgPool,
- chain_id: Uuid,
- status: &str,
-) -> Result<DirectiveChain, sqlx::Error> {
- sqlx::query_as::<_, DirectiveChain>(
- r#"
- UPDATE directive_chains 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', 'superseded') THEN NOW() ELSE completed_at END,
- updated_at = NOW()
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(chain_id)
- .bind(status)
- .fetch_one(pool)
- .await
-}
-
-/// Supersede a chain (mark as superseded and update directive).
-pub async fn supersede_chain(pool: &PgPool, chain_id: Uuid) -> Result<(), sqlx::Error> {
- sqlx::query(
- r#"
- UPDATE directive_chains SET status = 'superseded', completed_at = NOW(), updated_at = NOW()
- WHERE id = $1
- "#,
- )
- .bind(chain_id)
- .execute(pool)
- .await?;
- Ok(())
-}
-
-// =============================================================================
-// Chain Step Operations
-// =============================================================================
-
-/// Create a new step in a chain.
-pub async fn create_chain_step(
- pool: &PgPool,
- chain_id: Uuid,
- req: AddStepRequest,
-) -> Result<ChainStep, sqlx::Error> {
- let step_type = req.step_type.unwrap_or_else(|| "execute".to_string());
- let contract_type = req.contract_type.unwrap_or_else(|| "simple".to_string());
- let phases = req.phases.unwrap_or_default();
- let depends_on = req.depends_on.unwrap_or_default();
- let requirement_ids = req.requirement_ids.unwrap_or_default();
- let acceptance_criteria_ids = req.acceptance_criteria_ids.unwrap_or_default();
- let verifier_config = req.verifier_config.unwrap_or(serde_json::json!({}));
-
- // Get next order index
- let order_index = sqlx::query_scalar::<_, i32>(
- "SELECT COALESCE(MAX(order_index), 0) + 1 FROM chain_steps WHERE chain_id = $1"
- )
- .bind(chain_id)
- .fetch_one(pool)
- .await?;
-
- let step = sqlx::query_as::<_, ChainStep>(
- r#"
- INSERT INTO chain_steps (
- chain_id, name, description, step_type, contract_type,
- initial_phase, task_plan, phases, depends_on, parallel_group,
- requirement_ids, acceptance_criteria_ids, verifier_config,
- editor_x, editor_y, order_index
- )
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
- RETURNING *
- "#,
- )
- .bind(chain_id)
- .bind(&req.name)
- .bind(&req.description)
- .bind(&step_type)
- .bind(&contract_type)
- .bind(&req.initial_phase)
- .bind(&req.task_plan)
- .bind(&phases)
- .bind(&depends_on)
- .bind(&req.parallel_group)
- .bind(&requirement_ids)
- .bind(&acceptance_criteria_ids)
- .bind(&verifier_config)
- .bind(req.editor_x.unwrap_or(0.0))
- .bind(req.editor_y.unwrap_or(0.0))
- .bind(order_index)
- .fetch_one(pool)
- .await?;
-
- // Update chain total_steps count
- sqlx::query(
- "UPDATE directive_chains SET total_steps = total_steps + 1, updated_at = NOW() WHERE id = $1"
- )
- .bind(chain_id)
- .execute(pool)
- .await?;
-
- Ok(step)
-}
-
-/// Get a chain step by ID.
-pub async fn get_chain_step(pool: &PgPool, id: Uuid) -> Result<Option<ChainStep>, sqlx::Error> {
- sqlx::query_as::<_, ChainStep>(
- "SELECT * FROM chain_steps WHERE id = $1"
- )
- .bind(id)
- .fetch_optional(pool)
- .await
-}
-
-/// List all steps in a chain.
-pub async fn list_chain_steps(pool: &PgPool, chain_id: Uuid) -> Result<Vec<ChainStep>, sqlx::Error> {
- sqlx::query_as::<_, ChainStep>(
- "SELECT * FROM chain_steps WHERE chain_id = $1 ORDER BY order_index"
- )
- .bind(chain_id)
- .fetch_all(pool)
- .await
-}
-
-/// Update a chain step.
-pub async fn update_chain_step(
- pool: &PgPool,
- step_id: Uuid,
- req: UpdateStepRequest,
-) -> Result<ChainStep, sqlx::Error> {
- sqlx::query_as::<_, ChainStep>(
- r#"
- UPDATE chain_steps SET
- name = COALESCE($2, name),
- description = COALESCE($3, description),
- task_plan = COALESCE($4, task_plan),
- depends_on = COALESCE($5, depends_on),
- requirement_ids = COALESCE($6, requirement_ids),
- acceptance_criteria_ids = COALESCE($7, acceptance_criteria_ids),
- verifier_config = COALESCE($8, verifier_config),
- editor_x = COALESCE($9, editor_x),
- editor_y = COALESCE($10, editor_y)
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(step_id)
- .bind(&req.name)
- .bind(&req.description)
- .bind(&req.task_plan)
- .bind(&req.depends_on)
- .bind(&req.requirement_ids)
- .bind(&req.acceptance_criteria_ids)
- .bind(&req.verifier_config)
- .bind(req.editor_x)
- .bind(req.editor_y)
- .fetch_one(pool)
- .await
-}
-
-/// Delete a chain step.
-pub async fn delete_chain_step(pool: &PgPool, step_id: Uuid) -> Result<bool, sqlx::Error> {
- // Get chain_id first for updating count
- let chain_id = sqlx::query_scalar::<_, Uuid>(
- "SELECT chain_id FROM chain_steps WHERE id = $1"
- )
- .bind(step_id)
- .fetch_optional(pool)
- .await?;
-
- let result = sqlx::query("DELETE FROM chain_steps WHERE id = $1")
- .bind(step_id)
- .execute(pool)
- .await?;
-
- // Update chain total_steps count
- if let Some(cid) = chain_id {
- sqlx::query(
- "UPDATE directive_chains SET total_steps = total_steps - 1, updated_at = NOW() WHERE id = $1"
- )
- .bind(cid)
- .execute(pool)
- .await?;
- }
-
- Ok(result.rows_affected() > 0)
-}
-
-/// Find steps that are ready to execute (all dependencies met, status=pending).
-pub async fn find_ready_steps(pool: &PgPool, chain_id: Uuid) -> Result<Vec<ChainStep>, sqlx::Error> {
- sqlx::query_as::<_, ChainStep>(
- r#"
- SELECT s.* FROM chain_steps s
- WHERE s.chain_id = $1
- AND s.status = 'pending'
- AND NOT EXISTS (
- SELECT 1 FROM chain_steps dep
- WHERE dep.id = ANY(s.depends_on)
- AND dep.status NOT IN ('passed', 'skipped')
- )
- ORDER BY s.order_index
- "#,
- )
- .bind(chain_id)
- .fetch_all(pool)
- .await
-}
-
-/// Update step status.
-pub async fn update_step_status(
- pool: &PgPool,
- step_id: Uuid,
- status: &str,
-) -> Result<ChainStep, sqlx::Error> {
- let step = 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', 'skipped') THEN NOW() ELSE completed_at END
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(step_id)
- .bind(status)
- .fetch_one(pool)
- .await?;
-
- // Update chain completed_steps and failed_steps counts
- if status == "passed" || status == "skipped" {
- sqlx::query(
- "UPDATE directive_chains SET completed_steps = completed_steps + 1, updated_at = NOW() WHERE id = $1"
- )
- .bind(step.chain_id)
- .execute(pool)
- .await?;
- } else if status == "failed" {
- sqlx::query(
- "UPDATE directive_chains SET failed_steps = failed_steps + 1, updated_at = NOW() WHERE id = $1"
- )
- .bind(step.chain_id)
- .execute(pool)
- .await?;
- }
-
- Ok(step)
-}
-
-/// Link a step to a contract.
-pub async fn update_step_contract(
- pool: &PgPool,
- step_id: Uuid,
- contract_id: Uuid,
- supervisor_task_id: Option<Uuid>,
-) -> Result<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_one(pool)
- .await
-}
-
-/// Update step confidence score and level.
-pub async fn update_step_confidence(
- pool: &PgPool,
- step_id: Uuid,
- score: f64,
- level: &str,
- evaluation_id: Uuid,
-) -> Result<ChainStep, sqlx::Error> {
- sqlx::query_as::<_, ChainStep>(
- r#"
- UPDATE chain_steps SET
- confidence_score = $2,
- confidence_level = $3,
- last_evaluation_id = $4,
- evaluation_count = evaluation_count + 1
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(step_id)
- .bind(score)
- .bind(level)
- .bind(evaluation_id)
- .fetch_one(pool)
- .await
-}
-
-/// Increment step rework count.
-pub async fn increment_step_rework_count(pool: &PgPool, step_id: Uuid) -> Result<ChainStep, sqlx::Error> {
- sqlx::query_as::<_, ChainStep>(
- r#"
- UPDATE chain_steps SET rework_count = rework_count + 1, status = 'rework'
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(step_id)
- .fetch_one(pool)
- .await
-}
-
-/// Get chain graph for visualization.
-pub async fn get_chain_graph(
- pool: &PgPool,
- chain_id: Uuid,
-) -> Result<DirectiveChainGraphResponse, sqlx::Error> {
- let chain = get_directive_chain(pool, chain_id).await?
- .ok_or_else(|| sqlx::Error::RowNotFound)?;
-
- let steps = list_chain_steps(pool, chain_id).await?;
-
- let nodes: Vec<DirectiveChainGraphNode> = steps.iter().map(|s| {
- DirectiveChainGraphNode {
- id: s.id,
- name: s.name.clone(),
- step_type: s.step_type.clone(),
- status: s.status.clone(),
- confidence_score: s.confidence_score,
- confidence_level: s.confidence_level.clone(),
- contract_id: s.contract_id,
- editor_x: s.editor_x,
- editor_y: s.editor_y,
- }
- }).collect();
-
- let mut edges = Vec::new();
- for step in &steps {
- for dep_id in &step.depends_on {
- edges.push(DirectiveChainGraphEdge {
- source: *dep_id,
- target: step.id,
- });
- }
- }
-
- Ok(DirectiveChainGraphResponse {
- chain_id,
- directive_id: chain.directive_id,
- nodes,
- edges,
- })
-}
-
-// =============================================================================
-// Directive Evaluation Operations
-// =============================================================================
-
-/// Create a directive evaluation.
-pub async fn create_directive_evaluation(
- pool: &PgPool,
- directive_id: Uuid,
- chain_id: Option<Uuid>,
- step_id: Option<Uuid>,
- contract_id: Option<Uuid>,
- evaluation_type: &str,
- evaluator: Option<&str>,
- passed: bool,
- overall_score: Option<f64>,
- confidence_level: Option<&str>,
- programmatic_results: serde_json::Value,
- llm_results: serde_json::Value,
- criteria_results: serde_json::Value,
- summary_feedback: &str,
- rework_instructions: Option<&str>,
-) -> Result<DirectiveEvaluation, sqlx::Error> {
- // Get next evaluation number for this step/directive
- let evaluation_number = if let Some(sid) = step_id {
- sqlx::query_scalar::<_, i32>(
- "SELECT COALESCE(MAX(evaluation_number), 0) + 1 FROM directive_evaluations WHERE step_id = $1"
- )
- .bind(sid)
- .fetch_one(pool)
- .await?
- } else {
- sqlx::query_scalar::<_, i32>(
- "SELECT COALESCE(MAX(evaluation_number), 0) + 1 FROM directive_evaluations WHERE directive_id = $1 AND step_id IS NULL"
- )
- .bind(directive_id)
- .fetch_one(pool)
- .await?
- };
-
- 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,
- programmatic_results, llm_results, criteria_results,
- summary_feedback, rework_instructions,
- completed_at
- )
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, NOW())
- RETURNING *
- "#,
- )
- .bind(directive_id)
- .bind(chain_id)
- .bind(step_id)
- .bind(contract_id)
- .bind(evaluation_type)
- .bind(evaluation_number)
- .bind(evaluator)
- .bind(passed)
- .bind(overall_score)
- .bind(confidence_level)
- .bind(&programmatic_results)
- .bind(&llm_results)
- .bind(&criteria_results)
- .bind(summary_feedback)
- .bind(rework_instructions)
- .fetch_one(pool)
- .await
-}
-
-/// List evaluations for a step.
-pub async fn list_step_evaluations(
- pool: &PgPool,
- step_id: Uuid,
-) -> Result<Vec<DirectiveEvaluation>, sqlx::Error> {
- sqlx::query_as::<_, DirectiveEvaluation>(
- "SELECT * FROM directive_evaluations WHERE step_id = $1 ORDER BY evaluation_number DESC"
- )
- .bind(step_id)
- .fetch_all(pool)
- .await
-}
-
-/// List evaluations for a directive.
-pub async fn list_directive_evaluations(
- pool: &PgPool,
- directive_id: Uuid,
- limit: Option<i64>,
-) -> Result<Vec<DirectiveEvaluation>, sqlx::Error> {
- let limit = limit.unwrap_or(100);
- sqlx::query_as::<_, DirectiveEvaluation>(
- "SELECT * FROM directive_evaluations WHERE directive_id = $1 ORDER BY created_at DESC LIMIT $2"
- )
- .bind(directive_id)
- .bind(limit)
- .fetch_all(pool)
- .await
-}
-
-// =============================================================================
-// Directive Event Operations
-// =============================================================================
-
-/// Emit a directive event.
-pub async fn emit_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
-}
-
-/// List directive events.
-pub async fn list_directive_events(
- pool: &PgPool,
- directive_id: Uuid,
- limit: Option<i64>,
-) -> Result<Vec<DirectiveEvent>, sqlx::Error> {
- let limit = limit.unwrap_or(100);
- sqlx::query_as::<_, DirectiveEvent>(
- "SELECT * FROM directive_events WHERE directive_id = $1 ORDER BY created_at DESC LIMIT $2"
- )
- .bind(directive_id)
- .bind(limit)
- .fetch_all(pool)
- .await
-}
-
-// =============================================================================
-// Directive Verifier Operations
-// =============================================================================
-
-/// Create a directive verifier.
-pub async fn create_directive_verifier(
- pool: &PgPool,
- directive_id: Uuid,
- name: &str,
- verifier_type: &str,
- command: Option<&str>,
- working_directory: Option<&str>,
- auto_detect: bool,
- detect_files: Vec<String>,
- weight: f64,
- required: bool,
-) -> Result<DirectiveVerifier, sqlx::Error> {
- sqlx::query_as::<_, DirectiveVerifier>(
- r#"
- INSERT INTO directive_verifiers (
- directive_id, name, verifier_type, command, working_directory,
- auto_detect, detect_files, weight, required
- )
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
- RETURNING *
- "#,
- )
- .bind(directive_id)
- .bind(name)
- .bind(verifier_type)
- .bind(command)
- .bind(working_directory)
- .bind(auto_detect)
- .bind(&detect_files)
- .bind(weight)
- .bind(required)
- .fetch_one(pool)
- .await
-}
-
-/// List verifiers for a directive.
-pub async fn list_directive_verifiers(
- pool: &PgPool,
- directive_id: Uuid,
-) -> Result<Vec<DirectiveVerifier>, sqlx::Error> {
- sqlx::query_as::<_, DirectiveVerifier>(
- "SELECT * FROM directive_verifiers WHERE directive_id = $1 ORDER BY name"
- )
- .bind(directive_id)
- .fetch_all(pool)
- .await
-}
-
-/// Update a directive verifier.
-pub async fn update_directive_verifier(
- pool: &PgPool,
- verifier_id: Uuid,
- enabled: Option<bool>,
- command: Option<&str>,
- weight: Option<f64>,
- required: Option<bool>,
-) -> Result<DirectiveVerifier, sqlx::Error> {
- sqlx::query_as::<_, DirectiveVerifier>(
- r#"
- UPDATE directive_verifiers SET
- enabled = COALESCE($2, enabled),
- command = COALESCE($3, command),
- weight = COALESCE($4, weight),
- required = COALESCE($5, required),
- updated_at = NOW()
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(verifier_id)
- .bind(enabled)
- .bind(command)
- .bind(weight)
- .bind(required)
- .fetch_one(pool)
- .await
-}
-
-/// Update verifier last run result.
-pub async fn update_verifier_result(
- pool: &PgPool,
- verifier_id: Uuid,
- result: serde_json::Value,
-) -> Result<DirectiveVerifier, sqlx::Error> {
- sqlx::query_as::<_, DirectiveVerifier>(
- r#"
- UPDATE directive_verifiers SET last_run_at = NOW(), last_result = $2, updated_at = NOW()
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(verifier_id)
- .bind(result)
- .fetch_one(pool)
- .await
-}
-
-// =============================================================================
-// Directive Approval Operations
-// =============================================================================
-
-/// Create an approval request.
-pub async fn create_approval_request(
- pool: &PgPool,
- directive_id: Uuid,
- step_id: Option<Uuid>,
- approval_type: &str,
- description: &str,
- context: Option<serde_json::Value>,
- urgency: &str,
- expires_at: Option<chrono::DateTime<Utc>>,
-) -> Result<DirectiveApproval, sqlx::Error> {
- sqlx::query_as::<_, DirectiveApproval>(
- r#"
- INSERT INTO directive_approvals (
- directive_id, step_id, approval_type, description, context, urgency, expires_at
- )
- VALUES ($1, $2, $3, $4, $5, $6, $7)
- RETURNING *
- "#,
- )
- .bind(directive_id)
- .bind(step_id)
- .bind(approval_type)
- .bind(description)
- .bind(context)
- .bind(urgency)
- .bind(expires_at)
- .fetch_one(pool)
- .await
-}
-
-/// Resolve an approval request.
-pub async fn resolve_approval(
- pool: &PgPool,
- approval_id: Uuid,
- status: &str,
- response: Option<&str>,
- responded_by: Uuid,
-) -> Result<DirectiveApproval, sqlx::Error> {
- sqlx::query_as::<_, DirectiveApproval>(
- r#"
- UPDATE directive_approvals SET
- status = $2,
- response = $3,
- responded_by = $4,
- responded_at = NOW()
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(approval_id)
- .bind(status)
- .bind(response)
- .bind(responded_by)
- .fetch_one(pool)
- .await
-}
-
-/// List pending approvals for a directive.
-pub async fn list_pending_approvals(
- pool: &PgPool,
- directive_id: Uuid,
-) -> Result<Vec<DirectiveApproval>, sqlx::Error> {
- sqlx::query_as::<_, DirectiveApproval>(
- r#"
- SELECT * FROM directive_approvals
- WHERE directive_id = $1 AND status = 'pending'
- ORDER BY
- CASE urgency
- WHEN 'critical' THEN 1
- WHEN 'high' THEN 2
- WHEN 'normal' THEN 3
- ELSE 4
- END,
- created_at
- "#,
- )
- .bind(directive_id)
- .fetch_all(pool)
- .await
-}
-
-/// Get step by contract ID.
-pub async fn get_step_by_contract_id(
- pool: &PgPool,
- contract_id: Uuid,
-) -> Result<Option<ChainStep>, sqlx::Error> {
- sqlx::query_as::<_, ChainStep>(
- "SELECT * FROM chain_steps WHERE contract_id = $1"
- )
- .bind(contract_id)
- .fetch_optional(pool)
- .await
-}
-
-// =============================================================================
// Helper Functions
// =============================================================================
diff --git a/makima/src/lib.rs b/makima/src/lib.rs
index 3bc460b..8d3db58 100644
--- a/makima/src/lib.rs
+++ b/makima/src/lib.rs
@@ -3,6 +3,5 @@ pub mod daemon;
pub mod db;
pub mod listen;
pub mod llm;
-pub mod orchestration;
pub mod server;
pub mod tts;
diff --git a/makima/src/orchestration/engine.rs b/makima/src/orchestration/engine.rs
deleted file mode 100644
index 9f7c3b1..0000000
--- a/makima/src/orchestration/engine.rs
+++ /dev/null
@@ -1,1335 +0,0 @@
-//! Directive orchestration engine.
-//!
-//! Manages the lifecycle of directives:
-//! - Starts directives and generates initial chains
-//! - Monitors step execution and triggers evaluations
-//! - Handles rework, escalation, and chain regeneration
-//! - Enforces circuit breakers (cost, time, rework limits)
-
-use std::collections::HashMap;
-
-use sqlx::PgPool;
-use thiserror::Error;
-use tokio::sync::broadcast;
-use uuid::Uuid;
-
-use crate::db::models::{
- AddStepRequest, ChainStep, CreateContractRequest, CreateTaskRequest, Directive,
- DirectiveEvent, UpdateStepRequest,
-};
-use crate::db::repository::{self, RepositoryError};
-
-use super::planner::{ChainPlanner, GeneratedChain, PlannerError};
-use super::verifier::{
- auto_detect_verifiers, CompositeEvaluator, ConfidenceLevel, EvaluationResult,
- VerificationContext,
-};
-
-/// Error type for engine operations.
-#[derive(Error, Debug)]
-pub enum EngineError {
- #[error("Database error: {0}")]
- Database(#[from] sqlx::Error),
-
- #[error("Repository error: {0}")]
- Repository(#[from] RepositoryError),
-
- #[error("Planner error: {0}")]
- Planner(#[from] PlannerError),
-
- #[error("Directive not found: {0}")]
- DirectiveNotFound(Uuid),
-
- #[error("Chain not found for directive: {0}")]
- ChainNotFound(Uuid),
-
- #[error("Step not found: {0}")]
- StepNotFound(Uuid),
-
- #[error("Invalid state transition: {from} -> {to}")]
- InvalidStateTransition { from: String, to: String },
-
- #[error("Circuit breaker triggered: {0}")]
- CircuitBreaker(String),
-
- #[error("Directive is paused")]
- DirectivePaused,
-
- #[error("Contract creation failed: {0}")]
- ContractCreation(String),
-
- #[error("LLM error: {0}")]
- LlmError(String),
-}
-
-/// Event emitted by the engine for UI updates.
-#[derive(Debug, Clone)]
-pub enum EngineEvent {
- /// Directive status changed
- DirectiveStatusChanged {
- directive_id: Uuid,
- old_status: String,
- new_status: String,
- },
- /// Step status changed
- StepStatusChanged {
- directive_id: Uuid,
- step_id: Uuid,
- old_status: String,
- new_status: String,
- },
- /// Evaluation completed
- EvaluationCompleted {
- directive_id: Uuid,
- step_id: Uuid,
- passed: bool,
- confidence_level: ConfidenceLevel,
- },
- /// Approval required
- ApprovalRequired {
- directive_id: Uuid,
- approval_id: Uuid,
- approval_type: String,
- },
- /// Chain regenerated
- ChainRegenerated {
- directive_id: Uuid,
- old_chain_id: Uuid,
- new_chain_id: Uuid,
- },
-}
-
-/// Result from starting a directive, containing info needed for auto-start.
-pub struct PlanningStartResult {
- /// The planning task ID that needs to be started on a daemon
- pub task_id: Uuid,
- /// The owner ID for finding available daemons
- pub owner_id: Uuid,
- /// The planning task details needed for the SpawnTask command
- pub task_name: String,
- pub plan: String,
- pub contract_id: Uuid,
- pub repository_url: Option<String>,
- pub base_branch: Option<String>,
-}
-
-/// Main orchestration engine for directives.
-pub struct DirectiveEngine {
- pool: PgPool,
- planner: ChainPlanner,
- event_tx: Option<broadcast::Sender<EngineEvent>>,
-}
-
-impl DirectiveEngine {
- /// Create a new directive engine.
- pub fn new(pool: PgPool) -> Self {
- Self {
- planner: ChainPlanner::new(pool.clone()),
- pool,
- event_tx: None,
- }
- }
-
- /// Set the event broadcast channel for UI updates.
- pub fn with_event_channel(mut self, tx: broadcast::Sender<EngineEvent>) -> Self {
- self.event_tx = Some(tx);
- self
- }
-
- /// Emit an event if channel is configured.
- fn emit_event(&self, event: EngineEvent) {
- if let Some(tx) = &self.event_tx {
- let _ = tx.send(event);
- }
- }
-
- // ========================================================================
- // Directive Lifecycle
- // ========================================================================
-
- /// Start a directive: spawn a planning contract+task to generate the chain.
- /// Returns a `PlanningStartResult` so the caller can auto-start the task on a daemon.
- pub async fn start_directive(&self, directive_id: Uuid) -> Result<PlanningStartResult, EngineError> {
- let directive = repository::get_directive(&self.pool, directive_id)
- .await?
- .ok_or(EngineError::DirectiveNotFound(directive_id))?;
-
- // Validate current state
- if directive.status != "draft" && directive.status != "paused" {
- return Err(EngineError::InvalidStateTransition {
- from: directive.status,
- to: "planning".to_string(),
- });
- }
-
- // Update status to planning
- repository::update_directive_status(&self.pool, directive_id, "planning").await?;
- self.emit_directive_event(
- directive_id,
- "status_changed",
- "info",
- serde_json::json!({"old_status": directive.status, "new_status": "planning"}),
- "system",
- )
- .await?;
-
- // Create an empty chain for the planning task to populate
- let chain_name = format!(
- "{}-chain",
- directive.title.to_lowercase().replace(' ', "-")
- );
- let _db_chain = repository::create_directive_chain(
- &self.pool,
- directive_id,
- &chain_name,
- Some(&format!("Execution plan for: {}", directive.goal)),
- None, // rationale
- None, // planning_model
- )
- .await?;
-
- // Create a planning contract (type "execute", no phase guard)
- let contract = repository::create_contract_for_owner(
- &self.pool,
- directive.owner_id,
- CreateContractRequest {
- name: format!("{} - Planning", directive.title),
- description: Some(format!(
- "Planning contract for directive: {}",
- directive.goal
- )),
- contract_type: Some("execute".to_string()),
- template_id: None,
- initial_phase: Some("execute".to_string()),
- autonomous_loop: Some(true),
- phase_guard: Some(false),
- local_only: Some(false),
- auto_merge_local: None,
- },
- )
- .await
- .map_err(|e| {
- EngineError::ContractCreation(format!("Failed to create planning contract: {}", e))
- })?;
-
- // Build instructions for the planning task
- let plan = self.build_planning_task_instructions(&directive);
-
- // Create the planning task
- let task_name = format!("{} - Planning", directive.title);
- let task = repository::create_task_for_owner(
- &self.pool,
- directive.owner_id,
- CreateTaskRequest {
- contract_id: Some(contract.id),
- name: task_name.clone(),
- description: Some(format!(
- "Plan the execution chain for directive: {}",
- directive.goal
- )),
- plan: plan.clone(),
- 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: None,
- completion_action: Some("none".to_string()),
- 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| {
- EngineError::ContractCreation(format!("Failed to create planning task: {}", e))
- })?;
-
- // Link the supervisor task to the contract
- if let Err(e) = repository::update_contract_supervisor(
- &self.pool,
- contract.id,
- task.id,
- )
- .await
- {
- tracing::warn!(
- contract_id = %contract.id,
- task_id = %task.id,
- error = %e,
- "Failed to link supervisor task to planning contract"
- );
- }
-
- // Link the planning contract to the directive
- repository::set_directive_orchestrator_contract(
- &self.pool,
- directive_id,
- contract.id,
- )
- .await?;
-
- self.emit_directive_event(
- directive_id,
- "planning_started",
- "info",
- serde_json::json!({
- "contract_id": contract.id,
- "task_id": task.id,
- "message": "Planning task spawned, waiting for chain generation",
- }),
- "system",
- )
- .await?;
-
- Ok(PlanningStartResult {
- task_id: task.id,
- owner_id: directive.owner_id,
- task_name,
- plan,
- contract_id: contract.id,
- repository_url: directive.repository_url.clone(),
- base_branch: directive.base_branch.clone(),
- })
- }
-
- /// Pause a directive.
- pub async fn pause_directive(&self, directive_id: Uuid) -> Result<(), EngineError> {
- let directive = repository::get_directive(&self.pool, directive_id)
- .await?
- .ok_or(EngineError::DirectiveNotFound(directive_id))?;
-
- if directive.status != "active" {
- return Err(EngineError::InvalidStateTransition {
- from: directive.status,
- to: "paused".to_string(),
- });
- }
-
- repository::update_directive_status(&self.pool, directive_id, "paused").await?;
- self.emit_event(EngineEvent::DirectiveStatusChanged {
- directive_id,
- old_status: "active".to_string(),
- new_status: "paused".to_string(),
- });
-
- Ok(())
- }
-
- /// Resume a paused directive.
- pub async fn resume_directive(&self, directive_id: Uuid) -> Result<(), EngineError> {
- let directive = repository::get_directive(&self.pool, directive_id)
- .await?
- .ok_or(EngineError::DirectiveNotFound(directive_id))?;
-
- if directive.status != "paused" {
- return Err(EngineError::InvalidStateTransition {
- from: directive.status,
- to: "active".to_string(),
- });
- }
-
- repository::update_directive_status(&self.pool, directive_id, "active").await?;
- self.emit_event(EngineEvent::DirectiveStatusChanged {
- directive_id,
- old_status: "paused".to_string(),
- new_status: "active".to_string(),
- });
-
- // Continue execution
- self.advance_chain(directive_id).await?;
-
- Ok(())
- }
-
- /// Stop a directive (cannot be resumed).
- pub async fn stop_directive(&self, directive_id: Uuid) -> Result<(), EngineError> {
- let directive = repository::get_directive(&self.pool, directive_id)
- .await?
- .ok_or(EngineError::DirectiveNotFound(directive_id))?;
-
- if directive.status == "completed" || directive.status == "failed" {
- return Err(EngineError::InvalidStateTransition {
- from: directive.status,
- to: "failed".to_string(),
- });
- }
-
- repository::update_directive_status(&self.pool, directive_id, "failed").await?;
- self.emit_event(EngineEvent::DirectiveStatusChanged {
- directive_id,
- old_status: directive.status,
- new_status: "failed".to_string(),
- });
-
- Ok(())
- }
-
- // ========================================================================
- // Chain Management
- // ========================================================================
-
- /// Build a default chain as a fallback.
- fn build_default_chain(&self, directive: &Directive) -> GeneratedChain {
- GeneratedChain {
- name: format!(
- "{}-chain",
- directive.title.to_lowercase().replace(' ', "-")
- ),
- description: format!("Execution plan for: {}", directive.goal),
- steps: vec![
- super::planner::GeneratedStep {
- name: "research".to_string(),
- step_type: "research".to_string(),
- description: format!(
- "Research and understand the requirements for: {}",
- directive.goal
- ),
- depends_on: vec![],
- requirement_ids: vec![],
- contract_template: None,
- },
- super::planner::GeneratedStep {
- name: "implement".to_string(),
- step_type: "implement".to_string(),
- description: format!("Implement the solution for: {}", directive.goal),
- depends_on: vec!["research".to_string()],
- requirement_ids: vec![],
- contract_template: None,
- },
- super::planner::GeneratedStep {
- name: "test".to_string(),
- step_type: "test".to_string(),
- description: "Test and verify the implementation".to_string(),
- depends_on: vec!["implement".to_string()],
- requirement_ids: vec![],
- contract_template: None,
- },
- ],
- }
- }
-
- /// Create database steps from a generated chain.
- async fn create_steps_from_chain(
- &self,
- chain_id: &Uuid,
- chain: &GeneratedChain,
- ) -> Result<(), EngineError> {
- // First pass: create all steps and build name-to-id map
- let mut step_id_map: HashMap<String, Uuid> = HashMap::new();
-
- // Get editor positions
- let positions = self.planner.compute_editor_positions(chain);
-
- for step in &chain.steps {
- let (editor_x, editor_y) = positions
- .get(&step.name)
- .copied()
- .unwrap_or((100.0, 100.0));
-
- let task_plan = step
- .contract_template
- .as_ref()
- .and_then(|t| t.tasks.first())
- .map(|t| t.plan.clone())
- .or_else(|| Some(step.description.clone()));
-
- let request = AddStepRequest {
- name: step.name.clone(),
- description: Some(step.description.clone()),
- step_type: Some(step.step_type.clone()),
- contract_type: step.contract_template.as_ref().map(|t| t.contract_type.clone()),
- initial_phase: Some("plan".to_string()),
- task_plan,
- phases: step.contract_template.as_ref().map(|t| t.phases.clone()),
- depends_on: None, // Will update in second pass
- parallel_group: None,
- requirement_ids: Some(step.requirement_ids.clone()),
- acceptance_criteria_ids: None,
- verifier_config: None,
- editor_x: Some(editor_x),
- editor_y: Some(editor_y),
- };
-
- let db_step = repository::create_chain_step(&self.pool, *chain_id, request).await?;
- step_id_map.insert(step.name.clone(), db_step.id);
- }
-
- // Second pass: update dependencies
- for step in &chain.steps {
- if step.depends_on.is_empty() {
- continue;
- }
-
- let step_id = step_id_map.get(&step.name).unwrap();
- let dep_ids: Vec<Uuid> = step
- .depends_on
- .iter()
- .filter_map(|name| step_id_map.get(name))
- .copied()
- .collect();
-
- // Update step with proper dependencies
- let update = UpdateStepRequest {
- name: None,
- description: None,
- task_plan: None,
- depends_on: Some(dep_ids),
- requirement_ids: None,
- acceptance_criteria_ids: None,
- verifier_config: None,
- editor_x: None,
- editor_y: None,
- };
-
- repository::update_chain_step(&self.pool, *step_id, update).await?;
- }
-
- Ok(())
- }
-
- /// Regenerate chain while preserving completed steps.
- pub async fn regenerate_chain(
- &self,
- directive_id: Uuid,
- reason: &str,
- ) -> Result<Uuid, EngineError> {
- let directive = repository::get_directive(&self.pool, directive_id)
- .await?
- .ok_or(EngineError::DirectiveNotFound(directive_id))?;
-
- let current_chain = repository::get_current_chain(&self.pool, directive_id)
- .await?
- .ok_or(EngineError::ChainNotFound(directive_id))?;
-
- // Use default chain for regeneration
- // (planning contract handles initial generation; regeneration uses fallback)
- let new_chain = self.build_default_chain(&directive);
-
- // Supersede old chain
- repository::supersede_chain(&self.pool, current_chain.id).await?;
-
- // Create new chain
- let db_chain = repository::create_directive_chain(
- &self.pool,
- directive_id,
- &new_chain.name,
- Some(&new_chain.description),
- Some(reason), // rationale
- None, // planning_model
- )
- .await?;
-
- // Create steps
- self.create_steps_from_chain(&db_chain.id, &new_chain).await?;
-
- self.emit_event(EngineEvent::ChainRegenerated {
- directive_id,
- old_chain_id: current_chain.id,
- new_chain_id: db_chain.id,
- });
-
- // Continue execution
- self.advance_chain(directive_id).await?;
-
- Ok(db_chain.id)
- }
-
- // ========================================================================
- // Step Execution
- // ========================================================================
-
- /// Advance chain execution: find ready steps and start them.
- pub async fn advance_chain(&self, directive_id: Uuid) -> Result<(), EngineError> {
- let directive = repository::get_directive(&self.pool, directive_id)
- .await?
- .ok_or(EngineError::DirectiveNotFound(directive_id))?;
-
- // Check if directive is active
- if directive.status == "paused" {
- return Err(EngineError::DirectivePaused);
- }
- if directive.status != "active" {
- return Ok(()); // Not an error, just nothing to do
- }
-
- // Check circuit breakers
- self.check_circuit_breakers(&directive).await?;
-
- // Get current chain
- let chain = repository::get_current_chain(&self.pool, directive_id)
- .await?
- .ok_or(EngineError::ChainNotFound(directive_id))?;
-
- // Find ready steps (dependencies met, status=pending)
- let ready_steps = repository::find_ready_steps(&self.pool, chain.id).await?;
-
- // Start each ready step
- for step in ready_steps {
- self.start_step(&directive, &step).await?;
- }
-
- // Check if chain is complete
- let all_steps = repository::list_chain_steps(&self.pool, chain.id).await?;
- let all_passed = all_steps.iter().all(|s| s.status == "passed" || s.status == "skipped");
- let any_blocked = all_steps.iter().any(|s| s.status == "blocked" || s.status == "failed");
-
- if all_passed && !all_steps.is_empty() {
- // Complete the directive
- self.complete_directive(directive_id).await?;
- } else if any_blocked {
- // Check if we should regenerate or fail
- let failed_count = all_steps.iter().filter(|s| s.status == "failed").count();
- if failed_count > 3 {
- // Too many failures, fail the directive
- repository::update_directive_status(&self.pool, directive_id, "failed").await?;
- }
- }
-
- Ok(())
- }
-
- /// Start a step by creating its contract and supervisor task.
- async fn start_step(&self, directive: &Directive, step: &ChainStep) -> Result<(), EngineError> {
- // Update step status to ready
- repository::update_step_status(&self.pool, step.id, "ready").await?;
- self.emit_event(EngineEvent::StepStatusChanged {
- directive_id: directive.id,
- step_id: step.id,
- old_status: "pending".to_string(),
- new_status: "ready".to_string(),
- });
-
- // Get contract details from step template
- let (name, description, contract_type, initial_phase) =
- self.get_contract_details(directive, step);
-
- // Create contract for this step
- let contract = repository::create_contract_for_owner(
- &self.pool,
- directive.owner_id,
- CreateContractRequest {
- name: name.clone(),
- description: description.clone(),
- contract_type: Some(contract_type),
- template_id: None,
- initial_phase: Some(initial_phase),
- autonomous_loop: Some(directive.autonomy_level == "full_auto"),
- phase_guard: Some(true),
- local_only: Some(false),
- auto_merge_local: None,
- },
- )
- .await
- .map_err(|e| EngineError::ContractCreation(format!("Failed to create contract: {}", e)))?;
-
- // Build task plan from step description and task_plan
- let task_plan = step
- .task_plan
- .clone()
- .unwrap_or_else(|| {
- format!(
- "## Step: {}\n\n{}\n\n## Directive Goal\n{}",
- step.name,
- description.as_deref().unwrap_or("Complete this step."),
- directive.goal,
- )
- });
-
- // Create supervisor task linked to the contract
- let task = repository::create_task_for_owner(
- &self.pool,
- directive.owner_id,
- CreateTaskRequest {
- contract_id: Some(contract.id),
- name: name.clone(),
- description: 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: Some("pr".to_string()),
- target_repo_path: None,
- completion_action: Some("pr".to_string()),
- 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| EngineError::ContractCreation(format!("Failed to create task: {}", e)))?;
-
- // Link contract and task to step
- repository::update_step_contract(&self.pool, step.id, contract.id, Some(task.id)).await?;
-
- // Update step status to running
- repository::update_step_status(&self.pool, step.id, "running").await?;
- self.emit_event(EngineEvent::StepStatusChanged {
- directive_id: directive.id,
- step_id: step.id,
- old_status: "ready".to_string(),
- new_status: "running".to_string(),
- });
-
- self.emit_directive_event(
- directive.id,
- "step_started",
- "info",
- serde_json::json!({
- "step_id": step.id,
- "step_name": step.name,
- "contract_id": contract.id,
- "task_id": task.id,
- }),
- "system",
- )
- .await?;
-
- Ok(())
- }
-
- /// Build contract details from a step.
- /// Returns (name, description, contract_type, initial_phase)
- fn get_contract_details(
- &self,
- directive: &Directive,
- step: &ChainStep,
- ) -> (String, Option<String>, String, String) {
- let name = format!("{} - {}", directive.title, step.name);
- let description = step.description.clone();
- let contract_type = step.contract_type.clone();
- let initial_phase = step.initial_phase.clone().unwrap_or_else(|| "plan".to_string());
-
- (name, description, contract_type, initial_phase)
- }
-
- // ========================================================================
- // Evaluation
- // ========================================================================
-
- /// Handle contract completion: evaluate the step.
- pub async fn on_contract_completed(
- &self,
- contract_id: Uuid,
- ) -> Result<(), EngineError> {
- // Find the step for this contract
- let step = repository::get_step_by_contract_id(&self.pool, contract_id)
- .await?
- .ok_or(EngineError::StepNotFound(contract_id))?;
-
- // Get directive
- let chain = repository::get_directive_chain(&self.pool, step.chain_id)
- .await?
- .ok_or(EngineError::ChainNotFound(step.chain_id))?;
-
- let directive = repository::get_directive(&self.pool, chain.directive_id)
- .await?
- .ok_or(EngineError::DirectiveNotFound(chain.directive_id))?;
-
- // Update step status to evaluating
- repository::update_step_status(&self.pool, step.id, "evaluating").await?;
- self.emit_event(EngineEvent::StepStatusChanged {
- directive_id: directive.id,
- step_id: step.id,
- old_status: "running".to_string(),
- new_status: "evaluating".to_string(),
- });
-
- // Run evaluation
- let result = self.evaluate_step(&directive, &step).await?;
-
- // Record evaluation
- let programmatic_results = result
- .verifier_results
- .iter()
- .filter(|r| r.verifier_type != super::verifier::VerifierType::Llm)
- .map(|r| serde_json::to_value(r).unwrap_or(serde_json::Value::Null))
- .collect::<Vec<_>>();
-
- let llm_results = result
- .verifier_results
- .iter()
- .filter(|r| r.verifier_type == super::verifier::VerifierType::Llm)
- .map(|r| serde_json::to_value(r).unwrap_or(serde_json::Value::Null))
- .collect::<Vec<_>>();
-
- // Get chain_id from step
- let chain_id = step.chain_id;
-
- let _evaluation = repository::create_directive_evaluation(
- &self.pool,
- directive.id,
- Some(chain_id),
- Some(step.id),
- step.contract_id,
- "composite",
- Some("orchestration_engine"),
- result.passed,
- Some(result.composite_score),
- Some(result.confidence_level.as_str()),
- serde_json::Value::Array(programmatic_results),
- serde_json::Value::Array(llm_results),
- serde_json::Value::Null, // criteria_results
- &result.summary,
- result.rework_instructions.as_deref(),
- )
- .await?;
-
- // Update step based on result
- let new_status = match result.confidence_level {
- ConfidenceLevel::Green => "passed",
- ConfidenceLevel::Yellow => {
- // Check autonomy level
- if directive.autonomy_level == "full_auto" {
- "passed" // Accept yellow in full auto mode
- } else {
- // Create approval request
- self.request_approval(
- &directive,
- &step,
- "step_review",
- &format!(
- "Step '{}' completed with yellow confidence ({:.0}%). Review required.",
- step.name,
- result.composite_score * 100.0
- ),
- )
- .await?;
- "evaluating" // Wait for approval
- }
- }
- ConfidenceLevel::Red => {
- // Initiate rework
- self.initiate_rework(&directive, &step, &result).await?;
- "rework"
- }
- };
-
- repository::update_step_status(&self.pool, step.id, new_status).await?;
- repository::update_step_confidence(
- &self.pool,
- step.id,
- result.composite_score,
- result.confidence_level.as_str(),
- result.id,
- )
- .await?;
-
- self.emit_event(EngineEvent::EvaluationCompleted {
- directive_id: directive.id,
- step_id: step.id,
- passed: result.passed,
- confidence_level: result.confidence_level,
- });
-
- // If passed, continue chain execution
- if new_status == "passed" {
- self.advance_chain(directive.id).await?;
- }
-
- Ok(())
- }
-
- /// Evaluate a step using tiered verification.
- async fn evaluate_step(
- &self,
- directive: &Directive,
- step: &ChainStep,
- ) -> Result<EvaluationResult, EngineError> {
- // Get repository path
- let repo_path = directive
- .local_path
- .as_ref()
- .map(std::path::PathBuf::from)
- .unwrap_or_else(|| std::path::PathBuf::from("."));
-
- // Auto-detect verifiers
- let verifiers = auto_detect_verifiers(&repo_path).await;
-
- // Build verification context
- let context = VerificationContext {
- step_id: step.id,
- contract_id: step.contract_id,
- modified_files: vec![], // TODO: Get from contract/git
- step_description: step.description.clone().unwrap_or_default(),
- acceptance_criteria: vec![], // TODO: Get from directive
- directive_context: directive.goal.clone(),
- };
-
- // Run composite evaluation
- let evaluator = CompositeEvaluator::new(verifiers)
- .with_thresholds(
- directive.confidence_threshold_green,
- directive.confidence_threshold_yellow,
- );
-
- Ok(evaluator.evaluate(&repo_path, &context).await)
- }
-
- /// Initiate rework for a failed step.
- async fn initiate_rework(
- &self,
- directive: &Directive,
- step: &ChainStep,
- result: &EvaluationResult,
- ) -> Result<(), EngineError> {
- // Increment rework count
- let updated_step = repository::increment_step_rework_count(&self.pool, step.id).await?;
-
- // Check rework limit
- let max_rework = directive.max_rework_cycles.unwrap_or(3);
- if updated_step.rework_count >= max_rework {
- // Too many rework attempts, mark as blocked
- repository::update_step_status(&self.pool, step.id, "blocked").await?;
- self.emit_directive_event(
- directive.id,
- "step_blocked",
- "warning",
- serde_json::json!({
- "step_id": step.id,
- "step_name": step.name,
- "reason": "Max rework attempts reached",
- }),
- "system",
- )
- .await?;
- return Ok(());
- }
-
- // Log rework event
- self.emit_directive_event(
- directive.id,
- "step_rework",
- "info",
- serde_json::json!({
- "step_id": step.id,
- "step_name": step.name,
- "rework_count": updated_step.rework_count,
- "instructions": result.rework_instructions,
- }),
- "system",
- )
- .await?;
-
- // TODO: Send rework instructions to supervisor task
- // This would involve:
- // 1. Reset contract phase to 'plan'
- // 2. Send message to supervisor with rework instructions
- // 3. Update step status to 'running'
-
- Ok(())
- }
-
- /// Request human approval for a step.
- async fn request_approval(
- &self,
- directive: &Directive,
- step: &ChainStep,
- approval_type: &str,
- description: &str,
- ) -> Result<Uuid, EngineError> {
- let context = serde_json::json!({
- "step_id": step.id,
- "step_name": step.name,
- "confidence_score": step.confidence_score,
- });
-
- let approval = repository::create_approval_request(
- &self.pool,
- directive.id,
- Some(step.id),
- approval_type,
- description,
- Some(context),
- "medium",
- None, // expires_at
- )
- .await?;
-
- self.emit_event(EngineEvent::ApprovalRequired {
- directive_id: directive.id,
- approval_id: approval.id,
- approval_type: approval_type.to_string(),
- });
-
- Ok(approval.id)
- }
-
- /// Handle approval resolution.
- pub async fn on_approval_resolved(
- &self,
- approval_id: Uuid,
- approved: bool,
- responded_by: Uuid,
- ) -> Result<(), EngineError> {
- let status = if approved { "approved" } else { "denied" };
- let approval = repository::resolve_approval(
- &self.pool,
- approval_id,
- status,
- None,
- responded_by,
- )
- .await?;
-
- if let Some(step_id) = approval.step_id {
- let step = repository::get_chain_step(&self.pool, step_id)
- .await?
- .ok_or(EngineError::StepNotFound(step_id))?;
-
- let chain = repository::get_directive_chain(&self.pool, step.chain_id)
- .await?
- .ok_or(EngineError::ChainNotFound(step.chain_id))?;
-
- if approved {
- // Mark step as passed and continue
- repository::update_step_status(&self.pool, step_id, "passed").await?;
- self.advance_chain(chain.directive_id).await?;
- } else {
- // Mark step as failed/blocked
- repository::update_step_status(&self.pool, step_id, "blocked").await?;
- }
- }
-
- Ok(())
- }
-
- // ========================================================================
- // Planning
- // ========================================================================
-
- /// Build the task instructions for the planning task.
- fn build_planning_task_instructions(&self, directive: &Directive) -> String {
- let requirements: Vec<String> = directive
- .requirements
- .as_array()
- .map(|arr| {
- arr.iter()
- .filter_map(|v| v.as_object())
- .map(|obj| {
- let id = obj.get("id").and_then(|v| v.as_str()).unwrap_or("?");
- let desc = obj
- .get("description")
- .and_then(|v| v.as_str())
- .unwrap_or("?");
- format!("- {}: {}", id, desc)
- })
- .collect()
- })
- .unwrap_or_default();
-
- let criteria: Vec<String> = directive
- .acceptance_criteria
- .as_array()
- .map(|arr| {
- arr.iter()
- .filter_map(|v| v.as_object())
- .map(|obj| {
- let id = obj.get("id").and_then(|v| v.as_str()).unwrap_or("?");
- let criterion = obj
- .get("criterion")
- .and_then(|v| v.as_str())
- .unwrap_or("?");
- format!("- {}: {}", id, criterion)
- })
- .collect()
- })
- .unwrap_or_default();
-
- let constraints: Vec<String> = directive
- .constraints
- .as_array()
- .map(|arr| {
- arr.iter()
- .filter_map(|v| v.as_str())
- .map(|s| format!("- {}", s))
- .collect()
- })
- .unwrap_or_default();
-
- let repo_info = directive
- .repository_url
- .as_deref()
- .unwrap_or("(not specified)");
-
- format!(
- r#"You are planning an execution chain for a directive.
-
-## Directive: {title}
-## Goal
-{goal}
-
-## Requirements
-{requirements}
-
-## Acceptance Criteria
-{criteria}
-
-## Constraints
-{constraints}
-
-## Repository: {repo}
-
-## Your Task
-
-Analyze the repository and create a chain of execution steps.
-For each step, add it via the API:
-
-```bash
-curl -s -X POST "$MAKIMA_URL/api/v1/directives/{directive_id}/chain/steps" \
- -H "Authorization: Bearer $MAKIMA_API_KEY" \
- -H "Content-Type: application/json" \
- -d '{{
- "name": "step-name",
- "description": "What this step accomplishes",
- "step_type": "implement",
- "depends_on": [],
- "contract_type": "execute",
- "initial_phase": "execute",
- "task_plan": "Detailed instructions for the step executor"
- }}'
-```
-
-### Step types
-Use these step types: research, design, implement, test, review, document
-
-### Dependencies
-Each step can depend on other steps by name. Use the `depends_on` field with an array of step names.
-Steps with no dependencies will run in parallel.
-
-### Guidelines
-1. Break the work into logical, independently executable steps
-2. Each step should be completable by a single Claude Code session
-3. Use dependencies to enforce ordering where needed
-4. Include a "test" or "verify" step at the end
-5. Keep step names in kebab-case
-6. The `task_plan` field should contain detailed instructions for the agent that will execute the step
-
-When you have added all steps, your task is complete."#,
- title = directive.title,
- goal = directive.goal,
- requirements = if requirements.is_empty() {
- "(none)".to_string()
- } else {
- requirements.join("\n")
- },
- criteria = if criteria.is_empty() {
- "(none)".to_string()
- } else {
- criteria.join("\n")
- },
- constraints = if constraints.is_empty() {
- "(none)".to_string()
- } else {
- constraints.join("\n")
- },
- repo = repo_info,
- directive_id = directive.id,
- )
- }
-
- /// Handle planning task completion.
- pub async fn on_planning_complete(
- &self,
- directive_id: Uuid,
- success: bool,
- ) -> Result<(), EngineError> {
- let directive = repository::get_directive(&self.pool, directive_id)
- .await?
- .ok_or(EngineError::DirectiveNotFound(directive_id))?;
-
- // Only process if directive is still in planning state
- if directive.status != "planning" {
- tracing::warn!(
- "Directive {} is in state '{}', not 'planning'. Skipping planning completion.",
- directive_id,
- directive.status
- );
- return Ok(());
- }
-
- // Get current chain
- let chain = repository::get_current_chain(&self.pool, directive_id)
- .await?
- .ok_or(EngineError::ChainNotFound(directive_id))?;
-
- // Check if chain has steps
- let steps = repository::list_chain_steps(&self.pool, chain.id).await?;
-
- if success && !steps.is_empty() {
- tracing::info!(
- "Planning completed successfully for directive {} with {} steps",
- directive_id,
- steps.len()
- );
- } else {
- // Fall back to default chain
- let reason = if !success {
- "Planning task failed"
- } else {
- "Planning task produced no steps"
- };
- tracing::warn!(
- "{} for directive {}, using default chain",
- reason,
- directive_id
- );
-
- let default_chain = self.build_default_chain(&directive);
- self.create_steps_from_chain(&chain.id, &default_chain)
- .await?;
- }
-
- // Activate the directive
- repository::update_directive_status(&self.pool, directive_id, "active").await?;
- self.emit_event(EngineEvent::DirectiveStatusChanged {
- directive_id,
- old_status: "planning".to_string(),
- new_status: "active".to_string(),
- });
-
- self.emit_directive_event(
- directive_id,
- "planning_completed",
- "info",
- serde_json::json!({"success": success}),
- "system",
- )
- .await?;
-
- // Start ready steps
- self.advance_chain(directive_id).await?;
-
- Ok(())
- }
-
- // ========================================================================
- // Circuit Breakers
- // ========================================================================
-
- /// Check circuit breakers for a directive.
- async fn check_circuit_breakers(&self, directive: &Directive) -> Result<(), EngineError> {
- // Check cost limit
- if let Some(max_cost) = directive.max_total_cost_usd {
- let current_cost = directive.total_cost_usd;
- if current_cost >= max_cost {
- return Err(EngineError::CircuitBreaker(format!(
- "Cost limit exceeded: ${:.2} >= ${:.2}",
- current_cost, max_cost
- )));
- }
- }
-
- // Check time limit (stored in minutes)
- if let Some(max_minutes) = directive.max_wall_time_minutes {
- if let Some(started_at) = directive.started_at {
- let elapsed = chrono::Utc::now().signed_duration_since(started_at);
- let elapsed_minutes = elapsed.num_minutes();
- if elapsed_minutes >= max_minutes as i64 {
- return Err(EngineError::CircuitBreaker(format!(
- "Time limit exceeded: {} min >= {} min",
- elapsed_minutes, max_minutes
- )));
- }
- }
- }
-
- // Check chain generation limit
- if let Some(max_gen) = directive.max_chain_regenerations {
- let current_gen = directive.chain_generation_count;
- if current_gen >= max_gen {
- return Err(EngineError::CircuitBreaker(format!(
- "Chain generation limit exceeded: {} >= {}",
- current_gen, max_gen
- )));
- }
- }
-
- Ok(())
- }
-
- // ========================================================================
- // Completion
- // ========================================================================
-
- /// Complete a directive after all steps pass.
- async fn complete_directive(&self, directive_id: Uuid) -> Result<(), EngineError> {
- // Run final evaluation (optional)
- // TODO: LLM evaluation of overall directive completion
-
- // Update directive status
- repository::update_directive_status(&self.pool, directive_id, "completed").await?;
-
- self.emit_event(EngineEvent::DirectiveStatusChanged {
- directive_id,
- old_status: "active".to_string(),
- new_status: "completed".to_string(),
- });
-
- self.emit_directive_event(
- directive_id,
- "directive_completed",
- "info",
- serde_json::json!({}),
- "system",
- )
- .await?;
-
- Ok(())
- }
-
- // ========================================================================
- // Event Logging
- // ========================================================================
-
- /// Emit a directive event to the database.
- async fn emit_directive_event(
- &self,
- directive_id: Uuid,
- event_type: &str,
- severity: &str,
- event_data: serde_json::Value,
- actor_type: &str,
- ) -> Result<DirectiveEvent, EngineError> {
- Ok(repository::emit_directive_event(
- &self.pool,
- directive_id,
- None, // chain_id
- None, // step_id
- event_type,
- severity,
- Some(event_data),
- actor_type,
- None, // actor_id
- )
- .await?)
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_confidence_level_decision() {
- // Green confidence should pass in all modes
- assert_eq!(ConfidenceLevel::Green.as_str(), "green");
-
- // Yellow confidence behavior depends on autonomy level
- assert_eq!(ConfidenceLevel::Yellow.as_str(), "yellow");
-
- // Red confidence should always trigger rework
- assert_eq!(ConfidenceLevel::Red.as_str(), "red");
- }
-}
diff --git a/makima/src/orchestration/mod.rs b/makima/src/orchestration/mod.rs
deleted file mode 100644
index 8fca5ba..0000000
--- a/makima/src/orchestration/mod.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-//! Orchestration engine for directive-driven autonomous execution.
-//!
-//! This module provides the core orchestration capabilities:
-//! - [`DirectiveEngine`]: Main orchestration loop that manages directive lifecycle
-//! - [`ChainPlanner`]: LLM-based chain generation from directive goals
-//! - [`Verifier`]: Pluggable verification system for step validation
-//!
-//! # Architecture
-//!
-//! The orchestration system follows a directive-first approach:
-//! 1. Directives define goals, requirements, and acceptance criteria
-//! 2. Chains are generated execution plans (DAGs of steps)
-//! 3. Steps map to contracts that are created and monitored
-//! 4. Tiered verification (programmatic first, then LLM) determines confidence
-//! 5. Confidence scoring (green/yellow/red) drives autonomy decisions
-
-mod engine;
-mod planner;
-mod verifier;
-
-pub use engine::{DirectiveEngine, EngineError, PlanningStartResult};
-pub use planner::{ChainPlanner, GeneratedSpec, PlannerError};
-pub use verifier::{
- auto_detect_verifiers, CompositeEvaluator, ConfidenceLevel, EvaluationResult, Verifier,
- VerifierError, VerifierInfo, VerifierResult, VerifierType,
-};
diff --git a/makima/src/orchestration/planner.rs b/makima/src/orchestration/planner.rs
deleted file mode 100644
index aec2e48..0000000
--- a/makima/src/orchestration/planner.rs
+++ /dev/null
@@ -1,848 +0,0 @@
-//! Chain planner for LLM-based execution plan generation.
-//!
-//! Generates chains (DAGs of steps) from directive goals and requirements.
-//! Supports both initial plan generation and replanning while preserving
-//! completed work.
-
-use serde::{Deserialize, Serialize};
-use std::collections::{HashMap, HashSet};
-use thiserror::Error;
-use uuid::Uuid;
-
-use crate::db::models::{AddStepRequest, ChainStep, Directive};
-
-/// Error type for planner operations.
-#[derive(Error, Debug)]
-pub enum PlannerError {
- #[error("Cycle detected in DAG: {0}")]
- CycleDetected(String),
-
- #[error("Invalid dependency: step '{step}' depends on unknown step '{dependency}'")]
- InvalidDependency { step: String, dependency: String },
-
- #[error("LLM generation failed: {0}")]
- LlmError(String),
-
- #[error("Requirement not covered: {0}")]
- RequirementNotCovered(String),
-
- #[error("Invalid plan: {0}")]
- InvalidPlan(String),
-
- #[error("Empty plan generated")]
- EmptyPlan,
-}
-
-/// Generated step from LLM planning.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct GeneratedStep {
- /// Unique name within the chain
- pub name: String,
- /// Type of step (e.g., "research", "implement", "test", "review")
- pub step_type: String,
- /// Description of what this step accomplishes
- pub description: String,
- /// Names of steps this depends on
- pub depends_on: Vec<String>,
- /// IDs of requirements this step addresses
- pub requirement_ids: Vec<String>,
- /// Contract template fields
- pub contract_template: Option<ContractTemplate>,
-}
-
-/// Template for contract creation from step.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct ContractTemplate {
- /// Contract name
- pub name: String,
- /// Contract description
- pub description: String,
- /// Contract type (e.g., "simple", "agentic")
- pub contract_type: String,
- /// Phases for the contract
- pub phases: Vec<String>,
- /// Tasks within the contract
- pub tasks: Vec<TaskTemplate>,
- /// Deliverables expected
- pub deliverables: Vec<DeliverableTemplate>,
-}
-
-/// Template for task within contract.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct TaskTemplate {
- pub name: String,
- pub plan: String,
-}
-
-/// Template for deliverable.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct DeliverableTemplate {
- pub id: String,
- pub name: String,
- pub priority: String,
-}
-
-/// Generated chain from planning.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct GeneratedChain {
- /// Name for the chain
- pub name: String,
- /// Description of the execution plan
- pub description: String,
- /// Steps in the chain
- pub steps: Vec<GeneratedStep>,
-}
-
-/// Generated specification from LLM.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct GeneratedSpec {
- /// Generated title (if improved from goal)
- pub title: Option<String>,
- /// Structured requirements
- pub requirements: serde_json::Value,
- /// Structured acceptance criteria
- pub acceptance_criteria: serde_json::Value,
- /// Constraints extracted from goal
- pub constraints: Option<serde_json::Value>,
-}
-
-/// Chain planner for LLM-based plan generation.
-pub struct ChainPlanner {
- /// Default step types to suggest (reserved for future use)
- #[allow(dead_code)]
- default_step_types: Vec<String>,
- /// Database pool for persistence
- #[allow(dead_code)]
- pool: Option<sqlx::PgPool>,
-}
-
-impl Default for ChainPlanner {
- fn default() -> Self {
- Self::without_pool()
- }
-}
-
-impl ChainPlanner {
- /// Create a new chain planner without database pool.
- pub fn without_pool() -> Self {
- Self {
- default_step_types: vec![
- "research".to_string(),
- "design".to_string(),
- "implement".to_string(),
- "test".to_string(),
- "review".to_string(),
- "document".to_string(),
- ],
- pool: None,
- }
- }
-
- /// Create a new chain planner (backwards compatible).
- pub fn new(pool: sqlx::PgPool) -> Self {
- Self {
- default_step_types: vec![
- "research".to_string(),
- "design".to_string(),
- "implement".to_string(),
- "test".to_string(),
- "review".to_string(),
- "document".to_string(),
- ],
- pool: Some(pool),
- }
- }
-
- /// Generate a specification from a directive's goal.
- ///
- /// Analyzes the goal text to produce structured requirements and
- /// acceptance criteria. In production, this would call an LLM for
- /// richer spec generation.
- pub async fn generate_spec(
- &self,
- directive: &Directive,
- ) -> Result<GeneratedSpec, PlannerError> {
- // Build a prompt for spec generation
- let prompt = format!(
- r#"Analyze this goal and generate structured requirements and acceptance criteria.
-
-Goal: {}
-
-Generate a JSON response with:
-- title: A concise title
-- requirements: Array of {{id, title, description, priority, category}}
-- acceptance_criteria: Array of {{id, requirementIds, description, testable, verificationMethod}}
-- constraints: Array of constraint strings"#,
- directive.goal
- );
-
- // For now, generate a basic spec from the goal text.
- // When LLM integration is available, this will call the LLM with the prompt.
- let _prompt = prompt; // Will be used when LLM is wired up
-
- let title = generate_title_from_goal(&directive.goal);
-
- let requirements = serde_json::json!([
- {
- "id": "REQ-001",
- "title": title,
- "description": directive.goal,
- "priority": "required",
- "category": "core"
- }
- ]);
-
- let acceptance_criteria = serde_json::json!([
- {
- "id": "AC-001",
- "requirementIds": ["REQ-001"],
- "description": format!("Goal is achieved: {}", directive.goal),
- "testable": true,
- "verificationMethod": "manual"
- }
- ]);
-
- Ok(GeneratedSpec {
- title: Some(title),
- requirements,
- acceptance_criteria,
- constraints: None,
- })
- }
-
- /// Build a planning prompt for the LLM.
- pub fn build_planning_prompt(&self, directive: &Directive) -> String {
- let requirements: Vec<String> = directive
- .requirements
- .as_array()
- .map(|arr| {
- arr.iter()
- .filter_map(|v| v.as_object())
- .map(|obj| {
- let id = obj.get("id").and_then(|v| v.as_str()).unwrap_or("?");
- let desc = obj
- .get("description")
- .and_then(|v| v.as_str())
- .unwrap_or("?");
- format!("- {}: {}", id, desc)
- })
- .collect()
- })
- .unwrap_or_default();
-
- let criteria: Vec<String> = directive
- .acceptance_criteria
- .as_array()
- .map(|arr| {
- arr.iter()
- .filter_map(|v| v.as_object())
- .map(|obj| {
- let id = obj.get("id").and_then(|v| v.as_str()).unwrap_or("?");
- let criterion = obj
- .get("criterion")
- .and_then(|v| v.as_str())
- .unwrap_or("?");
- format!("- {}: {}", id, criterion)
- })
- .collect()
- })
- .unwrap_or_default();
-
- let constraints: Vec<String> = directive
- .constraints
- .as_array()
- .map(|arr| {
- arr.iter()
- .filter_map(|v| v.as_str())
- .map(|s| format!("- {}", s))
- .collect()
- })
- .unwrap_or_default();
-
- format!(
- r#"You are a software architect planning an execution chain for a coding task.
-
-## Directive Goal
-{goal}
-
-## Requirements
-{requirements}
-
-## Acceptance Criteria
-{criteria}
-
-## Constraints
-{constraints}
-
-## Instructions
-
-Create an execution plan as a chain of steps. Each step should:
-1. Have a unique, descriptive name (kebab-case)
-2. Specify its type (research, design, implement, test, review, document)
-3. Declare dependencies on prior steps (if any)
-4. Map to specific requirement IDs it addresses
-5. Include a contract template with tasks and deliverables
-
-The chain should form a valid DAG (no cycles). Steps can run in parallel if they don't depend on each other.
-
-Respond with a JSON object in this format:
-```json
-{{
- "name": "chain-name",
- "description": "Brief description of the plan",
- "steps": [
- {{
- "name": "step-name",
- "step_type": "implement",
- "description": "What this step does",
- "depends_on": ["prior-step-name"],
- "requirement_ids": ["REQ-001"],
- "contract_template": {{
- "name": "Contract Name",
- "description": "Contract description",
- "contract_type": "simple",
- "phases": ["plan", "execute"],
- "tasks": [
- {{"name": "Task 1", "plan": "Detailed plan for this task"}}
- ],
- "deliverables": [
- {{"id": "del-1", "name": "Deliverable 1", "priority": "required"}}
- ]
- }}
- }}
- ]
-}}
-```
-
-Generate the optimal execution plan now."#,
- goal = directive.goal,
- requirements = requirements.join("\n"),
- criteria = criteria.join("\n"),
- constraints = constraints.join("\n"),
- )
- }
-
- /// Parse LLM response into a generated chain.
- pub fn parse_plan_response(&self, response: &str) -> Result<GeneratedChain, PlannerError> {
- // Extract JSON from response (may be wrapped in markdown code blocks)
- let json_str = extract_json_from_response(response)?;
-
- let chain: GeneratedChain = serde_json::from_str(&json_str)
- .map_err(|e| PlannerError::InvalidPlan(format!("JSON parse error: {}", e)))?;
-
- if chain.steps.is_empty() {
- return Err(PlannerError::EmptyPlan);
- }
-
- // Validate the chain
- self.validate_chain(&chain)?;
-
- Ok(chain)
- }
-
- /// Validate a generated chain.
- pub fn validate_chain(&self, chain: &GeneratedChain) -> Result<(), PlannerError> {
- // Build step name set
- let step_names: HashSet<&str> = chain.steps.iter().map(|s| s.name.as_str()).collect();
-
- // Check for duplicate names
- if step_names.len() != chain.steps.len() {
- return Err(PlannerError::InvalidPlan(
- "Duplicate step names detected".to_string(),
- ));
- }
-
- // Validate dependencies exist
- for step in &chain.steps {
- for dep in &step.depends_on {
- if !step_names.contains(dep.as_str()) {
- return Err(PlannerError::InvalidDependency {
- step: step.name.clone(),
- dependency: dep.clone(),
- });
- }
- }
- }
-
- // Check for cycles using DFS
- self.detect_cycles(chain)?;
-
- Ok(())
- }
-
- /// Detect cycles in the chain DAG using DFS.
- fn detect_cycles(&self, chain: &GeneratedChain) -> Result<(), PlannerError> {
- let mut visited = HashSet::new();
- let mut rec_stack = HashSet::new();
-
- // Build adjacency map
- let adj: HashMap<&str, Vec<&str>> = chain
- .steps
- .iter()
- .map(|s| (s.name.as_str(), s.depends_on.iter().map(|d| d.as_str()).collect()))
- .collect();
-
- for step in &chain.steps {
- if !visited.contains(step.name.as_str()) {
- if self.has_cycle(&step.name, &adj, &mut visited, &mut rec_stack) {
- return Err(PlannerError::CycleDetected(step.name.clone()));
- }
- }
- }
-
- Ok(())
- }
-
- fn has_cycle<'a>(
- &self,
- node: &'a str,
- adj: &HashMap<&'a str, Vec<&'a str>>,
- visited: &mut HashSet<&'a str>,
- rec_stack: &mut HashSet<&'a str>,
- ) -> bool {
- visited.insert(node);
- rec_stack.insert(node);
-
- if let Some(deps) = adj.get(node) {
- for &dep in deps {
- if !visited.contains(dep) {
- if self.has_cycle(dep, adj, visited, rec_stack) {
- return true;
- }
- } else if rec_stack.contains(dep) {
- return true;
- }
- }
- }
-
- rec_stack.remove(node);
- false
- }
-
- /// Check that all requirements are covered by at least one step.
- pub fn check_requirement_coverage(
- &self,
- chain: &GeneratedChain,
- directive: &Directive,
- ) -> Result<(), PlannerError> {
- let required_ids: HashSet<String> = directive
- .requirements
- .as_array()
- .map(|arr| {
- arr.iter()
- .filter_map(|v| v.get("id").and_then(|id| id.as_str()))
- .map(|s| s.to_string())
- .collect()
- })
- .unwrap_or_default();
-
- let covered_ids: HashSet<String> = chain
- .steps
- .iter()
- .flat_map(|s| s.requirement_ids.clone())
- .collect();
-
- for req_id in required_ids {
- if !covered_ids.contains(&req_id) {
- return Err(PlannerError::RequirementNotCovered(req_id));
- }
- }
-
- Ok(())
- }
-
- /// Get topological order of steps.
- pub fn topological_sort<'a>(
- &self,
- chain: &'a GeneratedChain,
- ) -> Result<Vec<&'a str>, PlannerError> {
- let mut in_degree: HashMap<&str, usize> = HashMap::new();
- let mut adj: HashMap<&str, Vec<&str>> = HashMap::new();
-
- // Initialize
- for step in &chain.steps {
- in_degree.entry(step.name.as_str()).or_insert(0);
- adj.entry(step.name.as_str()).or_insert_with(Vec::new);
- }
-
- // Build graph (reversed - edges from dependency to dependent)
- for step in &chain.steps {
- for dep in &step.depends_on {
- adj.entry(dep.as_str())
- .or_insert_with(Vec::new)
- .push(step.name.as_str());
- *in_degree.entry(step.name.as_str()).or_insert(0) += 1;
- }
- }
-
- // Kahn's algorithm
- let mut queue: Vec<&str> = in_degree
- .iter()
- .filter(|&(_, deg)| *deg == 0)
- .map(|(&name, _)| name)
- .collect();
-
- let mut result = Vec::new();
-
- while let Some(node) = queue.pop() {
- result.push(node);
-
- if let Some(neighbors) = adj.get(node) {
- for &neighbor in neighbors {
- let deg = in_degree.get_mut(neighbor).unwrap();
- *deg -= 1;
- if *deg == 0 {
- queue.push(neighbor);
- }
- }
- }
- }
-
- if result.len() != chain.steps.len() {
- return Err(PlannerError::CycleDetected(
- "Cycle detected during topological sort".to_string(),
- ));
- }
-
- Ok(result)
- }
-
- /// Convert generated steps to AddStepRequest for database insertion.
- pub fn steps_to_requests(
- &self,
- chain: &GeneratedChain,
- step_id_map: &HashMap<String, Uuid>,
- ) -> Vec<AddStepRequest> {
- chain
- .steps
- .iter()
- .map(|step| {
- let depends_on: Vec<Uuid> = step
- .depends_on
- .iter()
- .filter_map(|name| step_id_map.get(name))
- .copied()
- .collect();
-
- let task_plan = step
- .contract_template
- .as_ref()
- .and_then(|t| t.tasks.first())
- .map(|t| t.plan.clone());
-
- AddStepRequest {
- name: step.name.clone(),
- description: Some(step.description.clone()),
- step_type: Some(step.step_type.clone()),
- contract_type: step.contract_template.as_ref().map(|t| t.contract_type.clone()),
- initial_phase: Some("plan".to_string()),
- task_plan,
- phases: step.contract_template.as_ref().map(|t| t.phases.clone()),
- depends_on: Some(depends_on),
- parallel_group: None,
- requirement_ids: Some(step.requirement_ids.clone()),
- acceptance_criteria_ids: None,
- verifier_config: None,
- editor_x: None,
- editor_y: None,
- }
- })
- .collect()
- }
-
- /// Compute editor positions for steps based on DAG layout.
- pub fn compute_editor_positions(
- &self,
- chain: &GeneratedChain,
- ) -> HashMap<String, (f64, f64)> {
- let depths = self.get_step_depths(chain);
- let mut positions: HashMap<String, (f64, f64)> = HashMap::new();
-
- // Group by depth
- let mut by_depth: HashMap<usize, Vec<&str>> = HashMap::new();
- for step in &chain.steps {
- let depth = depths.get(&step.name).copied().unwrap_or(0);
- by_depth.entry(depth).or_default().push(&step.name);
- }
-
- // Compute positions: x based on depth, y based on index within depth
- let x_spacing = 250.0;
- let y_spacing = 150.0;
-
- for (depth, steps) in &by_depth {
- let x = (*depth as f64) * x_spacing + 100.0;
- for (i, name) in steps.iter().enumerate() {
- let y = (i as f64) * y_spacing + 100.0;
- positions.insert(name.to_string(), (x, y));
- }
- }
-
- positions
- }
-
- /// Get depth of each step in the DAG.
- fn get_step_depths(&self, chain: &GeneratedChain) -> HashMap<String, usize> {
- let mut depths: HashMap<String, usize> = HashMap::new();
-
- // Build dependency map
- let deps: HashMap<String, Vec<String>> = chain
- .steps
- .iter()
- .map(|s| (s.name.clone(), s.depends_on.clone()))
- .collect();
-
- fn compute_depth(
- name: &str,
- deps: &HashMap<String, Vec<String>>,
- depths: &mut HashMap<String, usize>,
- ) -> usize {
- if let Some(&d) = depths.get(name) {
- return d;
- }
-
- let depth = deps
- .get(name)
- .map(|dep_list| {
- dep_list
- .iter()
- .map(|d| compute_depth(d, deps, depths) + 1)
- .max()
- .unwrap_or(0)
- })
- .unwrap_or(0);
-
- depths.insert(name.to_string(), depth);
- depth
- }
-
- for step in &chain.steps {
- compute_depth(&step.name, &deps, &mut depths);
- }
-
- depths
- }
-
- /// Build a replanning prompt that preserves completed steps.
- pub fn build_replan_prompt(
- &self,
- directive: &Directive,
- completed_steps: &[ChainStep],
- failed_step: Option<&ChainStep>,
- reason: &str,
- ) -> String {
- let completed_summary: Vec<String> = completed_steps
- .iter()
- .map(|s| format!("- {} ({}): completed", s.name, s.step_type))
- .collect();
-
- let failed_summary = failed_step
- .map(|s| format!("Failed step: {} - {}", s.name, s.description.as_deref().unwrap_or("")))
- .unwrap_or_default();
-
- format!(
- r#"You are a software architect replanning an execution chain.
-
-## Original Goal
-{goal}
-
-## Completed Steps (preserve these)
-{completed}
-
-## Failure Information
-{failed}
-Reason: {reason}
-
-## Instructions
-Generate a new execution plan that:
-1. Preserves all completed work
-2. Addresses the failure
-3. Continues toward the original goal
-
-Use the same JSON format as before. Do not include already completed steps."#,
- goal = directive.goal,
- completed = completed_summary.join("\n"),
- failed = failed_summary,
- reason = reason,
- )
- }
-}
-
-/// Generate a concise title from a goal string.
-fn generate_title_from_goal(goal: &str) -> String {
- // Take the first sentence or first 80 chars
- let title = if let Some(pos) = goal.find('.') {
- if pos < 100 {
- &goal[..pos]
- } else {
- &goal[..80.min(goal.len())]
- }
- } else if goal.len() > 80 {
- &goal[..80]
- } else {
- goal
- };
- title.trim().to_string()
-}
-
-/// Extract JSON from LLM response (handles markdown code blocks).
-fn extract_json_from_response(response: &str) -> Result<String, PlannerError> {
- // Try to find JSON in code block
- if let Some(start) = response.find("```json") {
- let json_start = start + 7;
- if let Some(end) = response[json_start..].find("```") {
- return Ok(response[json_start..json_start + end].trim().to_string());
- }
- }
-
- // Try to find JSON in generic code block
- if let Some(start) = response.find("```") {
- let block_start = start + 3;
- // Skip language identifier if present
- let json_start = response[block_start..]
- .find('\n')
- .map(|i| block_start + i + 1)
- .unwrap_or(block_start);
- if let Some(end) = response[json_start..].find("```") {
- return Ok(response[json_start..json_start + end].trim().to_string());
- }
- }
-
- // Try to parse the whole thing as JSON
- if response.trim().starts_with('{') {
- return Ok(response.trim().to_string());
- }
-
- Err(PlannerError::InvalidPlan(
- "Could not extract JSON from response".to_string(),
- ))
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- fn make_test_chain() -> GeneratedChain {
- GeneratedChain {
- name: "test-chain".to_string(),
- description: "Test chain".to_string(),
- steps: vec![
- GeneratedStep {
- name: "step-a".to_string(),
- step_type: "research".to_string(),
- description: "Research step".to_string(),
- depends_on: vec![],
- requirement_ids: vec!["REQ-001".to_string()],
- contract_template: None,
- },
- GeneratedStep {
- name: "step-b".to_string(),
- step_type: "implement".to_string(),
- description: "Implementation step".to_string(),
- depends_on: vec!["step-a".to_string()],
- requirement_ids: vec!["REQ-002".to_string()],
- contract_template: None,
- },
- GeneratedStep {
- name: "step-c".to_string(),
- step_type: "test".to_string(),
- description: "Test step".to_string(),
- depends_on: vec!["step-b".to_string()],
- requirement_ids: vec!["REQ-001".to_string()],
- contract_template: None,
- },
- ],
- }
- }
-
- #[test]
- fn test_validate_chain_valid() {
- let planner = ChainPlanner::without_pool();
- let chain = make_test_chain();
- assert!(planner.validate_chain(&chain).is_ok());
- }
-
- #[test]
- fn test_validate_chain_invalid_dependency() {
- let planner = ChainPlanner::without_pool();
- let mut chain = make_test_chain();
- chain.steps[1].depends_on = vec!["nonexistent".to_string()];
-
- let result = planner.validate_chain(&chain);
- assert!(matches!(result, Err(PlannerError::InvalidDependency { .. })));
- }
-
- #[test]
- fn test_validate_chain_cycle() {
- let planner = ChainPlanner::without_pool();
- let chain = GeneratedChain {
- name: "cyclic".to_string(),
- description: "Has cycle".to_string(),
- steps: vec![
- GeneratedStep {
- name: "a".to_string(),
- step_type: "research".to_string(),
- description: "A".to_string(),
- depends_on: vec!["c".to_string()],
- requirement_ids: vec![],
- contract_template: None,
- },
- GeneratedStep {
- name: "b".to_string(),
- step_type: "implement".to_string(),
- description: "B".to_string(),
- depends_on: vec!["a".to_string()],
- requirement_ids: vec![],
- contract_template: None,
- },
- GeneratedStep {
- name: "c".to_string(),
- step_type: "test".to_string(),
- description: "C".to_string(),
- depends_on: vec!["b".to_string()],
- requirement_ids: vec![],
- contract_template: None,
- },
- ],
- };
-
- let result = planner.validate_chain(&chain);
- assert!(matches!(result, Err(PlannerError::CycleDetected(_))));
- }
-
- #[test]
- fn test_topological_sort() {
- let planner = ChainPlanner::without_pool();
- let chain = make_test_chain();
- let order = planner.topological_sort(&chain).unwrap();
-
- // step-a must come before step-b, step-b before step-c
- let pos_a = order.iter().position(|&n| n == "step-a").unwrap();
- let pos_b = order.iter().position(|&n| n == "step-b").unwrap();
- let pos_c = order.iter().position(|&n| n == "step-c").unwrap();
-
- assert!(pos_a < pos_b);
- assert!(pos_b < pos_c);
- }
-
- #[test]
- fn test_extract_json_from_code_block() {
- let response = r#"
-Here's the plan:
-
-```json
-{"name": "test"}
-```
-
-That's it!
-"#;
- let json = extract_json_from_response(response).unwrap();
- assert_eq!(json, r#"{"name": "test"}"#);
- }
-
- #[test]
- fn test_extract_json_raw() {
- let response = r#"{"name": "test"}"#;
- let json = extract_json_from_response(response).unwrap();
- assert_eq!(json, r#"{"name": "test"}"#);
- }
-}
diff --git a/makima/src/orchestration/verifier.rs b/makima/src/orchestration/verifier.rs
deleted file mode 100644
index bc29e47..0000000
--- a/makima/src/orchestration/verifier.rs
+++ /dev/null
@@ -1,833 +0,0 @@
-//! Verification system for directive step evaluation.
-//!
-//! Provides tiered verification: programmatic verifiers run first,
-//! then LLM evaluation if programmatic checks pass. Composite scoring
-//! combines results with configurable weights.
-
-use async_trait::async_trait;
-use serde::{Deserialize, Serialize};
-use serde_json::Value as JsonValue;
-use std::path::Path;
-use thiserror::Error;
-use uuid::Uuid;
-
-/// Confidence level based on composite score and thresholds.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(rename_all = "snake_case")]
-pub enum ConfidenceLevel {
- /// High confidence (score >= green threshold)
- Green,
- /// Medium confidence (score >= yellow threshold but < green)
- Yellow,
- /// Low confidence (score < yellow threshold)
- Red,
-}
-
-impl ConfidenceLevel {
- /// Compute confidence level from score and thresholds.
- pub fn from_score(score: f64, green_threshold: f64, yellow_threshold: f64) -> Self {
- if score >= green_threshold {
- ConfidenceLevel::Green
- } else if score >= yellow_threshold {
- ConfidenceLevel::Yellow
- } else {
- ConfidenceLevel::Red
- }
- }
-
- /// Convert to string for database storage.
- pub fn as_str(&self) -> &'static str {
- match self {
- ConfidenceLevel::Green => "green",
- ConfidenceLevel::Yellow => "yellow",
- ConfidenceLevel::Red => "red",
- }
- }
-}
-
-impl std::fmt::Display for ConfidenceLevel {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}", self.as_str())
- }
-}
-
-/// Type of verifier for categorization.
-#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(rename_all = "snake_case")]
-pub enum VerifierType {
- /// Run test suite (npm test, cargo test, pytest, etc.)
- TestRunner,
- /// Run linter (eslint, clippy, ruff, etc.)
- Linter,
- /// Run type checker (tsc, mypy, etc.)
- TypeChecker,
- /// Run build command (npm build, cargo build, etc.)
- Build,
- /// Custom command verifier
- Custom,
- /// LLM-based semantic evaluation
- Llm,
-}
-
-impl VerifierType {
- pub fn as_str(&self) -> &'static str {
- match self {
- VerifierType::TestRunner => "test_runner",
- VerifierType::Linter => "linter",
- VerifierType::TypeChecker => "type_checker",
- VerifierType::Build => "build",
- VerifierType::Custom => "custom",
- VerifierType::Llm => "llm",
- }
- }
-}
-
-/// Result of a single verifier run.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct VerifierResult {
- /// Name of the verifier
- pub name: String,
- /// Type of verifier
- pub verifier_type: VerifierType,
- /// Whether the verification passed
- pub passed: bool,
- /// Score from 0.0 to 1.0 (1.0 = perfect, 0.0 = complete failure)
- pub score: f64,
- /// Weight for composite scoring (default 1.0 for programmatic, 2.0 for LLM)
- pub weight: f64,
- /// Whether this verifier is required (failure = automatic red confidence)
- pub required: bool,
- /// Human-readable output/feedback
- pub output: String,
- /// Structured details (test counts, lint errors, etc.)
- pub details: Option<JsonValue>,
- /// Execution time in milliseconds
- pub duration_ms: u64,
-}
-
-impl VerifierResult {
- /// Create a passed result with full score.
- pub fn passed(name: String, verifier_type: VerifierType, output: String) -> Self {
- Self {
- name,
- verifier_type,
- passed: true,
- score: 1.0,
- weight: 1.0,
- required: false,
- output,
- details: None,
- duration_ms: 0,
- }
- }
-
- /// Create a failed result with zero score.
- pub fn failed(name: String, verifier_type: VerifierType, output: String) -> Self {
- Self {
- name,
- verifier_type,
- passed: false,
- score: 0.0,
- weight: 1.0,
- required: false,
- output,
- details: None,
- duration_ms: 0,
- }
- }
-
- /// Set the weight for this result.
- pub fn with_weight(mut self, weight: f64) -> Self {
- self.weight = weight;
- self
- }
-
- /// Mark this verifier as required.
- pub fn as_required(mut self) -> Self {
- self.required = true;
- self
- }
-
- /// Set the score explicitly.
- pub fn with_score(mut self, score: f64) -> Self {
- self.score = score.clamp(0.0, 1.0);
- self
- }
-
- /// Set structured details.
- pub fn with_details(mut self, details: JsonValue) -> Self {
- self.details = Some(details);
- self
- }
-
- /// Set execution duration.
- pub fn with_duration(mut self, duration_ms: u64) -> Self {
- self.duration_ms = duration_ms;
- self
- }
-}
-
-/// Composite evaluation result combining multiple verifier results.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct EvaluationResult {
- /// Unique ID for this evaluation
- pub id: Uuid,
- /// Step ID being evaluated
- pub step_id: Uuid,
- /// Whether all required verifiers passed
- pub passed: bool,
- /// Weighted composite score (0.0-1.0)
- pub composite_score: f64,
- /// Confidence level derived from score
- pub confidence_level: ConfidenceLevel,
- /// Individual verifier results
- pub verifier_results: Vec<VerifierResult>,
- /// Summary feedback for the step
- pub summary: String,
- /// Rework instructions if failed
- pub rework_instructions: Option<String>,
- /// Total evaluation duration in milliseconds
- pub total_duration_ms: u64,
-}
-
-impl EvaluationResult {
- /// Create a new evaluation result from verifier results.
- pub fn from_verifiers(
- step_id: Uuid,
- results: Vec<VerifierResult>,
- green_threshold: f64,
- yellow_threshold: f64,
- ) -> Self {
- let id = Uuid::new_v4();
-
- // Check if any required verifier failed
- let any_required_failed = results.iter().any(|r| r.required && !r.passed);
-
- // Calculate weighted composite score
- let (total_weighted_score, total_weight) =
- results
- .iter()
- .fold((0.0, 0.0), |(score_acc, weight_acc), r| {
- (score_acc + r.score * r.weight, weight_acc + r.weight)
- });
-
- let composite_score = if total_weight > 0.0 {
- total_weighted_score / total_weight
- } else {
- 0.0
- };
-
- // Override confidence to red if any required verifier failed
- let confidence_level = if any_required_failed {
- ConfidenceLevel::Red
- } else {
- ConfidenceLevel::from_score(composite_score, green_threshold, yellow_threshold)
- };
-
- let passed = !any_required_failed && confidence_level != ConfidenceLevel::Red;
-
- // Generate summary
- let passed_count = results.iter().filter(|r| r.passed).count();
- let total_count = results.len();
- let summary = format!(
- "{}/{} verifiers passed, composite score: {:.2}, confidence: {}",
- passed_count, total_count, composite_score, confidence_level
- );
-
- // Generate rework instructions if failed
- let rework_instructions = if !passed {
- let failed_verifiers: Vec<&str> = results
- .iter()
- .filter(|r| !r.passed)
- .map(|r| r.name.as_str())
- .collect();
- Some(format!(
- "Fix issues identified by: {}",
- failed_verifiers.join(", ")
- ))
- } else {
- None
- };
-
- let total_duration_ms = results.iter().map(|r| r.duration_ms).sum();
-
- Self {
- id,
- step_id,
- passed,
- composite_score,
- confidence_level,
- verifier_results: results,
- summary,
- rework_instructions,
- total_duration_ms,
- }
- }
-}
-
-/// Error type for verification operations.
-#[derive(Error, Debug)]
-pub enum VerifierError {
- #[error("Command execution failed: {0}")]
- CommandFailed(String),
-
- #[error("Command timed out after {0}ms")]
- Timeout(u64),
-
- #[error("Working directory not found: {0}")]
- WorkingDirectoryNotFound(String),
-
- #[error("Verifier not configured: {0}")]
- NotConfigured(String),
-
- #[error("Parse error: {0}")]
- ParseError(String),
-
- #[error("LLM error: {0}")]
- LlmError(String),
-
- #[error("IO error: {0}")]
- Io(#[from] std::io::Error),
-}
-
-/// Information about a verifier for serialization and database storage.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct VerifierInfo {
- pub name: String,
- pub verifier_type: String,
- pub command: String,
- pub working_directory: Option<String>,
- pub detect_files: Vec<String>,
- pub weight: f64,
- pub required: bool,
-}
-
-/// Verifier trait for pluggable verification implementations.
-#[async_trait]
-pub trait Verifier: Send + Sync {
- /// Get the name of this verifier.
- fn name(&self) -> &str;
-
- /// Get the type of this verifier.
- fn verifier_type(&self) -> VerifierType;
-
- /// Get serializable info about this verifier.
- fn info(&self) -> VerifierInfo;
-
- /// Check if this verifier is applicable to the given repository.
- async fn is_applicable(&self, repo_path: &Path) -> bool;
-
- /// Run verification and return result.
- async fn verify(&self, repo_path: &Path, context: &VerificationContext)
- -> Result<VerifierResult, VerifierError>;
-}
-
-/// Context provided to verifiers during execution.
-#[derive(Debug, Clone)]
-pub struct VerificationContext {
- /// Step ID being verified
- pub step_id: Uuid,
- /// Contract ID if step has been instantiated
- pub contract_id: Option<Uuid>,
- /// Files that were modified in this step
- pub modified_files: Vec<String>,
- /// Step description for LLM context
- pub step_description: String,
- /// Acceptance criteria for LLM evaluation
- pub acceptance_criteria: Vec<String>,
- /// Additional context from directive
- pub directive_context: String,
-}
-
-/// Command-based verifier for running shell commands.
-pub struct CommandVerifier {
- name: String,
- verifier_type: VerifierType,
- command: String,
- #[allow(dead_code)]
- working_dir: Option<String>,
- #[allow(dead_code)]
- timeout_ms: u64,
- required: bool,
- /// Files/patterns that indicate this verifier is applicable
- applicable_patterns: Vec<String>,
-}
-
-impl CommandVerifier {
- /// Create a new command verifier.
- pub fn new(
- name: impl Into<String>,
- verifier_type: VerifierType,
- command: impl Into<String>,
- ) -> Self {
- Self {
- name: name.into(),
- verifier_type,
- command: command.into(),
- working_dir: None,
- timeout_ms: 300_000, // 5 minute default
- required: false,
- applicable_patterns: Vec::new(),
- }
- }
-
- /// Set the working directory.
- #[allow(dead_code)]
- pub fn with_working_dir(mut self, dir: impl Into<String>) -> Self {
- self.working_dir = Some(dir.into());
- self
- }
-
- /// Set the timeout in milliseconds.
- #[allow(dead_code)]
- pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
- self.timeout_ms = timeout_ms;
- self
- }
-
- /// Mark as required verifier.
- pub fn as_required(mut self) -> Self {
- self.required = true;
- self
- }
-
- /// Add applicability patterns (files that must exist).
- pub fn with_patterns(mut self, patterns: Vec<String>) -> Self {
- self.applicable_patterns = patterns;
- self
- }
-}
-
-#[async_trait]
-impl Verifier for CommandVerifier {
- fn name(&self) -> &str {
- &self.name
- }
-
- fn verifier_type(&self) -> VerifierType {
- self.verifier_type.clone()
- }
-
- fn info(&self) -> VerifierInfo {
- VerifierInfo {
- name: self.name.clone(),
- verifier_type: self.verifier_type.as_str().to_string(),
- command: self.command.clone(),
- working_directory: self.working_dir.clone(),
- detect_files: self.applicable_patterns.clone(),
- weight: 1.0,
- required: self.required,
- }
- }
-
- async fn is_applicable(&self, repo_path: &Path) -> bool {
- if self.applicable_patterns.is_empty() {
- return true;
- }
-
- for pattern in &self.applicable_patterns {
- let check_path = repo_path.join(pattern);
- if check_path.exists() {
- return true;
- }
- }
- false
- }
-
- async fn verify(
- &self,
- repo_path: &Path,
- _context: &VerificationContext,
- ) -> Result<VerifierResult, VerifierError> {
- let start = std::time::Instant::now();
-
- let work_dir = self
- .working_dir
- .as_ref()
- .map(|d| repo_path.join(d))
- .unwrap_or_else(|| repo_path.to_path_buf());
-
- if !work_dir.exists() {
- return Err(VerifierError::WorkingDirectoryNotFound(
- work_dir.display().to_string(),
- ));
- }
-
- // Parse command into program and args
- let parts: Vec<&str> = self.command.split_whitespace().collect();
- if parts.is_empty() {
- return Err(VerifierError::CommandFailed(
- "Empty command".to_string(),
- ));
- }
-
- let program = parts[0];
- let args = &parts[1..];
-
- // Execute command
- let output = tokio::process::Command::new(program)
- .args(args)
- .current_dir(&work_dir)
- .output()
- .await?;
-
- let duration_ms = start.elapsed().as_millis() as u64;
- let stdout = String::from_utf8_lossy(&output.stdout);
- let stderr = String::from_utf8_lossy(&output.stderr);
- let combined_output = format!("{}\n{}", stdout, stderr);
-
- let passed = output.status.success();
- let score = if passed { 1.0 } else { 0.0 };
-
- let mut result = VerifierResult {
- name: self.name.clone(),
- verifier_type: self.verifier_type.clone(),
- passed,
- score,
- weight: 1.0,
- required: self.required,
- output: combined_output,
- details: Some(serde_json::json!({
- "exit_code": output.status.code(),
- "command": self.command,
- "working_dir": work_dir.display().to_string(),
- })),
- duration_ms,
- };
-
- // Try to extract more detailed scoring from output
- result = self.enhance_result(result, &stdout);
-
- Ok(result)
- }
-}
-
-impl CommandVerifier {
- /// Enhance result with parsed details from output.
- fn enhance_result(&self, mut result: VerifierResult, stdout: &str) -> VerifierResult {
- match self.verifier_type {
- VerifierType::TestRunner => {
- // Try to parse test counts from common formats
- if let Some((passed, failed, total)) = parse_test_output(stdout) {
- result.details = Some(serde_json::json!({
- "tests_passed": passed,
- "tests_failed": failed,
- "tests_total": total,
- "command": self.command,
- }));
- if total > 0 {
- result.score = passed as f64 / total as f64;
- }
- }
- }
- VerifierType::Linter => {
- // Try to parse lint error counts
- if let Some(error_count) = parse_lint_output(stdout) {
- result.details = Some(serde_json::json!({
- "errors": error_count,
- "command": self.command,
- }));
- // Score decreases with more errors (up to 10 errors = 0)
- result.score = (1.0 - (error_count as f64 / 10.0)).max(0.0);
- }
- }
- _ => {}
- }
- result
- }
-}
-
-/// Parse test output for common formats (Jest, pytest, cargo test).
-fn parse_test_output(output: &str) -> Option<(u32, u32, u32)> {
- // Jest format: "Tests: X passed, Y failed, Z total"
- if let Some(caps) = regex::Regex::new(r"Tests:\s*(\d+)\s*passed,\s*(\d+)\s*failed,\s*(\d+)\s*total")
- .ok()?
- .captures(output)
- {
- let passed: u32 = caps.get(1)?.as_str().parse().ok()?;
- let failed: u32 = caps.get(2)?.as_str().parse().ok()?;
- let total: u32 = caps.get(3)?.as_str().parse().ok()?;
- return Some((passed, failed, total));
- }
-
- // pytest format: "X passed, Y failed"
- if let Some(caps) = regex::Regex::new(r"(\d+)\s*passed(?:,\s*(\d+)\s*failed)?")
- .ok()?
- .captures(output)
- {
- let passed: u32 = caps.get(1)?.as_str().parse().ok()?;
- let failed: u32 = caps.get(2).map(|m| m.as_str().parse().ok()).flatten().unwrap_or(0);
- let total = passed + failed;
- return Some((passed, failed, total));
- }
-
- // cargo test format: "test result: ok. X passed; Y failed;"
- if let Some(caps) = regex::Regex::new(r"test result:.*?(\d+)\s*passed;\s*(\d+)\s*failed")
- .ok()?
- .captures(output)
- {
- let passed: u32 = caps.get(1)?.as_str().parse().ok()?;
- let failed: u32 = caps.get(2)?.as_str().parse().ok()?;
- let total = passed + failed;
- return Some((passed, failed, total));
- }
-
- None
-}
-
-/// Parse lint output for error counts.
-fn parse_lint_output(output: &str) -> Option<u32> {
- // ESLint format: "X problems (Y errors, Z warnings)"
- if let Some(caps) = regex::Regex::new(r"(\d+)\s*problems?\s*\((\d+)\s*errors?")
- .ok()?
- .captures(output)
- {
- return caps.get(2)?.as_str().parse().ok();
- }
-
- // Clippy format: "warning: X warnings emitted"
- if let Some(caps) = regex::Regex::new(r"warning:\s*(\d+)\s*warnings?\s*emitted")
- .ok()?
- .captures(output)
- {
- return caps.get(1)?.as_str().parse().ok();
- }
-
- None
-}
-
-/// Auto-detect applicable verifiers for a repository.
-pub async fn auto_detect_verifiers(repo_path: &Path) -> Vec<Box<dyn Verifier>> {
- let mut verifiers: Vec<Box<dyn Verifier>> = Vec::new();
-
- // Check for package.json (Node.js)
- let package_json = repo_path.join("package.json");
- if package_json.exists() {
- if let Ok(content) = tokio::fs::read_to_string(&package_json).await {
- if let Ok(pkg) = serde_json::from_str::<serde_json::Value>(&content) {
- if let Some(scripts) = pkg.get("scripts").and_then(|s| s.as_object()) {
- // Test runner
- if scripts.contains_key("test") {
- verifiers.push(Box::new(
- CommandVerifier::new("npm-test", VerifierType::TestRunner, "npm test")
- .with_patterns(vec!["package.json".to_string()])
- .as_required(),
- ));
- }
-
- // Linter
- if scripts.contains_key("lint") {
- verifiers.push(Box::new(
- CommandVerifier::new("npm-lint", VerifierType::Linter, "npm run lint")
- .with_patterns(vec!["package.json".to_string()]),
- ));
- }
-
- // Build
- if scripts.contains_key("build") {
- verifiers.push(Box::new(
- CommandVerifier::new("npm-build", VerifierType::Build, "npm run build")
- .with_patterns(vec!["package.json".to_string()])
- .as_required(),
- ));
- }
-
- // Type check (for TypeScript projects)
- if scripts.contains_key("typecheck") || scripts.contains_key("type-check") {
- let cmd = if scripts.contains_key("typecheck") {
- "npm run typecheck"
- } else {
- "npm run type-check"
- };
- verifiers.push(Box::new(
- CommandVerifier::new("npm-typecheck", VerifierType::TypeChecker, cmd)
- .with_patterns(vec!["tsconfig.json".to_string()]),
- ));
- }
- }
- }
- }
- }
-
- // Check for Cargo.toml (Rust)
- let cargo_toml = repo_path.join("Cargo.toml");
- if cargo_toml.exists() {
- verifiers.push(Box::new(
- CommandVerifier::new("cargo-test", VerifierType::TestRunner, "cargo test")
- .with_patterns(vec!["Cargo.toml".to_string()])
- .as_required(),
- ));
-
- verifiers.push(Box::new(
- CommandVerifier::new("cargo-clippy", VerifierType::Linter, "cargo clippy -- -D warnings")
- .with_patterns(vec!["Cargo.toml".to_string()]),
- ));
-
- verifiers.push(Box::new(
- CommandVerifier::new("cargo-build", VerifierType::Build, "cargo build")
- .with_patterns(vec!["Cargo.toml".to_string()])
- .as_required(),
- ));
- }
-
- // Check for pyproject.toml or setup.py (Python)
- let pyproject = repo_path.join("pyproject.toml");
- let setup_py = repo_path.join("setup.py");
- if pyproject.exists() || setup_py.exists() {
- verifiers.push(Box::new(
- CommandVerifier::new("pytest", VerifierType::TestRunner, "pytest")
- .with_patterns(vec![
- "pyproject.toml".to_string(),
- "setup.py".to_string(),
- ])
- .as_required(),
- ));
-
- verifiers.push(Box::new(
- CommandVerifier::new("ruff", VerifierType::Linter, "ruff check .")
- .with_patterns(vec!["pyproject.toml".to_string()]),
- ));
- }
-
- verifiers
-}
-
-/// Composite evaluator that runs multiple verifiers and combines results.
-pub struct CompositeEvaluator {
- verifiers: Vec<Box<dyn Verifier>>,
- green_threshold: f64,
- yellow_threshold: f64,
-}
-
-impl CompositeEvaluator {
- /// Create a new composite evaluator with default thresholds.
- pub fn new(verifiers: Vec<Box<dyn Verifier>>) -> Self {
- Self {
- verifiers,
- green_threshold: 0.8,
- yellow_threshold: 0.5,
- }
- }
-
- /// Set confidence thresholds.
- pub fn with_thresholds(mut self, green: f64, yellow: f64) -> Self {
- self.green_threshold = green;
- self.yellow_threshold = yellow;
- self
- }
-
- /// Add a verifier.
- pub fn add_verifier(mut self, verifier: Box<dyn Verifier>) -> Self {
- self.verifiers.push(verifier);
- self
- }
-
- /// Run all applicable verifiers and return composite result.
- pub async fn evaluate(
- &self,
- repo_path: &Path,
- context: &VerificationContext,
- ) -> EvaluationResult {
- let mut results = Vec::new();
-
- for verifier in &self.verifiers {
- if !verifier.is_applicable(repo_path).await {
- continue;
- }
-
- match verifier.verify(repo_path, context).await {
- Ok(result) => results.push(result),
- Err(e) => {
- // Convert error to failed result
- results.push(VerifierResult::failed(
- verifier.name().to_string(),
- verifier.verifier_type(),
- format!("Verifier error: {}", e),
- ));
- }
- }
- }
-
- EvaluationResult::from_verifiers(
- context.step_id,
- results,
- self.green_threshold,
- self.yellow_threshold,
- )
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_confidence_level_from_score() {
- assert_eq!(
- ConfidenceLevel::from_score(0.9, 0.8, 0.5),
- ConfidenceLevel::Green
- );
- assert_eq!(
- ConfidenceLevel::from_score(0.8, 0.8, 0.5),
- ConfidenceLevel::Green
- );
- assert_eq!(
- ConfidenceLevel::from_score(0.6, 0.8, 0.5),
- ConfidenceLevel::Yellow
- );
- assert_eq!(
- ConfidenceLevel::from_score(0.5, 0.8, 0.5),
- ConfidenceLevel::Yellow
- );
- assert_eq!(
- ConfidenceLevel::from_score(0.4, 0.8, 0.5),
- ConfidenceLevel::Red
- );
- }
-
- #[test]
- fn test_evaluation_result_composite_score() {
- let results = vec![
- VerifierResult::passed("test1".into(), VerifierType::TestRunner, "OK".into())
- .with_weight(1.0),
- VerifierResult::failed("test2".into(), VerifierType::Linter, "Failed".into())
- .with_weight(1.0),
- ];
-
- let eval = EvaluationResult::from_verifiers(Uuid::new_v4(), results, 0.8, 0.5);
- assert!((eval.composite_score - 0.5).abs() < 0.001);
- assert_eq!(eval.confidence_level, ConfidenceLevel::Yellow);
- }
-
- #[test]
- fn test_required_verifier_override() {
- let results = vec![
- VerifierResult::passed("test1".into(), VerifierType::TestRunner, "OK".into()),
- VerifierResult::failed("build".into(), VerifierType::Build, "Failed".into())
- .as_required(),
- ];
-
- let eval = EvaluationResult::from_verifiers(Uuid::new_v4(), results, 0.8, 0.5);
- // Even though composite score is 0.5, required failure overrides to red
- assert_eq!(eval.confidence_level, ConfidenceLevel::Red);
- assert!(!eval.passed);
- }
-
- #[test]
- fn test_parse_test_output_jest() {
- let output = "Tests: 10 passed, 2 failed, 12 total";
- let (passed, failed, total) = parse_test_output(output).unwrap();
- assert_eq!(passed, 10);
- assert_eq!(failed, 2);
- assert_eq!(total, 12);
- }
-
- #[test]
- fn test_parse_test_output_cargo() {
- let output = "test result: ok. 25 passed; 0 failed;";
- let (passed, failed, total) = parse_test_output(output).unwrap();
- assert_eq!(passed, 25);
- assert_eq!(failed, 0);
- assert_eq!(total, 25);
- }
-}
diff --git a/makima/src/server/handlers/contracts.rs b/makima/src/server/handlers/contracts.rs
index 8a6ce0f..a83c72d 100644
--- a/makima/src/server/handlers/contracts.rs
+++ b/makima/src/server/handlers/contracts.rs
@@ -575,90 +575,7 @@ pub async fn update_contract(
}),
).await;
- // If contract is part of a directive chain step, update the step status
- // and emit an event for the directive engine to process
- let pool_for_step = pool.clone();
- let contract_id_for_step = contract.id;
- tokio::spawn(async move {
- // Look up the step by contract_id
- match repository::get_step_by_contract_id(&pool_for_step, contract_id_for_step).await {
- Ok(Some(step)) => {
- // Get the chain to find the directive_id
- let directive_id = match repository::get_directive_chain(&pool_for_step, step.chain_id).await {
- Ok(Some(chain)) => chain.directive_id,
- Ok(None) => {
- tracing::warn!(
- chain_id = %step.chain_id,
- "Chain not found for step"
- );
- return;
- }
- Err(e) => {
- tracing::warn!(
- chain_id = %step.chain_id,
- error = %e,
- "Failed to get chain for step"
- );
- return;
- }
- };
-
- // Update step status to 'evaluating'
- if let Err(e) = repository::update_step_status(&pool_for_step, step.id, "evaluating").await {
- tracing::warn!(
- step_id = %step.id,
- contract_id = %contract_id_for_step,
- error = %e,
- "Failed to update step status to evaluating"
- );
- } else {
- tracing::info!(
- step_id = %step.id,
- contract_id = %contract_id_for_step,
- chain_id = %step.chain_id,
- directive_id = %directive_id,
- "Contract completed - step transitioned to evaluating"
- );
-
- // Emit directive event for contract completion
- if let Err(e) = repository::emit_directive_event(
- &pool_for_step,
- directive_id,
- Some(step.chain_id),
- Some(step.id),
- "contract_completed",
- "info",
- Some(serde_json::json!({
- "contract_id": contract_id_for_step,
- "step_id": step.id,
- "step_name": step.name
- })),
- "system",
- None,
- ).await {
- tracing::warn!(
- step_id = %step.id,
- error = %e,
- "Failed to emit contract_completed directive event"
- );
- }
- }
- }
- Ok(None) => {
- tracing::debug!(
- contract_id = %contract_id_for_step,
- "Contract not linked to any directive chain step"
- );
- }
- Err(e) => {
- tracing::warn!(
- contract_id = %contract_id_for_step,
- error = %e,
- "Failed to look up step for completed contract"
- );
- }
- }
- });
+ // TODO: Directive engine integration (removed for reimplementation)
}
// 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 9c65c5e..0000000
--- a/makima/src/server/handlers/directives.rs
+++ /dev/null
@@ -1,2116 +0,0 @@
-//! API handlers for directives.
-//!
-//! Provides REST endpoints for managing directives, chains, steps,
-//! evaluations, events, verifiers, and approvals.
-
-use axum::{
- extract::{Path, Query, State},
- http::StatusCode,
- response::{
- sse::{Event, Sse},
- IntoResponse,
- },
- Json,
-};
-use futures::stream;
-use serde::{Deserialize, Serialize};
-use std::convert::Infallible;
-use std::time::Duration;
-use uuid::Uuid;
-
-use crate::db::models::{
- AddStepRequest, CreateDirectiveRequest, CreateVerifierRequest, ReworkStepRequest,
- UpdateCriteriaRequest, UpdateDirectiveRequest, UpdateRequirementsRequest, UpdateStepRequest,
- UpdateVerifierRequest,
-};
-use crate::db::repository;
-use crate::server::auth::Authenticated;
-use crate::server::messages::ApiError;
-use crate::server::state::SharedState;
-
-/// Query parameters for listing directives
-#[derive(Debug, Deserialize)]
-pub struct ListDirectivesQuery {
- pub status: Option<String>,
-}
-
-/// Query parameters for listing events
-#[derive(Debug, Deserialize)]
-pub struct ListEventsQuery {
- pub limit: Option<i64>,
-}
-
-/// Query parameters for SSE stream authentication
-/// EventSource API cannot set custom headers, so auth is passed via query params
-#[derive(Debug, Deserialize)]
-pub struct StreamAuthQuery {
- pub token: Option<String>,
- pub api_key: Option<String>,
-}
-
-/// Query parameters for listing evaluations
-#[derive(Debug, Deserialize)]
-pub struct ListEvaluationsQuery {
- pub limit: Option<i64>,
- #[serde(rename = "stepId")]
- pub step_id: Option<Uuid>,
-}
-
-/// Response for directive creation
-#[derive(Debug, Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct CreateDirectiveResponse {
- pub id: Uuid,
- pub title: String,
- pub status: String,
-}
-
-/// Response for approval actions
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct ApprovalActionRequest {
- pub response: Option<String>,
-}
-
-// =============================================================================
-// Directive CRUD
-// =============================================================================
-
-/// Create a new directive
-/// POST /api/v1/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) => Json(CreateDirectiveResponse {
- id: directive.id,
- title: directive.title,
- status: directive.status,
- })
- .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()
- }
- }
-}
-
-/// List directives for the authenticated owner
-/// GET /api/v1/directives
-pub async fn list_directives(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Query(params): Query<ListDirectivesQuery>,
-) -> 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, params.status.as_deref()).await
- {
- Ok(directives) => {
- let total = directives.len() as i64;
- Json(serde_json::json!({
- "directives": directives,
- "total": 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 with progress details
-/// GET /api/v1/directives/:id
-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();
- };
-
- match repository::get_directive_with_progress(pool, id, auth.owner_id).await {
- Ok(Some(directive)) => Json(directive).into_response(),
- Ok(None) => (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Directive not found")),
- )
- .into_response(),
- Err(e) => {
- tracing::error!("Failed to get directive: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Update a directive
-/// PUT /api/v1/directives/:id
-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(directive) => Json(directive).into_response(),
- Err(repository::RepositoryError::VersionConflict { expected, actual }) => (
- StatusCode::CONFLICT,
- Json(ApiError::new(
- "VERSION_CONFLICT",
- &format!(
- "Version conflict: expected {}, got {}",
- expected, actual
- ),
- )),
- )
- .into_response(),
- Err(e) => {
- tracing::error!("Failed to update directive: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Archive a directive
-/// DELETE /api/v1/directives/:id
-pub async fn archive_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::archive_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 archive directive: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-// =============================================================================
-// Directive Lifecycle
-// =============================================================================
-
-/// Start a directive (generate chain and begin execution)
-/// POST /api/v1/directives/:id/start
-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();
- };
-
- // Verify ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- // Start directive via orchestration engine
- let engine = crate::orchestration::DirectiveEngine::new(pool.clone());
- match engine.start_directive(id).await {
- Ok(planning) => {
- // Auto-start the planning task on an available daemon
- if let Some(daemon_id) = state.find_alternative_daemon(auth.owner_id, &[]) {
- // Update task status to "starting" and assign daemon
- let update_req = crate::db::models::UpdateTaskRequest {
- status: Some("starting".to_string()),
- daemon_id: Some(daemon_id),
- ..Default::default()
- };
- if let Err(e) = repository::update_task_for_owner(
- pool,
- planning.task_id,
- auth.owner_id,
- update_req,
- )
- .await
- {
- tracing::warn!("Failed to update planning task status: {}", e);
- }
-
- let command = crate::server::state::DaemonCommand::SpawnTask {
- task_id: planning.task_id,
- task_name: planning.task_name,
- plan: planning.plan,
- repo_url: planning.repository_url,
- base_branch: planning.base_branch,
- target_branch: None,
- parent_task_id: None,
- depth: 0,
- is_orchestrator: false,
- target_repo_path: None,
- completion_action: Some("none".to_string()),
- continue_from_task_id: None,
- copy_files: None,
- contract_id: Some(planning.contract_id),
- is_supervisor: true,
- autonomous_loop: false,
- resume_session: false,
- conversation_history: None,
- patch_data: None,
- patch_base_sha: None,
- local_only: false,
- auto_merge_local: false,
- supervisor_worktree_task_id: None,
- };
-
- if let Err(e) = state.send_daemon_command(daemon_id, command).await {
- tracing::warn!(
- "Failed to auto-start planning task on daemon {}: {}",
- daemon_id,
- e
- );
- } else {
- tracing::info!(
- "Auto-started planning task {} on daemon {} for directive {}",
- planning.task_id,
- daemon_id,
- id
- );
- }
- } else {
- tracing::warn!(
- "No daemon available to auto-start planning task for directive {}",
- id
- );
- }
-
- // Return the updated directive with progress
- match repository::get_directive_with_progress(pool, id, auth.owner_id).await {
- Ok(Some(directive)) => Json(directive).into_response(),
- Ok(None) => (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Directive not found")),
- )
- .into_response(),
- Err(e) => (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response(),
- }
- }
- Err(e) => {
- tracing::error!("Failed to start directive: {}", e);
- (
- StatusCode::BAD_REQUEST,
- Json(ApiError::new("START_FAILED", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Pause a directive
-/// POST /api/v1/directives/:id/pause
-pub async fn pause_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();
- };
-
- // Verify ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- let engine = crate::orchestration::DirectiveEngine::new(pool.clone());
- match engine.pause_directive(id).await {
- Ok(()) => match repository::get_directive(pool, id).await {
- Ok(Some(directive)) => Json(directive).into_response(),
- Ok(None) => (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Directive not found")),
- )
- .into_response(),
- Err(e) => (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response(),
- },
- Err(e) => {
- tracing::error!("Failed to pause directive: {}", e);
- (
- StatusCode::BAD_REQUEST,
- Json(ApiError::new("PAUSE_FAILED", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Resume a paused directive
-/// POST /api/v1/directives/:id/resume
-pub async fn resume_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();
- };
-
- // Verify ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- let engine = crate::orchestration::DirectiveEngine::new(pool.clone());
- match engine.resume_directive(id).await {
- Ok(()) => match repository::get_directive_with_progress(pool, id, auth.owner_id).await {
- Ok(Some(directive)) => Json(directive).into_response(),
- Ok(None) => (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Directive not found")),
- )
- .into_response(),
- Err(e) => (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response(),
- },
- Err(e) => {
- tracing::error!("Failed to resume directive: {}", e);
- (
- StatusCode::BAD_REQUEST,
- Json(ApiError::new("RESUME_FAILED", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Stop a directive (cannot be resumed)
-/// POST /api/v1/directives/:id/stop
-pub async fn stop_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();
- };
-
- // Verify ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- let engine = crate::orchestration::DirectiveEngine::new(pool.clone());
- match engine.stop_directive(id).await {
- Ok(()) => match repository::get_directive(pool, id).await {
- Ok(Some(directive)) => Json(directive).into_response(),
- Ok(None) => (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Directive not found")),
- )
- .into_response(),
- Err(e) => (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response(),
- },
- Err(e) => {
- tracing::error!("Failed to stop directive: {}", e);
- (
- StatusCode::BAD_REQUEST,
- Json(ApiError::new("STOP_FAILED", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-// =============================================================================
-// Chain Management
-// =============================================================================
-
-/// Get current chain for a directive
-/// GET /api/v1/directives/:id/chain
-pub async fn get_chain(
- 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 ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- match repository::get_current_chain(pool, id).await {
- Ok(Some(chain)) => {
- match repository::list_chain_steps(pool, chain.id).await {
- Ok(steps) => Json(serde_json::json!({
- "chain": chain,
- "steps": steps,
- }))
- .into_response(),
- Err(e) => (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response(),
- }
- }
- Ok(None) => (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "No active chain")),
- )
- .into_response(),
- Err(e) => {
- tracing::error!("Failed to get chain: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Get chain graph for DAG visualization
-/// GET /api/v1/directives/:id/chain/graph
-pub async fn get_chain_graph(
- 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 ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- // Get current chain
- let chain = match repository::get_current_chain(pool, id).await {
- Ok(Some(chain)) => chain,
- Ok(None) => {
- return (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "No active chain")),
- )
- .into_response()
- }
- Err(e) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- };
-
- match repository::get_chain_graph(pool, chain.id).await {
- Ok(graph) => Json(graph).into_response(),
- Err(e) => {
- tracing::error!("Failed to get chain graph: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Regenerate chain (force replan)
-/// POST /api/v1/directives/:id/chain/replan
-pub async fn replan_chain(
- 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 ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- let engine = crate::orchestration::DirectiveEngine::new(pool.clone());
- match engine.regenerate_chain(id, "Manual replan requested").await {
- Ok(new_chain_id) => Json(serde_json::json!({
- "chainId": new_chain_id,
- "message": "Chain regenerated successfully",
- }))
- .into_response(),
- Err(e) => {
- tracing::error!("Failed to replan chain: {}", e);
- (
- StatusCode::BAD_REQUEST,
- Json(ApiError::new("REPLAN_FAILED", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-// =============================================================================
-// Step Management
-// =============================================================================
-
-/// Add a step to the current chain
-/// POST /api/v1/directives/:id/chain/steps
-pub async fn add_step(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path(id): Path<Uuid>,
- Json(req): Json<AddStepRequest>,
-) -> 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 ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- // Get current chain
- let chain = match repository::get_current_chain(pool, id).await {
- Ok(Some(chain)) => chain,
- Ok(None) => {
- return (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "No active chain")),
- )
- .into_response()
- }
- Err(e) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- };
-
- match repository::create_chain_step(pool, chain.id, req).await {
- Ok(step) => (StatusCode::CREATED, Json(step)).into_response(),
- Err(e) => {
- tracing::error!("Failed to add step: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Get step details
-/// GET /api/v1/directives/:id/chain/steps/:step_id
-pub async fn get_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();
- };
-
- // Verify ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- match repository::get_chain_step(pool, step_id).await {
- Ok(Some(step)) => Json(step).into_response(),
- Ok(None) => (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Step not found")),
- )
- .into_response(),
- Err(e) => {
- tracing::error!("Failed to get step: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Update a step
-/// PUT /api/v1/directives/:id/chain/steps/:step_id
-pub async fn update_step(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path((id, step_id)): Path<(Uuid, Uuid)>,
- Json(req): Json<UpdateStepRequest>,
-) -> 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 ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- match repository::update_chain_step(pool, step_id, req).await {
- Ok(step) => Json(step).into_response(),
- Err(e) => {
- tracing::error!("Failed to update step: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Delete a step
-/// DELETE /api/v1/directives/:id/chain/steps/:step_id
-pub async fn delete_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();
- };
-
- // Verify ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- match repository::delete_chain_step(pool, step_id).await {
- Ok(true) => StatusCode::NO_CONTENT.into_response(),
- Ok(false) => (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Step not found")),
- )
- .into_response(),
- Err(e) => {
- tracing::error!("Failed to delete step: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Skip a step
-/// POST /api/v1/directives/:id/chain/steps/:step_id/skip
-pub async fn skip_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();
- };
-
- // Verify ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- match repository::update_step_status(pool, step_id, "skipped").await {
- Ok(step) => Json(step).into_response(),
- Err(e) => {
- tracing::error!("Failed to skip step: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-// =============================================================================
-// Evaluations
-// =============================================================================
-
-/// List evaluations for a directive
-/// GET /api/v1/directives/:id/evaluations
-pub async fn list_evaluations(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path(id): Path<Uuid>,
- Query(params): Query<ListEvaluationsQuery>,
-) -> 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 ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- let result = if let Some(step_id) = params.step_id {
- repository::list_step_evaluations(pool, step_id).await
- } else {
- repository::list_directive_evaluations(pool, id, params.limit).await
- };
-
- match result {
- Ok(evaluations) => Json(evaluations).into_response(),
- Err(e) => {
- tracing::error!("Failed to list evaluations: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-// =============================================================================
-// Events
-// =============================================================================
-
-/// List events for a directive
-/// GET /api/v1/directives/:id/events
-pub async fn list_events(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path(id): Path<Uuid>,
- Query(params): Query<ListEventsQuery>,
-) -> 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 ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- match repository::list_directive_events(pool, id, params.limit).await {
- Ok(events) => Json(events).into_response(),
- Err(e) => {
- tracing::error!("Failed to list events: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// SSE stream of events for a directive
-/// GET /api/v1/directives/:id/events/stream
-///
-/// EventSource API cannot set custom headers, so authentication is accepted
-/// via query parameters: ?token=<jwt> or ?api_key=<key>
-pub async fn stream_events(
- State(state): State<SharedState>,
- Path(id): Path<Uuid>,
- Query(auth_params): Query<StreamAuthQuery>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- .into_response();
- };
-
- // Authenticate via query params (EventSource cannot set headers)
- let auth = if let Some(ref token) = auth_params.token {
- // JWT token
- let verifier = match state.jwt_verifier.as_ref() {
- Some(v) => v,
- None => {
- return (
- StatusCode::UNAUTHORIZED,
- Json(ApiError::new("AUTH_NOT_CONFIGURED", "Authentication not configured")),
- )
- .into_response()
- }
- };
- let claims = match verifier.verify(token) {
- Ok(c) => c,
- Err(_) => {
- return (
- StatusCode::UNAUTHORIZED,
- Json(ApiError::new("INVALID_TOKEN", "Invalid authentication token")),
- )
- .into_response()
- }
- };
- match crate::server::auth::resolve_owner_id_public(pool, claims.sub, claims.email.as_deref()).await {
- Ok(owner_id) => crate::server::auth::AuthenticatedUser {
- user_id: claims.sub,
- owner_id,
- auth_source: crate::server::auth::AuthSource::Jwt,
- email: claims.email,
- },
- Err(_) => {
- return (
- StatusCode::UNAUTHORIZED,
- Json(ApiError::new("USER_NOT_FOUND", "User not found")),
- )
- .into_response()
- }
- }
- } else if let Some(ref api_key) = auth_params.api_key {
- // API key
- match crate::server::auth::validate_api_key_public(pool, api_key).await {
- Ok((user_id, owner_id)) => crate::server::auth::AuthenticatedUser {
- user_id,
- owner_id,
- auth_source: crate::server::auth::AuthSource::ApiKey,
- email: None,
- },
- Err(_) => {
- return (
- StatusCode::UNAUTHORIZED,
- Json(ApiError::new("INVALID_API_KEY", "Invalid or revoked API key")),
- )
- .into_response()
- }
- }
- } else {
- return (
- StatusCode::UNAUTHORIZED,
- Json(ApiError::new("MISSING_TOKEN", "Authentication required via ?token= or ?api_key= query parameter")),
- )
- .into_response();
- };
-
- // Verify ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- // Create SSE stream that polls for new events
- let pool_clone = pool.clone();
- let stream = stream::unfold(
- (pool_clone, id, None::<chrono::DateTime<chrono::Utc>>),
- move |(pool, directive_id, last_seen)| async move {
- // Wait a bit before next poll
- tokio::time::sleep(Duration::from_secs(1)).await;
-
- // Get recent events
- let events = repository::list_directive_events(&pool, directive_id, Some(10))
- .await
- .unwrap_or_default();
-
- // Filter to only new events
- let new_events: Vec<_> = events
- .into_iter()
- .filter(|e| last_seen.map(|ls| e.created_at > ls).unwrap_or(true))
- .collect();
-
- let new_last_seen = new_events.first().map(|e| e.created_at).or(last_seen);
-
- // Convert to SSE events
- let sse_events: Vec<Result<Event, Infallible>> = new_events
- .into_iter()
- .map(|e| {
- Ok(Event::default()
- .event("directive_event")
- .data(serde_json::to_string(&e).unwrap_or_default()))
- })
- .collect();
-
- Some((stream::iter(sse_events), (pool, directive_id, new_last_seen)))
- },
- );
-
- use futures::StreamExt;
- Sse::new(stream.flatten())
- .keep_alive(
- axum::response::sse::KeepAlive::new()
- .interval(Duration::from_secs(15))
- .text("keepalive"),
- )
- .into_response()
-}
-
-// =============================================================================
-// Verifiers
-// =============================================================================
-
-/// List verifiers for a directive
-/// GET /api/v1/directives/:id/verifiers
-pub async fn list_verifiers(
- 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 ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- match repository::list_directive_verifiers(pool, id).await {
- Ok(verifiers) => Json(verifiers).into_response(),
- Err(e) => {
- tracing::error!("Failed to list verifiers: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Add a verifier to a directive
-/// POST /api/v1/directives/:id/verifiers
-pub async fn add_verifier(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path(id): Path<Uuid>,
- Json(req): Json<CreateVerifierRequest>,
-) -> 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 ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- match repository::create_directive_verifier(
- pool,
- id,
- &req.name,
- &req.verifier_type,
- req.command.as_deref(),
- req.working_directory.as_deref(),
- false, // auto_detect
- vec![], // detect_files
- req.weight.unwrap_or(1.0),
- req.required.unwrap_or(false),
- )
- .await
- {
- Ok(verifier) => (StatusCode::CREATED, Json(verifier)).into_response(),
- Err(e) => {
- tracing::error!("Failed to add verifier: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Update a verifier
-/// PUT /api/v1/directives/:id/verifiers/:verifier_id
-pub async fn update_verifier(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path((id, verifier_id)): Path<(Uuid, Uuid)>,
- Json(req): Json<UpdateVerifierRequest>,
-) -> 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 ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- match repository::update_directive_verifier(
- pool,
- verifier_id,
- req.enabled,
- req.command.as_deref(),
- req.weight,
- req.required,
- )
- .await
- {
- Ok(verifier) => Json(verifier).into_response(),
- Err(e) => {
- tracing::error!("Failed to update verifier: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-// =============================================================================
-// Approvals
-// =============================================================================
-
-/// List pending approvals for a directive
-/// GET /api/v1/directives/:id/approvals
-pub async fn list_approvals(
- 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 ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- match repository::list_pending_approvals(pool, id).await {
- Ok(approvals) => Json(approvals).into_response(),
- Err(e) => {
- tracing::error!("Failed to list approvals: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Approve a pending approval request
-/// POST /api/v1/directives/:id/approvals/:approval_id/approve
-pub async fn approve_request(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path((id, approval_id)): Path<(Uuid, Uuid)>,
- Json(req): Json<ApprovalActionRequest>,
-) -> 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 ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- let engine = crate::orchestration::DirectiveEngine::new(pool.clone());
- match engine
- .on_approval_resolved(approval_id, true, auth.owner_id)
- .await
- {
- Ok(()) => {
- match repository::resolve_approval(
- pool,
- approval_id,
- "approved",
- req.response.as_deref(),
- auth.owner_id,
- )
- .await
- {
- Ok(approval) => Json(approval).into_response(),
- Err(e) => (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response(),
- }
- }
- Err(e) => {
- tracing::error!("Failed to process approval: {}", e);
- (
- StatusCode::BAD_REQUEST,
- Json(ApiError::new("APPROVAL_FAILED", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Deny a pending approval request
-/// POST /api/v1/directives/:id/approvals/:approval_id/deny
-pub async fn deny_request(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path((id, approval_id)): Path<(Uuid, Uuid)>,
- Json(req): Json<ApprovalActionRequest>,
-) -> 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 ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- let engine = crate::orchestration::DirectiveEngine::new(pool.clone());
- match engine
- .on_approval_resolved(approval_id, false, auth.owner_id)
- .await
- {
- Ok(()) => {
- match repository::resolve_approval(
- pool,
- approval_id,
- "denied",
- req.response.as_deref(),
- auth.owner_id,
- )
- .await
- {
- Ok(approval) => Json(approval).into_response(),
- Err(e) => (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response(),
- }
- }
- Err(e) => {
- tracing::error!("Failed to process denial: {}", e);
- (
- StatusCode::BAD_REQUEST,
- Json(ApiError::new("DENIAL_FAILED", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-// =============================================================================
-// Step Evaluation & Rework
-// =============================================================================
-
-/// Force re-evaluation of a step
-/// POST /api/v1/directives/:id/steps/:step_id/evaluate
-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();
- };
-
- // Verify ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- // Set step to evaluating status
- match repository::update_step_status(pool, step_id, "evaluating").await {
- Ok(_) => {}
- Err(e) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- // Trigger evaluation via engine
- let engine = crate::orchestration::DirectiveEngine::new(pool.clone());
- match engine.on_contract_completed(step_id).await {
- Ok(()) => {
- // Return updated step
- match repository::get_chain_step(pool, step_id).await {
- Ok(Some(step)) => Json(step).into_response(),
- Ok(None) => (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Step not found")),
- )
- .into_response(),
- Err(e) => (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response(),
- }
- }
- Err(e) => {
- tracing::error!("Failed to evaluate step: {}", e);
- (
- StatusCode::BAD_REQUEST,
- Json(ApiError::new("EVALUATE_FAILED", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Trigger manual rework for a step
-/// POST /api/v1/directives/:id/steps/:step_id/rework
-pub async fn rework_step(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path((id, step_id)): Path<(Uuid, Uuid)>,
- Json(req): Json<ReworkStepRequest>,
-) -> 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 ownership
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- // Set step to rework status and increment rework count
- match repository::update_step_status(pool, step_id, "rework").await {
- Ok(_) => {}
- Err(e) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-
- let _ = repository::increment_step_rework_count(pool, step_id).await;
-
- // Emit rework event
- let _ = repository::emit_directive_event(
- pool,
- id,
- None,
- Some(step_id),
- "step_rework",
- "info",
- Some(serde_json::json!({
- "step_id": step_id,
- "instructions": req.instructions,
- "initiated_by": "user",
- })),
- "user",
- Some(auth.owner_id),
- )
- .await;
-
- // Return updated step
- match repository::get_chain_step(pool, step_id).await {
- Ok(Some(step)) => Json(step).into_response(),
- Ok(None) => (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Step not found")),
- )
- .into_response(),
- Err(e) => (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response(),
- }
-}
-
-// =============================================================================
-// Auto-detect Verifiers
-// =============================================================================
-
-/// Auto-detect verifiers for a directive based on repository content
-/// POST /api/v1/directives/:id/verifiers/auto-detect
-pub async fn auto_detect_verifiers(
- 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();
- };
-
- // Get directive with ownership check
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- };
-
- // Get repository path
- let repo_path = directive
- .local_path
- .as_ref()
- .map(std::path::PathBuf::from)
- .unwrap_or_else(|| std::path::PathBuf::from("."));
-
- // Auto-detect verifiers
- let detected = crate::orchestration::auto_detect_verifiers(&repo_path).await;
-
- // Save detected verifiers to the database
- let mut created = Vec::new();
- for verifier in &detected {
- let info = verifier.info();
- match repository::create_directive_verifier(
- pool,
- id,
- &info.name,
- &info.verifier_type,
- Some(&info.command),
- info.working_directory.as_deref(),
- true, // auto_detect
- info.detect_files.clone(),
- info.weight,
- info.required,
- )
- .await
- {
- Ok(v) => created.push(v),
- Err(e) => {
- tracing::warn!("Failed to create detected verifier '{}': {}", info.name, e);
- }
- }
- }
-
- Json(serde_json::json!({
- "detected": created.len(),
- "verifiers": created,
- }))
- .into_response()
-}
-
-// =============================================================================
-// Requirements & Criteria
-// =============================================================================
-
-/// Update directive requirements
-/// PUT /api/v1/directives/:id/requirements
-pub async fn update_requirements(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path(id): Path<Uuid>,
- Json(req): Json<UpdateRequirementsRequest>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- .into_response();
- };
-
- // Get directive with ownership check to get current version
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- };
-
- // Build update request with just requirements
- let update = UpdateDirectiveRequest {
- title: None,
- goal: None,
- requirements: Some(serde_json::to_value(&req.requirements).unwrap_or_default()),
- acceptance_criteria: None,
- constraints: None,
- external_dependencies: None,
- autonomy_level: None,
- confidence_threshold_green: None,
- confidence_threshold_yellow: None,
- max_total_cost_usd: None,
- max_wall_time_minutes: None,
- max_rework_cycles: None,
- max_chain_regenerations: None,
- version: directive.version,
- };
-
- match repository::update_directive_for_owner(pool, id, auth.owner_id, update).await {
- Ok(directive) => Json(directive).into_response(),
- Err(repository::RepositoryError::VersionConflict { expected, actual }) => (
- StatusCode::CONFLICT,
- Json(ApiError::new(
- "VERSION_CONFLICT",
- &format!("Version conflict: expected {}, got {}", expected, actual),
- )),
- )
- .into_response(),
- Err(e) => {
- tracing::error!("Failed to update requirements: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Update directive acceptance criteria
-/// PUT /api/v1/directives/:id/criteria
-pub async fn update_criteria(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path(id): Path<Uuid>,
- Json(req): Json<UpdateCriteriaRequest>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- .into_response();
- };
-
- // Get directive with ownership check to get current version
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- };
-
- // Build update request with just acceptance criteria
- let update = UpdateDirectiveRequest {
- title: None,
- goal: None,
- requirements: None,
- acceptance_criteria: Some(
- serde_json::to_value(&req.acceptance_criteria).unwrap_or_default(),
- ),
- constraints: None,
- external_dependencies: None,
- autonomy_level: None,
- confidence_threshold_green: None,
- confidence_threshold_yellow: None,
- max_total_cost_usd: None,
- max_wall_time_minutes: None,
- max_rework_cycles: None,
- max_chain_regenerations: None,
- version: directive.version,
- };
-
- match repository::update_directive_for_owner(pool, id, auth.owner_id, update).await {
- Ok(directive) => Json(directive).into_response(),
- Err(repository::RepositoryError::VersionConflict { expected, actual }) => (
- StatusCode::CONFLICT,
- Json(ApiError::new(
- "VERSION_CONFLICT",
- &format!("Version conflict: expected {}, got {}", expected, actual),
- )),
- )
- .into_response(),
- Err(e) => {
- tracing::error!("Failed to update criteria: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-// =============================================================================
-// Spec Generation
-// =============================================================================
-
-/// Generate a specification from the directive's goal using LLM
-/// POST /api/v1/directives/:id/generate-spec
-pub async fn generate_spec(
- 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();
- };
-
- // Get directive with ownership check
- 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) => {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- };
-
- // Use the planner to generate a spec from the goal
- let planner = crate::orchestration::ChainPlanner::new(pool.clone());
- match planner.generate_spec(&directive).await {
- Ok(spec) => {
- // Update the directive with the generated spec
- let update = UpdateDirectiveRequest {
- title: spec.title,
- goal: None,
- requirements: Some(spec.requirements),
- acceptance_criteria: Some(spec.acceptance_criteria),
- constraints: spec.constraints,
- external_dependencies: None,
- autonomy_level: None,
- confidence_threshold_green: None,
- confidence_threshold_yellow: None,
- max_total_cost_usd: None,
- max_wall_time_minutes: None,
- max_rework_cycles: None,
- max_chain_regenerations: None,
- version: directive.version,
- };
-
- match repository::update_directive_for_owner(pool, id, auth.owner_id, update).await {
- Ok(updated) => Json(updated).into_response(),
- Err(e) => {
- tracing::error!("Failed to save generated spec: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", &e.to_string())),
- )
- .into_response()
- }
- }
- }
- Err(e) => {
- tracing::error!("Failed to generate spec: {}", e);
- (
- StatusCode::BAD_REQUEST,
- Json(ApiError::new("SPEC_GENERATION_FAILED", &e.to_string())),
- )
- .into_response()
- }
- }
-}
diff --git a/makima/src/server/handlers/mesh_daemon.rs b/makima/src/server/handlers/mesh_daemon.rs
index 9938145..beb4c15 100644
--- a/makima/src/server/handlers/mesh_daemon.rs
+++ b/makima/src/server/handlers/mesh_daemon.rs
@@ -1303,20 +1303,7 @@ async fn handle_daemon_connection(socket: WebSocket, state: SharedState, auth_re
}),
).await;
- // Check if this task's contract is a directive orchestrator
- if let Some(contract_id) = updated_task.contract_id {
- if let Ok(Some(directive)) = repository::get_directive_by_orchestrator_contract_id(
- &pool, contract_id
- ).await {
- let engine = crate::orchestration::DirectiveEngine::new(pool.clone());
- if let Err(e) = engine.on_planning_complete(directive.id, success).await {
- tracing::error!(
- "Failed to handle planning completion for directive {}: {}",
- directive.id, e
- );
- }
- }
- }
+ // TODO: Directive engine integration (removed for reimplementation)
}
Ok(None) => {
tracing::warn!(
diff --git a/makima/src/server/handlers/mod.rs b/makima/src/server/handlers/mod.rs
index d3fabf7..ae370c9 100644
--- a/makima/src/server/handlers/mod.rs
+++ b/makima/src/server/handlers/mod.rs
@@ -1,8 +1,6 @@
//! HTTP and WebSocket request handlers.
pub mod api_keys;
-// pub mod chains; // Removed - replaced by directives
-pub mod directives;
pub mod chat;
pub mod contract_chat;
pub mod contract_daemon;
diff --git a/makima/src/server/mod.rs b/makima/src/server/mod.rs
index 463a5f5..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;
@@ -214,61 +214,6 @@ pub fn make_router(state: SharedState) -> Router {
)
// Timeline endpoint (unified history for user)
.route("/timeline", get(history::get_timeline))
- // Directive endpoints (replacement for chains)
- .route(
- "/directives",
- get(directives::list_directives).post(directives::create_directive),
- )
- .route(
- "/directives/{id}",
- get(directives::get_directive)
- .put(directives::update_directive)
- .delete(directives::archive_directive),
- )
- .route("/directives/{id}/start", post(directives::start_directive))
- .route("/directives/{id}/pause", post(directives::pause_directive))
- .route("/directives/{id}/resume", post(directives::resume_directive))
- .route("/directives/{id}/stop", post(directives::stop_directive))
- .route("/directives/{id}/requirements", axum::routing::put(directives::update_requirements))
- .route("/directives/{id}/criteria", axum::routing::put(directives::update_criteria))
- .route("/directives/{id}/generate-spec", post(directives::generate_spec))
- // Directive chain management
- .route("/directives/{id}/chain", get(directives::get_chain))
- .route("/directives/{id}/chain/graph", get(directives::get_chain_graph))
- .route("/directives/{id}/chain/replan", post(directives::replan_chain))
- // Directive step management
- .route(
- "/directives/{id}/chain/steps",
- post(directives::add_step),
- )
- .route(
- "/directives/{id}/chain/steps/{step_id}",
- get(directives::get_step)
- .put(directives::update_step)
- .delete(directives::delete_step),
- )
- .route("/directives/{id}/chain/steps/{step_id}/skip", post(directives::skip_step))
- .route("/directives/{id}/chain/steps/{step_id}/evaluate", post(directives::evaluate_step))
- .route("/directives/{id}/chain/steps/{step_id}/rework", post(directives::rework_step))
- // Directive evaluations
- .route("/directives/{id}/evaluations", get(directives::list_evaluations))
- // Directive events
- .route("/directives/{id}/events", get(directives::list_events))
- .route("/directives/{id}/events/stream", get(directives::stream_events))
- // Directive verifiers
- .route("/directives/{id}/verifiers/auto-detect", post(directives::auto_detect_verifiers))
- .route(
- "/directives/{id}/verifiers",
- get(directives::list_verifiers).post(directives::add_verifier),
- )
- .route(
- "/directives/{id}/verifiers/{verifier_id}",
- axum::routing::put(directives::update_verifier),
- )
- // Directive approvals
- .route("/directives/{id}/approvals", get(directives::list_approvals))
- .route("/directives/{id}/approvals/{approval_id}/approve", post(directives::approve_request))
- .route("/directives/{id}/approvals/{approval_id}/deny", post(directives::deny_request))
// Contract type templates (built-in only)
.route("/contract-types", get(templates::list_contract_types))
// Settings endpoints