From 3662b334dfd68cfdf00ed44ae88927c2e1b2aabe Mon Sep 17 00:00:00 2001 From: soryu Date: Sun, 8 Feb 2026 21:07:30 +0000 Subject: Remove directive mechanism --- makima/frontend/src/components/NavStrip.tsx | 1 - .../directives/DirectiveContractsTab.tsx | 148 -- .../src/components/directives/DirectiveDetail.tsx | 425 ----- .../src/components/directives/DirectiveList.tsx | 143 -- .../src/components/directives/StepDiagram.tsx | 313 ---- makima/frontend/src/hooks/useDirectives.ts | 127 -- makima/frontend/src/lib/api.ts | 213 --- makima/frontend/src/main.tsx | 17 - makima/frontend/src/routes/directives.tsx | 403 ----- .../20260209000000_remove_directive_system.sql | 18 + makima/src/bin/makima.rs | 93 +- makima/src/daemon/api/directive.rs | 106 -- makima/src/daemon/api/mod.rs | 1 - makima/src/daemon/cli/directive.rs | 60 - makima/src/daemon/cli/mod.rs | 40 - makima/src/daemon/skills/directive.md | 88 - makima/src/daemon/skills/mod.rs | 4 - makima/src/db/models.rs | 333 ---- makima/src/db/repository.rs | 888 +---------- makima/src/orchestration/directive.rs | 1685 -------------------- makima/src/orchestration/mod.rs | 2 +- makima/src/server/handlers/contracts.rs | 18 - makima/src/server/handlers/directives.rs | 785 --------- makima/src/server/handlers/mesh_daemon.rs | 10 - makima/src/server/handlers/mod.rs | 1 - makima/src/server/mod.rs | 19 +- makima/src/server/openapi.rs | 44 +- 27 files changed, 35 insertions(+), 5950 deletions(-) delete mode 100644 makima/frontend/src/components/directives/DirectiveContractsTab.tsx delete mode 100644 makima/frontend/src/components/directives/DirectiveDetail.tsx delete mode 100644 makima/frontend/src/components/directives/DirectiveList.tsx delete mode 100644 makima/frontend/src/components/directives/StepDiagram.tsx delete mode 100644 makima/frontend/src/hooks/useDirectives.ts delete mode 100644 makima/frontend/src/routes/directives.tsx create mode 100644 makima/migrations/20260209000000_remove_directive_system.sql delete mode 100644 makima/src/daemon/api/directive.rs delete mode 100644 makima/src/daemon/cli/directive.rs delete mode 100644 makima/src/daemon/skills/directive.md delete mode 100644 makima/src/orchestration/directive.rs delete mode 100644 makima/src/server/handlers/directives.rs diff --git a/makima/frontend/src/components/NavStrip.tsx b/makima/frontend/src/components/NavStrip.tsx index f7e67db..fb95c7f 100644 --- a/makima/frontend/src/components/NavStrip.tsx +++ b/makima/frontend/src/components/NavStrip.tsx @@ -11,7 +11,6 @@ interface NavLink { const NAV_LINKS: NavLink[] = [ { label: "Listen", href: "/listen" }, { label: "Contracts", href: "/contracts", requiresAuth: true }, - { label: "Directives", href: "/directives", requiresAuth: true }, { label: "Board", href: "/workflow", requiresAuth: true }, { label: "Mesh", href: "/mesh", requiresAuth: true }, { label: "History", href: "/history", requiresAuth: true }, diff --git a/makima/frontend/src/components/directives/DirectiveContractsTab.tsx b/makima/frontend/src/components/directives/DirectiveContractsTab.tsx deleted file mode 100644 index 28da117..0000000 --- a/makima/frontend/src/components/directives/DirectiveContractsTab.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import { useNavigate } from "react-router"; -import type { - DirectiveWithChains, - StepContractSummary, - ContractPhase, -} from "../../lib/api"; -import { PhaseProgressBarCompact } from "../contracts/PhaseProgressBar"; - -interface DirectiveContractsTabProps { - directive: DirectiveWithChains; -} - -const statusColors: Record = { - active: "text-green-400", - completed: "text-blue-400", - archived: "text-[#555]", -}; - -function ContractCard({ - summary, - label, -}: { - summary: StepContractSummary; - label: string; -}) { - const navigate = useNavigate(); - - const progressPct = - summary.taskCount > 0 - ? Math.round((summary.tasksDone / summary.taskCount) * 100) - : 0; - - return ( -
navigate(`/contracts/${summary.id}`)} - > -
-
- - {summary.name} - - - {summary.contractType} - -
-
- - {summary.status} - - -
-
- -
- - {label} - - -
- - {/* Task progress bar */} -
-
-
-
- - {summary.tasksDone}/{summary.taskCount} tasks - -
-
- ); -} - -export function DirectiveContractsTab({ - directive, -}: DirectiveContractsTabProps) { - // Collect all contract summaries - const contracts: { summary: StepContractSummary; label: string }[] = []; - - if (directive.orchestratorContractSummary) { - contracts.push({ - summary: directive.orchestratorContractSummary, - label: "Planning", - }); - } - - for (const chain of directive.chains) { - for (const step of chain.steps) { - if (step.contractSummary) { - contracts.push({ - summary: step.contractSummary, - label: step.name, - }); - } - // Show monitoring/evaluation contracts - if (step.monitoringContractId) { - contracts.push({ - summary: { - id: step.monitoringContractId, - name: `${step.name} - Evaluation`, - contractType: "monitoring", - status: step.status === "evaluating" ? "active" : "completed", - phase: "plan", - taskCount: 1, - tasksDone: step.status === "evaluating" ? 0 : 1, - tasksRunning: step.status === "evaluating" ? 1 : 0, - tasksFailed: 0, - }, - label: `${step.name} eval`, - }); - } - } - } - - if (contracts.length === 0) { - return ( -
-

- {directive.status === "draft" - ? "No contracts yet. Start the directive to begin planning." - : directive.status === "planning" - ? "Planning in progress... contracts will appear when steps are created." - : "No contracts associated with this directive."} -

-
- ); - } - - return ( -
- {contracts.map((c) => ( - - ))} -
- ); -} diff --git a/makima/frontend/src/components/directives/DirectiveDetail.tsx b/makima/frontend/src/components/directives/DirectiveDetail.tsx deleted file mode 100644 index 6bdf5aa..0000000 --- a/makima/frontend/src/components/directives/DirectiveDetail.tsx +++ /dev/null @@ -1,425 +0,0 @@ -import { useState, useEffect, useRef } from "react"; -import { useNavigate } from "react-router"; -import type { - DirectiveWithChains, - DirectiveStatus, - ContractPhase, -} from "../../lib/api"; -import { getDirective } from "../../lib/api"; -import { PhaseProgressBarCompact } from "../contracts/PhaseProgressBar"; -import { StepDiagram } from "./StepDiagram"; -import { DirectiveContractsTab } from "./DirectiveContractsTab"; - -interface DirectiveDetailProps { - directive: DirectiveWithChains; - onBack: () => void; - onDelete?: (id: string) => void; - onStart?: (id: string) => void; - onRefresh?: (updated: DirectiveWithChains) => void; -} - -type Tab = "overview" | "chain" | "contracts"; - -const statusColors: Record = { - draft: "text-[#888]", - planning: "text-yellow-400", - active: "text-green-400", - paused: "text-orange-400", - completed: "text-blue-400", - archived: "text-[#555]", - failed: "text-red-400", -}; - -function JsonSection({ - label, - data, -}: { - label: string; - data: unknown[] | unknown; -}) { - const items = Array.isArray(data) ? data : []; - if (items.length === 0) return null; - - return ( -
-

- {label} -

-
- {items.map((item, i) => ( -
- {typeof item === "string" ? item : JSON.stringify(item)} -
- ))} -
-
- ); -} - -export function DirectiveDetail({ - directive, - onBack, - onDelete, - onStart, - onRefresh, -}: DirectiveDetailProps) { - const navigate = useNavigate(); - const [activeTab, setActiveTab] = useState("overview"); - - // Auto-poll when directive is in an active state - const isLive = - directive.status === "planning" || directive.status === "active"; - const intervalRef = useRef | null>(null); - - useEffect(() => { - if (!isLive) { - if (intervalRef.current) { - clearInterval(intervalRef.current); - intervalRef.current = null; - } - return; - } - - intervalRef.current = setInterval(async () => { - try { - const updated = await getDirective(directive.id); - if (updated && onRefresh) { - onRefresh(updated); - } - } catch { - // Ignore poll errors - } - }, 5000); - - return () => { - if (intervalRef.current) { - clearInterval(intervalRef.current); - intervalRef.current = null; - } - }; - }, [isLive, directive.id, onRefresh]); - - // Count total steps and completed steps across all chains - const totalSteps = directive.chains.reduce( - (sum, c) => sum + c.totalSteps, - 0 - ); - const completedSteps = directive.chains.reduce( - (sum, c) => sum + c.completedSteps, - 0 - ); - - // Count contracts - const contractCount = - (directive.orchestratorContractSummary ? 1 : 0) + - directive.chains.reduce( - (sum, c) => - sum + c.steps.filter((s) => s.contractSummary != null).length, - 0 - ); - - const tabs: { key: Tab; label: string; count?: number }[] = [ - { key: "overview", label: "Overview" }, - { key: "chain", label: "Chain", count: totalSteps }, - { key: "contracts", label: "Contracts", count: contractCount }, - ]; - - return ( -
- {/* Header */} -
-
- -
- {onStart && directive.status === "draft" && ( - - )} - {onDelete && ( - - )} -
-
-
-

- {directive.title} -

- - {directive.status} - - {isLive && ( - - polling - - )} - - v{directive.version} - -
-
- - {/* Tabs */} -
- {tabs.map((tab) => ( - - ))} -
- - {/* Tab content */} -
- {activeTab === "overview" && ( -
- {/* Orchestrator contract link */} - {directive.orchestratorContractId && ( -
- - Planning Contract - - {directive.orchestratorContractSummary && ( - - )} - - {directive.status === "planning" && ( - - planning in progress - - )} -
- )} - - {/* Goal */} -
-

- Goal -

-

- {directive.goal} -

-
- - {/* Config grid */} -
-
- - Autonomy - -
- {directive.autonomyLevel} -
-
-
- - Chains - -
- {directive.chainGenerationCount} generated -
-
-
- - Cost - -
- ${directive.totalCostUsd.toFixed(2)} -
-
- {directive.repositoryUrl && ( -
- - Repository - -
- {directive.repositoryUrl} -
-
- )} -
- - {/* Stat cards */} -
-
-
- {totalSteps} -
-
- Total Steps -
-
-
-
- {completedSteps} -
-
- Completed -
-
-
-
- ${directive.totalCostUsd.toFixed(2)} -
-
- Cost -
-
-
- - {/* Structured sections */} - - - - - - {/* Metadata */} -
-

- Metadata -

-
- Created - - {new Date(directive.createdAt).toLocaleString()} - - Updated - - {new Date(directive.updatedAt).toLocaleString()} - - {directive.startedAt && ( - <> - Started - - {new Date(directive.startedAt).toLocaleString()} - - - )} - {directive.completedAt && ( - <> - Completed - - {new Date(directive.completedAt).toLocaleString()} - - - )} - Version - {directive.version} -
-
-
- )} - - {activeTab === "chain" && ( -
- {directive.chains.length === 0 ? ( -
-
- {directive.status === "planning" ? "\u2699" : "\u25CB"} -
-

- {directive.status === "planning" - ? "Planning in progress... the chain will appear when the planner submits a plan." - : directive.status === "draft" - ? "No chains yet. Start the directive to begin planning." - : "No chains created for this directive."} -

-
- ) : ( - <> - {/* Chain metadata header */} - {directive.chains.map((chain) => ( -
-
- - {chain.name} - - - gen {chain.generation} - - - {chain.status} - - - {chain.completedSteps}/{chain.totalSteps} steps - {chain.failedSteps > 0 && ( - - ({chain.failedSteps} failed) - - )} - -
- -
- ))} - - )} -
- )} - - {activeTab === "contracts" && ( - - )} -
-
- ); -} diff --git a/makima/frontend/src/components/directives/DirectiveList.tsx b/makima/frontend/src/components/directives/DirectiveList.tsx deleted file mode 100644 index 5afa36e..0000000 --- a/makima/frontend/src/components/directives/DirectiveList.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { useState } from "react"; -import type { DirectiveSummary, DirectiveStatus } from "../../lib/api"; - -interface DirectiveListProps { - directives: DirectiveSummary[]; - loading: boolean; - onSelect: (id: string) => void; - onCreate: () => void; - onDelete?: (directive: DirectiveSummary) => void; - selectedId?: string; -} - -const statusColors: Record = { - draft: "text-[#888]", - planning: "text-yellow-400", - active: "text-green-400", - paused: "text-orange-400", - completed: "text-blue-400", - archived: "text-[#555]", - failed: "text-red-400", -}; - -export function DirectiveList({ - directives, - loading, - onSelect, - onCreate, - selectedId, -}: DirectiveListProps) { - const [filter, setFilter] = useState("all"); - - const filteredDirectives = - filter === "all" - ? directives - : directives.filter((d) => d.status === filter); - - if (loading) { - return ( -
-
Loading...
-
- ); - } - - return ( -
- {/* Header */} -
-
-

- Directives -

- -
- - {/* Filter tabs */} -
- {(["all", "draft", "planning", "active", "paused", "completed", "failed"] as const).map( - (status) => ( - - ) - )} -
-
- - {/* List */} -
- {filteredDirectives.length === 0 ? ( -
-

- {filter === "all" - ? "No directives yet" - : `No ${filter} directives`} -

-
- ) : ( -
- {filteredDirectives.map((directive) => ( - - ))} -
- )} -
-
- ); -} diff --git a/makima/frontend/src/components/directives/StepDiagram.tsx b/makima/frontend/src/components/directives/StepDiagram.tsx deleted file mode 100644 index 33892e0..0000000 --- a/makima/frontend/src/components/directives/StepDiagram.tsx +++ /dev/null @@ -1,313 +0,0 @@ -import { useNavigate } from "react-router"; -import type { ChainStep, ContractPhase } from "../../lib/api"; -import { PhaseProgressBarCompact } from "../contracts/PhaseProgressBar"; - -interface StepDiagramProps { - steps: ChainStep[]; -} - -const statusColors: Record = { - pending: { - border: "border-[#444]", - dot: "bg-[#555]", - bg: "bg-[rgba(40,40,50,0.6)]", - glow: "", - }, - running: { - border: "border-yellow-400/60", - dot: "bg-yellow-400", - bg: "bg-[rgba(80,70,20,0.3)]", - glow: "shadow-[0_0_8px_rgba(250,204,21,0.15)]", - }, - evaluating: { - border: "border-blue-400/60", - dot: "bg-blue-400", - bg: "bg-[rgba(20,50,80,0.3)]", - glow: "shadow-[0_0_8px_rgba(96,165,250,0.15)]", - }, - passed: { - border: "border-green-400/60", - dot: "bg-green-400", - bg: "bg-[rgba(20,60,30,0.3)]", - glow: "", - }, - failed: { - border: "border-red-400/60", - dot: "bg-red-400", - bg: "bg-[rgba(60,20,20,0.3)]", - glow: "", - }, -}; - -const statusLabels: Record = { - pending: "Pending", - ready: "Ready", - running: "Running", - evaluating: "Evaluating", - passed: "Passed", - failed: "Failed", - rework: "Rework", - skipped: "Skipped", - blocked: "Blocked", -}; - -const confidenceColors: Record = { - green: "text-green-400", - yellow: "text-yellow-400", - red: "text-red-400", -}; - -/** - * Assign depth to each step via topological sort based on dependsOn UUIDs. - */ -function assignDepths(steps: ChainStep[]): Map { - const depths = new Map(); - const stepMap = new Map(steps.map((s) => [s.id, s])); - - function getDepth(id: string): number { - if (depths.has(id)) return depths.get(id)!; - const step = stepMap.get(id); - if (!step || !step.dependsOn || step.dependsOn.length === 0) { - depths.set(id, 0); - return 0; - } - const maxParent = Math.max( - ...step.dependsOn.map((depId) => getDepth(depId)) - ); - const d = maxParent + 1; - depths.set(id, d); - return d; - } - - for (const step of steps) { - getDepth(step.id); - } - - return depths; -} - -function StepCard({ step }: { step: ChainStep }) { - const navigate = useNavigate(); - const colors = statusColors[step.status] || statusColors.pending; - const summary = step.contractSummary; - const hasContract = !!step.contractId; - - return ( -
{ - if (hasContract) navigate(`/contracts/${step.contractId}`); - }} - title={hasContract ? "View contract" : undefined} - > - {/* Status header */} -
-
-
- - {step.name} - -
- - {statusLabels[step.status] || step.status} - -
- - {/* Description */} - {step.description && ( -

- {step.description} -

- )} - - {/* Evaluation info */} - {(step.confidenceScore != null || step.evaluationCount > 0 || step.reworkCount > 0) && ( -
-
- {step.confidenceScore != null && ( - - {Math.round(step.confidenceScore * 100)}% confidence - - )} - {step.evaluationCount > 0 && ( - - eval #{step.evaluationCount} - - )} - {step.reworkCount > 0 && ( - - rework x{step.reworkCount} - - )} -
-
- )} - - {/* Monitoring link (when evaluating) */} - {step.status === "evaluating" && step.monitoringContractId && ( -
- { - e.stopPropagation(); - navigate(`/contracts/${step.monitoringContractId}`); - }} - > - evaluation contract → - -
- )} - - {/* Contract progress */} - {summary && ( -
-
- -
-
- - {summary.tasksDone}/{summary.taskCount} tasks - - {summary.tasksRunning > 0 && ( - - {summary.tasksRunning} running - - )} - {summary.tasksFailed > 0 && ( - - {summary.tasksFailed} failed - - )} -
-
- )} - - {/* Contract link arrow */} - {hasContract && !summary && step.status !== "evaluating" && ( -
- - view contract → - -
- )} -
- ); -} - -/** Vertical connector between levels */ -function LevelConnector({ count }: { count: number }) { - return ( -
-
- {Array.from({ length: count }).map((_, i) => ( -
-
-
-
-
- ))} -
-
- ); -} - -export function StepDiagram({ steps }: StepDiagramProps) { - if (steps.length === 0) { - return ( -

No steps to display.

- ); - } - - const depths = assignDepths(steps); - const maxDepth = Math.max(...Array.from(depths.values())); - - // Group steps by depth level - const levels: ChainStep[][] = []; - for (let d = 0; d <= maxDepth; d++) { - levels.push( - steps - .filter((s) => depths.get(s.id) === d) - .sort((a, b) => a.orderIndex - b.orderIndex) - ); - } - - // Compute overall progress - const passedCount = steps.filter(s => s.status === "passed").length; - const failedCount = steps.filter(s => s.status === "failed").length; - const runningCount = steps.filter(s => s.status === "running").length; - const evaluatingCount = steps.filter(s => s.status === "evaluating").length; - - return ( -
- {/* Progress summary */} -
- - {levels.length} level{levels.length !== 1 ? "s" : ""} · {steps.length} step{steps.length !== 1 ? "s" : ""} - - {passedCount > 0 && ( - {passedCount} passed - )} - {runningCount > 0 && ( - {runningCount} running - )} - {evaluatingCount > 0 && ( - {evaluatingCount} evaluating - )} - {failedCount > 0 && ( - {failedCount} failed - )} -
-
-
- - {Math.round((passedCount / steps.length) * 100)}% - -
- - {/* Chain flow */} -
- {levels.map((level, li) => ( -
- {/* Level label */} - {levels.length > 1 && ( -
- - {li === 0 ? "Start" : li === maxDepth ? "Final" : `Level ${li}`} - -
- )} - - {/* Steps at this level */} -
- {level.map((step) => ( - - ))} -
- - {/* Connector to next level */} - {li < maxDepth && ( - - )} -
- ))} -
-
- ); -} diff --git a/makima/frontend/src/hooks/useDirectives.ts b/makima/frontend/src/hooks/useDirectives.ts deleted file mode 100644 index af1c8c6..0000000 --- a/makima/frontend/src/hooks/useDirectives.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { useState, useCallback, useEffect } from "react"; -import { - listDirectives, - getDirective, - createDirective, - updateDirective, - deleteDirective, - startDirective as startDirectiveApi, - type DirectiveSummary, - type DirectiveWithChains, - type CreateDirectiveRequest, - type UpdateDirectiveRequest, -} from "../lib/api"; - -export function useDirectives() { - const [directives, setDirectives] = useState([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - - const fetchDirectives = useCallback(async () => { - setLoading(true); - setError(null); - try { - const response = await listDirectives(); - setDirectives(response.directives); - } catch (e) { - setError(e instanceof Error ? e.message : "Failed to fetch directives"); - } finally { - setLoading(false); - } - }, []); - - const fetchDirective = useCallback( - async (id: string): Promise => { - setError(null); - try { - return await getDirective(id); - } catch (e) { - setError(e instanceof Error ? e.message : "Failed to fetch directive"); - return null; - } - }, - [] - ); - - const saveDirective = useCallback( - async (data: CreateDirectiveRequest): Promise => { - setError(null); - try { - const directive = await createDirective(data); - await fetchDirectives(); - return directive as unknown as DirectiveSummary; - } catch (e) { - setError(e instanceof Error ? e.message : "Failed to create directive"); - return null; - } - }, - [fetchDirectives] - ); - - const editDirective = useCallback( - async ( - id: string, - data: UpdateDirectiveRequest - ): Promise => { - setError(null); - try { - const directive = await updateDirective(id, data); - await fetchDirectives(); - return directive as unknown as DirectiveSummary; - } catch (e) { - setError(e instanceof Error ? e.message : "Failed to update directive"); - return null; - } - }, - [fetchDirectives] - ); - - const removeDirective = useCallback( - async (id: string): Promise => { - setError(null); - try { - await deleteDirective(id); - await fetchDirectives(); - return true; - } catch (e) { - setError(e instanceof Error ? e.message : "Failed to delete directive"); - return false; - } - }, - [fetchDirectives] - ); - - const startDirective = useCallback( - async (id: string): Promise => { - setError(null); - try { - await startDirectiveApi(id); - await fetchDirectives(); - return true; - } catch (e) { - setError( - e instanceof Error ? e.message : "Failed to start directive" - ); - return false; - } - }, - [fetchDirectives] - ); - - // Initial fetch - useEffect(() => { - fetchDirectives(); - }, [fetchDirectives]); - - return { - directives, - loading, - error, - fetchDirectives, - fetchDirective, - saveDirective, - editDirective, - removeDirective, - startDirective, - }; -} diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts index 5080ee1..7732725 100644 --- a/makima/frontend/src/lib/api.ts +++ b/makima/frontend/src/lib/api.ts @@ -3003,217 +3003,4 @@ export async function listTaskPatches(taskId: string, contractId: string): Promi return res.json(); } -// ============================================================================= -// Directive Types & API -// ============================================================================= - -export type DirectiveStatus = "draft" | "planning" | "active" | "paused" | "completed" | "archived" | "failed"; -export type AutonomyLevel = "full_auto" | "guardrails" | "manual"; - -export interface DirectiveSummary { - id: string; - title: string; - goal: string; - status: DirectiveStatus; - autonomyLevel: AutonomyLevel; - chainCount: number; - stepCount: number; - totalCostUsd: number; - version: number; - createdAt: string; - updatedAt: string; -} - -export interface Directive { - id: string; - ownerId: string; - title: string; - goal: string; - requirements: unknown[]; - acceptanceCriteria: unknown[]; - constraints: unknown[]; - externalDependencies: unknown[]; - status: DirectiveStatus; - autonomyLevel: AutonomyLevel; - confidenceThresholdGreen: number; - confidenceThresholdYellow: number; - maxTotalCostUsd: number | null; - maxWallTimeMinutes: number | null; - maxReworkCycles: number | null; - maxChainRegenerations: number | null; - repositoryUrl: string | null; - localPath: string | null; - baseBranch: string | null; - orchestratorContractId: string | null; - currentChainId: string | null; - chainGenerationCount: number; - totalCostUsd: number; - startedAt: string | null; - completedAt: string | null; - version: number; - createdAt: string; - updatedAt: string; -} - -export interface DirectiveChain { - id: string; - directiveId: string; - generation: number; - name: string; - description: string | null; - rationale: string | null; - planningModel: string | null; - status: string; - totalSteps: number; - completedSteps: number; - failedSteps: number; - currentConfidence: number | null; - startedAt: string | null; - completedAt: string | null; - version: number; - createdAt: string; - updatedAt: string; -} - -export interface StepContractSummary { - id: string; - name: string; - contractType: string; - phase: string; - status: string; - taskCount: number; - tasksDone: number; - tasksRunning: number; - tasksFailed: number; -} - -export interface ChainStep { - id: string; - chainId: string; - name: string; - description: string | null; - stepType: string; - contractType: string; - initialPhase: string | null; - taskPlan: string | null; - dependsOn: string[] | null; - status: string; - contractId: string | null; - supervisorTaskId: string | null; - monitoringContractId: string | null; - monitoringTaskId: string | null; - confidenceScore: number | null; - confidenceLevel: string | null; - evaluationCount: number; - reworkCount: number; - lastEvaluationId: string | null; - orderIndex: number; - startedAt: string | null; - completedAt: string | null; - createdAt: string; - contractSummary: StepContractSummary | null; -} - -export interface ChainWithSteps extends DirectiveChain { - steps: ChainStep[]; -} - -export interface DirectiveWithChains extends Directive { - orchestratorContractSummary: StepContractSummary | null; - chains: ChainWithSteps[]; -} - -export interface DirectiveListResponse { - directives: DirectiveSummary[]; - total: number; -} - -export interface CreateDirectiveRequest { - title: string; - goal: string; - requirements?: unknown[]; - acceptanceCriteria?: unknown[]; - constraints?: unknown[]; - externalDependencies?: unknown[]; - autonomyLevel?: AutonomyLevel; - repositoryUrl?: string; - localPath?: string; - baseBranch?: string; -} - -export interface UpdateDirectiveRequest { - title?: string; - goal?: string; - status?: DirectiveStatus; - autonomyLevel?: AutonomyLevel; - version?: number; -} - -export async function listDirectives(): Promise { - const res = await authFetch(`${API_BASE}/api/v1/directives`); - if (!res.ok) { - throw new Error(`Failed to list directives: ${res.statusText}`); - } - return res.json(); -} - -export async function getDirective(id: string): Promise { - const res = await authFetch(`${API_BASE}/api/v1/directives/${id}`); - if (!res.ok) { - throw new Error(`Failed to get directive: ${res.statusText}`); - } - return res.json(); -} - -export async function createDirective( - data: CreateDirectiveRequest -): Promise { - const res = await authFetch(`${API_BASE}/api/v1/directives`, { - method: "POST", - body: JSON.stringify(data), - }); - if (!res.ok) { - throw new Error(`Failed to create directive: ${res.statusText}`); - } - return res.json(); -} - -export async function updateDirective( - id: string, - data: UpdateDirectiveRequest -): Promise { - const res = await authFetch(`${API_BASE}/api/v1/directives/${id}`, { - method: "PUT", - body: JSON.stringify(data), - }); - if (res.status === 409) { - const conflict = (await res.json()) as ConflictErrorResponse; - throw new VersionConflictError(conflict); - } - if (!res.ok) { - throw new Error(`Failed to update directive: ${res.statusText}`); - } - return res.json(); -} - -export async function deleteDirective(id: string): Promise { - const res = await authFetch(`${API_BASE}/api/v1/directives/${id}`, { - method: "DELETE", - }); - if (!res.ok) { - throw new Error(`Failed to delete directive: ${res.statusText}`); - } -} - -export async function startDirective(id: string): Promise { - const res = await authFetch(`${API_BASE}/api/v1/directives/${id}/start`, { - method: "POST", - }); - if (!res.ok) { - const body = await res.json().catch(() => null); - const msg = body?.message || res.statusText; - throw new Error(`Failed to start directive: ${msg}`); - } - return res.json(); -} diff --git a/makima/frontend/src/main.tsx b/makima/frontend/src/main.tsx index f07a143..50fffe4 100644 --- a/makima/frontend/src/main.tsx +++ b/makima/frontend/src/main.tsx @@ -18,7 +18,6 @@ import HistoryPage from "./routes/history"; import LoginPage from "./routes/login"; import SettingsPage from "./routes/settings"; import ContractFilePage from "./routes/contract-file"; -import DirectivesPage from "./routes/directives"; import SpeakPage from "./routes/speak"; createRoot(document.getElementById("root")!).render( @@ -80,22 +79,6 @@ createRoot(document.getElementById("root")!).render( } /> - - - - } - /> - - - - } - /> { - if (!authLoading && isAuthConfigured && !isAuthenticated) { - navigate("/login"); - } - }, [authLoading, isAuthConfigured, isAuthenticated, navigate]); - - if (authLoading) { - return ( -
- -
-

Loading...

-
-
- ); - } - - if (isAuthConfigured && !isAuthenticated) { - return null; - } - - return ; -} - -function DirectivesContent() { - const { id } = useParams<{ id?: string }>(); - const navigate = useNavigate(); - const { - directives, - loading, - error, - fetchDirective, - saveDirective, - removeDirective, - startDirective, - } = useDirectives(); - - const [selectedDirective, setSelectedDirective] = - useState(null); - const [detailLoading, setDetailLoading] = useState(false); - const [showCreateForm, setShowCreateForm] = useState(false); - const [createTitle, setCreateTitle] = useState(""); - const [createGoal, setCreateGoal] = useState(""); - - // Repository state - const [repoType, setRepoType] = useState("remote"); - const [repoUrl, setRepoUrl] = useState(""); - const [repoPath, setRepoPath] = useState(""); - const [suggestedDirectories, setSuggestedDirectories] = useState([]); - const [repoSuggestions, setRepoSuggestions] = useState([]); - const [showRepoSuggestions, setShowRepoSuggestions] = useState(false); - - // Fetch repository suggestions when modal opens and repo type changes - useEffect(() => { - if (showCreateForm && (repoType === "remote" || repoType === "local")) { - getRepositorySuggestions(repoType, undefined, 10) - .then((res) => { - setRepoSuggestions(res.entries); - setShowRepoSuggestions(res.entries.length > 0); - }) - .catch(() => { - setRepoSuggestions([]); - setShowRepoSuggestions(false); - }); - } else { - setRepoSuggestions([]); - setShowRepoSuggestions(false); - } - }, [showCreateForm, repoType]); - - // Fetch daemon directories when "local" repo type is selected - useEffect(() => { - if (repoType === "local" && showCreateForm) { - getDaemonDirectories() - .then((res) => setSuggestedDirectories(res.directories)) - .catch(() => setSuggestedDirectories([])); - } - }, [repoType, showCreateForm]); - - // Apply a repository suggestion - const applyRepoSuggestion = useCallback((suggestion: RepositoryHistoryEntry) => { - if (suggestion.repositoryUrl) { - setRepoUrl(suggestion.repositoryUrl); - } - if (suggestion.localPath) { - setRepoPath(suggestion.localPath); - } - setShowRepoSuggestions(false); - }, []); - - // Load directive when ID changes - useEffect(() => { - if (id) { - setDetailLoading(true); - fetchDirective(id).then((d) => { - setSelectedDirective(d); - setDetailLoading(false); - }); - } else { - setSelectedDirective(null); - } - }, [id, fetchDirective]); - - const handleSelect = useCallback( - (directiveId: string) => { - navigate(`/directives/${directiveId}`); - }, - [navigate] - ); - - const handleBack = useCallback(() => { - navigate("/directives"); - }, [navigate]); - - const resetCreateForm = useCallback(() => { - setShowCreateForm(false); - setCreateTitle(""); - setCreateGoal(""); - setRepoType("remote"); - setRepoUrl(""); - setRepoPath(""); - }, []); - - const handleCreate = useCallback(async () => { - if (!createTitle.trim() || !createGoal.trim()) return; - - const data: CreateDirectiveRequest = { - title: createTitle.trim(), - goal: createGoal.trim(), - }; - if (repoType === "remote" && repoUrl.trim()) { - data.repositoryUrl = repoUrl.trim(); - } else if (repoType === "local" && repoPath.trim()) { - data.localPath = repoPath.trim(); - } - - const result = await saveDirective(data); - if (result) { - resetCreateForm(); - } - }, [createTitle, createGoal, repoType, repoUrl, repoPath, saveDirective, resetCreateForm]); - - const handleDelete = useCallback( - async (directiveId: string) => { - const ok = await removeDirective(directiveId); - if (ok && id === directiveId) { - navigate("/directives"); - } - }, - [removeDirective, id, navigate] - ); - - const handleStart = useCallback( - async (directiveId: string) => { - const ok = await startDirective(directiveId); - if (ok) { - const updated = await fetchDirective(directiveId); - if (updated) { - setSelectedDirective(updated); - } - } - }, - [startDirective, fetchDirective] - ); - - const handleRefresh = useCallback( - (updated: DirectiveWithChains) => { - setSelectedDirective(updated); - }, - [] - ); - - return ( -
- -
- {error && ( -
- {error} -
- )} - - {/* Create directive modal */} - {showCreateForm && ( -
-
-

- New Directive -

-
- {/* Title */} -
- - setCreateTitle(e.target.value)} - className="w-full px-3 py-2 font-mono text-sm text-[#dbe7ff] bg-[#0d1b2d] border border-[#3f6fb3] focus:border-[#75aafc] outline-none" - autoFocus - /> -
- - {/* Goal */} -
- -