diff options
| author | soryu <soryu@soryu.co> | 2026-02-13 19:19:39 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-02-13 19:19:39 +0000 |
| commit | a6677bafe52d9988c9948df34c1635e4411c9591 (patch) | |
| tree | a3d05924fe6ff1a06c1abdfe1fa165e0d0546406 | |
| parent | 5edaf1228b4e48a441b98c49f58de312b7924ed6 (diff) | |
| download | soryu-a6677bafe52d9988c9948df34c1635e4411c9591.tar.gz soryu-a6677bafe52d9988c9948df34c1635e4411c9591.zip | |
Fix worktree branching for directive tasks and remove memories
| -rw-r--r-- | makima/frontend/src/components/directives/DirectiveDetail.tsx | 230 | ||||
| -rw-r--r-- | makima/frontend/src/hooks/useDirectiveMemories.ts | 86 | ||||
| -rw-r--r-- | makima/frontend/src/lib/api.ts | 176 | ||||
| -rw-r--r-- | makima/frontend/tsconfig.tsbuildinfo | 2 | ||||
| -rw-r--r-- | makima/migrations/20260213000000_drop_directive_memories.sql | 3 | ||||
| -rw-r--r-- | makima/src/bin/makima.rs | 44 | ||||
| -rw-r--r-- | makima/src/daemon/api/directive.rs | 199 | ||||
| -rw-r--r-- | makima/src/daemon/cli/directive.rs | 47 | ||||
| -rw-r--r-- | makima/src/daemon/cli/mod.rs | 18 | ||||
| -rw-r--r-- | makima/src/daemon/task/manager.rs | 144 | ||||
| -rw-r--r-- | makima/src/daemon/worktree/manager.rs | 79 | ||||
| -rw-r--r-- | makima/src/db/models.rs | 45 | ||||
| -rw-r--r-- | makima/src/db/repository.rs | 143 | ||||
| -rw-r--r-- | makima/src/orchestration/directive.rs | 84 | ||||
| -rw-r--r-- | makima/src/server/handlers/directives.rs | 394 | ||||
| -rw-r--r-- | makima/src/server/mod.rs | 4 | ||||
| -rw-r--r-- | makima/src/server/openapi.rs | 19 |
17 files changed, 199 insertions, 1518 deletions
diff --git a/makima/frontend/src/components/directives/DirectiveDetail.tsx b/makima/frontend/src/components/directives/DirectiveDetail.tsx index 369cdaa..f9e7eed 100644 --- a/makima/frontend/src/components/directives/DirectiveDetail.tsx +++ b/makima/frontend/src/components/directives/DirectiveDetail.tsx @@ -1,8 +1,7 @@ import { useState, useMemo, useEffect, useRef } from "react"; -import type { DirectiveWithSteps, DirectiveStatus, MemoryCategory } from "../../lib/api"; +import type { DirectiveWithSteps, DirectiveStatus } from "../../lib/api"; import { DirectiveDAG } from "./DirectiveDAG"; import { DirectiveLogStream } from "./DirectiveLogStream"; -import { useDirectiveMemories } from "../../hooks/useDirectiveMemories"; import { useMultiTaskSubscription } from "../../hooks/useMultiTaskSubscription"; const STATUS_BADGE: Record<DirectiveStatus, { color: string; label: string }> = { @@ -13,18 +12,6 @@ const STATUS_BADGE: Record<DirectiveStatus, { color: string; label: string }> = archived: { color: "text-[#556677] border-[#2a3a5a]", label: "ARCHIVED" }, }; -const CATEGORY_COLORS: Record<MemoryCategory, { text: string; border: string; bg: string; label: string }> = { - decision: { text: "text-amber-400", border: "border-amber-800", bg: "bg-amber-900/20", label: "Decision" }, - context: { text: "text-cyan-400", border: "border-cyan-800", bg: "bg-cyan-900/20", label: "Context" }, - preference: { text: "text-violet-400", border: "border-violet-800", bg: "bg-violet-900/20", label: "Preference" }, - learning: { text: "text-emerald-400", border: "border-emerald-800", bg: "bg-emerald-900/20", label: "Learning" }, - issue: { text: "text-red-400", border: "border-red-800", bg: "bg-red-900/20", label: "Issue" }, - progress: { text: "text-blue-400", border: "border-blue-800", bg: "bg-blue-900/20", label: "Progress" }, - other: { text: "text-[#7788aa]", border: "border-[#2a3a5a]", bg: "bg-[#1a2540]", label: "Other" }, -}; - -const ALL_CATEGORIES: MemoryCategory[] = ["decision", "context", "preference", "learning", "issue", "progress", "other"]; - interface DirectiveDetailProps { directive: DirectiveWithSteps; onStart: () => void; @@ -66,25 +53,6 @@ export function DirectiveDetail({ const terminalStatuses = new Set(["completed", "failed", "skipped"]); const hasTerminalTasks = directive.steps.some((s) => s.taskId && terminalStatuses.has(s.status)); - // Memory panel state - const [memoryOpen, setMemoryOpen] = useState(false); - const [addingMemory, setAddingMemory] = useState(false); - const [newCategory, setNewCategory] = useState<MemoryCategory>("context"); - const [newContent, setNewContent] = useState(""); - const [confirmClear, setConfirmClear] = useState(false); - - const { - grouped, - loading: memoryLoading, - error: memoryError, - add: addMemory, - remove: removeMemory, - clearAll: clearMemories, - refresh: refreshMemories, - } = useDirectiveMemories(directive.id); - - const totalMemories = Object.values(grouped).reduce((sum, arr) => sum + arr.length, 0); - // Build task map from directive steps and orchestrator const taskMap = useMemo(() => { const map = new Map<string, string>(); @@ -123,21 +91,6 @@ export function DirectiveDetail({ setEditingGoal(false); }; - const handleAddMemory = async () => { - if (!newContent.trim()) return; - await addMemory({ - category: newCategory, - content: newContent.trim(), - }); - setNewContent(""); - setAddingMemory(false); - }; - - const handleClearAll = async () => { - await clearMemories(); - setConfirmClear(false); - }; - return ( <div className="flex flex-col h-full overflow-y-auto"> {/* Header */} @@ -346,187 +299,6 @@ export function DirectiveDetail({ )} </div> - {/* Memory Panel */} - <div className="px-4 py-3 border-b border-[rgba(117,170,252,0.1)]"> - {/* Memory header — always visible */} - <div className="flex items-center justify-between"> - <button - type="button" - onClick={() => setMemoryOpen((v) => !v)} - className="flex items-center gap-1.5 group" - > - <span className="text-[10px] font-mono text-[#556677] group-hover:text-[#9bc3ff] transition-colors"> - {memoryOpen ? "\u25BC" : "\u25B6"} - </span> - <span className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide"> - Memory - </span> - {totalMemories > 0 && ( - <span className="text-[9px] font-mono text-[#556677] ml-1"> - ({totalMemories}) - </span> - )} - </button> - <div className="flex items-center gap-2" /> - </div> - - {/* Collapsible content */} - {memoryOpen && ( - <div className="mt-2"> - {memoryError && ( - <div className="text-[10px] font-mono text-red-400 mb-2 px-2 py-1 bg-red-900/10 border border-red-800/30 rounded"> - {memoryError} - </div> - )} - - {memoryLoading ? ( - <div className="text-[10px] font-mono text-[#556677] py-2">Loading...</div> - ) : totalMemories === 0 ? ( - <div className="text-[10px] font-mono text-[#556677] py-2"> - No memory entries yet. - </div> - ) : ( - /* Grouped entries */ - <div className="flex flex-col gap-2"> - {ALL_CATEGORIES.map((cat) => { - const entries = grouped[cat]; - if (entries.length === 0) return null; - const style = CATEGORY_COLORS[cat]; - return ( - <div key={cat}> - <div className="flex items-center gap-1.5 mb-1"> - <span className={`text-[9px] font-mono ${style.text} uppercase tracking-wider`}> - {style.label} - </span> - <span className="text-[9px] font-mono text-[#556677]"> - ({entries.length}) - </span> - </div> - <div className="flex flex-col gap-1"> - {entries.map((entry) => ( - <div - key={entry.id} - className={`flex items-start gap-2 px-2 py-1.5 rounded border ${style.border} ${style.bg}`} - > - <div className="flex-1 min-w-0"> - <p className="text-[10px] font-mono text-[#c0d0e0] whitespace-pre-wrap break-words"> - {entry.content} - </p> - </div> - <button - type="button" - onClick={() => removeMemory(entry.id)} - className="text-[9px] font-mono text-[#556677] hover:text-red-400 shrink-0 mt-0.5" - title="Delete entry" - > - x - </button> - </div> - ))} - </div> - </div> - ); - })} - </div> - )} - - {/* Action bar: Add + Clear */} - <div className="flex items-center gap-2 mt-2 pt-2 border-t border-[rgba(117,170,252,0.1)]"> - <button - type="button" - onClick={() => setAddingMemory((v) => !v)} - className="text-[10px] font-mono text-[#75aafc] hover:text-white border border-[rgba(117,170,252,0.2)] rounded px-2 py-0.5" - > - {addingMemory ? "Cancel" : "+ Add"} - </button> - {totalMemories > 0 && ( - <> - {confirmClear ? ( - <div className="flex items-center gap-1.5 ml-auto"> - <span className="text-[9px] font-mono text-red-400">Clear all?</span> - <button - type="button" - onClick={handleClearAll} - className="text-[9px] font-mono text-red-400 hover:text-red-300 border border-red-800 rounded px-1.5 py-0.5" - > - Yes - </button> - <button - type="button" - onClick={() => setConfirmClear(false)} - className="text-[9px] font-mono text-[#7788aa] hover:text-white border border-[#2a3a5a] rounded px-1.5 py-0.5" - > - No - </button> - </div> - ) : ( - <button - type="button" - onClick={() => setConfirmClear(true)} - className="text-[10px] font-mono text-[#556677] hover:text-red-400 ml-auto" - > - Clear all - </button> - )} - </> - )} - <button - type="button" - onClick={refreshMemories} - className="text-[9px] font-mono text-[#556677] hover:text-[#75aafc]" - title="Refresh memories" - > - [refresh] - </button> - </div> - - {/* Add form */} - {addingMemory && ( - <div className="mt-2 p-2 bg-[#0a1628] border border-[rgba(117,170,252,0.15)] rounded flex flex-col gap-2"> - <div className="flex items-center gap-2"> - <label className="text-[9px] font-mono text-[#7788aa] shrink-0">Category</label> - <select - value={newCategory} - onChange={(e) => setNewCategory(e.target.value as MemoryCategory)} - className="bg-[#1a2540] border border-[rgba(117,170,252,0.2)] rounded px-1.5 py-0.5 text-[10px] font-mono text-white flex-1" - > - {ALL_CATEGORIES.map((c) => ( - <option key={c} value={c}> - {CATEGORY_COLORS[c].label} - </option> - ))} - </select> - </div> - <textarea - value={newContent} - onChange={(e) => setNewContent(e.target.value)} - placeholder="Memory content..." - className="w-full bg-[#1a2540] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[10px] font-mono text-white resize-y min-h-[40px] placeholder:text-[#556677]" - rows={2} - /> - <div className="flex gap-1.5"> - <button - type="button" - onClick={handleAddMemory} - disabled={!newContent.trim()} - className="text-[10px] font-mono text-emerald-400 hover:text-emerald-300 border border-emerald-800 rounded px-2 py-0.5 disabled:opacity-40 disabled:cursor-not-allowed" - > - Save - </button> - <button - type="button" - onClick={() => { setAddingMemory(false); setNewContent(""); }} - className="text-[10px] font-mono text-[#7788aa] hover:text-white border border-[#2a3a5a] rounded px-2 py-0.5" - > - Cancel - </button> - </div> - </div> - )} - </div> - )} - </div> - {/* DAG */} <div className="px-4 py-3 flex-1"> <span className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-2"> diff --git a/makima/frontend/src/hooks/useDirectiveMemories.ts b/makima/frontend/src/hooks/useDirectiveMemories.ts deleted file mode 100644 index b2a87d0..0000000 --- a/makima/frontend/src/hooks/useDirectiveMemories.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { useState, useEffect, useCallback } from "react"; -import { - type DirectiveMemory, - type MemoryCategory, - type CreateDirectiveMemoryRequest, - listDirectiveMemories, - createDirectiveMemory, - deleteDirectiveMemory, -} from "../lib/api"; - -export function useDirectiveMemories(directiveId: string | undefined) { - const [memories, setMemories] = useState<DirectiveMemory[]>([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState<string | null>(null); - - const refreshMemories = useCallback(async () => { - if (!directiveId) return; - try { - setLoading(true); - setError(null); - const response = await listDirectiveMemories(directiveId); - setMemories(response.memories); - } catch (e) { - setError(e instanceof Error ? e.message : "Failed to load memories"); - } finally { - setLoading(false); - } - }, [directiveId]); - - useEffect(() => { - refreshMemories(); - }, [refreshMemories]); - - const add = useCallback(async (req: CreateDirectiveMemoryRequest) => { - if (!directiveId) return; - try { - setError(null); - await createDirectiveMemory(directiveId, req); - await refreshMemories(); - } catch (e) { - setError(e instanceof Error ? e.message : "Failed to add memory"); - } - }, [directiveId, refreshMemories]); - - const remove = useCallback(async (memoryId: string) => { - if (!directiveId) return; - try { - setError(null); - await deleteDirectiveMemory(directiveId, memoryId); - await refreshMemories(); - } catch (e) { - setError(e instanceof Error ? e.message : "Failed to delete memory"); - } - }, [directiveId, refreshMemories]); - - const clearAll = useCallback(async () => { - if (!directiveId) return; - try { - setError(null); - await Promise.all(memories.map((m) => deleteDirectiveMemory(directiveId, m.id))); - setMemories([]); - } catch (e) { - setError(e instanceof Error ? e.message : "Failed to clear memories"); - } - }, [directiveId, memories]); - - /** Group entries by category */ - const grouped = memories.reduce<Record<MemoryCategory, DirectiveMemory[]>>( - (acc, entry) => { - acc[entry.category].push(entry); - return acc; - }, - { decision: [], context: [], preference: [], learning: [], issue: [], progress: [], other: [] }, - ); - - return { - memories, - grouped, - loading, - error, - refresh: refreshMemories, - add, - remove, - clearAll, - }; -} diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts index 9d9cb1c..480041c 100644 --- a/makima/frontend/src/lib/api.ts +++ b/makima/frontend/src/lib/api.ts @@ -3246,179 +3246,3 @@ export async function cleanupDirectiveTasks(id: string): Promise<{ deleted: numb return res.json(); } -// ============================================================================= -// Directive Memory Types & API -// ============================================================================= - -/** Category of a directive memory entry */ -export type MemoryCategory = - | "decision" - | "learning" - | "context" - | "preference" - | "issue" - | "progress" - | "other"; - -/** A single memory entry associated with a directive */ -export interface DirectiveMemory { - id: string; - directiveId: string; - /** The memory content text */ - content: string; - /** Category for organizing memories */ - category: MemoryCategory; - /** Which step created this memory (null if directive-level) */ - stepId: string | null; - /** Which task created this memory (null if manually added) */ - taskId: string | null; - /** Importance score (1-10, higher = more important) */ - importance: number; - createdAt: string; - updatedAt: string; -} - -/** Response from listing directive memories */ -export interface DirectiveMemoryListResponse { - memories: DirectiveMemory[]; - total: number; -} - -/** Request to create a new directive memory */ -export interface CreateDirectiveMemoryRequest { - content: string; - category?: MemoryCategory; - stepId?: string; - taskId?: string; - importance?: number; -} - -/** Request to update a directive memory */ -export interface UpdateDirectiveMemoryRequest { - content?: string; - category?: MemoryCategory; - importance?: number; -} - -// Directive Memory API functions - -/** - * List all memories for a directive. - * Optionally filter by category or step. - */ -export async function listDirectiveMemories( - directiveId: string, - params?: { category?: MemoryCategory; stepId?: string } -): Promise<DirectiveMemoryListResponse> { - const searchParams = new URLSearchParams(); - if (params?.category) searchParams.set("category", params.category); - if (params?.stepId) searchParams.set("stepId", params.stepId); - const query = searchParams.toString(); - const url = `${API_BASE}/api/v1/directives/${directiveId}/memories${query ? `?${query}` : ""}`; - const res = await authFetch(url); - if (!res.ok) throw new Error(`Failed to list directive memories: ${res.statusText}`); - return res.json(); -} - -/** - * Get a single memory entry by ID. - */ -export async function getDirectiveMemory( - directiveId: string, - memoryId: string -): Promise<DirectiveMemory> { - const res = await authFetch( - `${API_BASE}/api/v1/directives/${directiveId}/memories/${memoryId}` - ); - if (!res.ok) throw new Error(`Failed to get directive memory: ${res.statusText}`); - return res.json(); -} - -/** - * Create a new memory entry for a directive. - */ -export async function createDirectiveMemory( - directiveId: string, - req: CreateDirectiveMemoryRequest -): Promise<DirectiveMemory> { - const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/memories`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(req), - }); - if (!res.ok) throw new Error(`Failed to create directive memory: ${res.statusText}`); - return res.json(); -} - -/** - * Update an existing memory entry. - */ -export async function updateDirectiveMemory( - directiveId: string, - memoryId: string, - req: UpdateDirectiveMemoryRequest -): Promise<DirectiveMemory> { - const res = await authFetch( - `${API_BASE}/api/v1/directives/${directiveId}/memories/${memoryId}`, - { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(req), - } - ); - if (!res.ok) throw new Error(`Failed to update directive memory: ${res.statusText}`); - return res.json(); -} - -/** - * Delete a memory entry. - */ -export async function deleteDirectiveMemory( - directiveId: string, - memoryId: string -): Promise<void> { - const res = await authFetch( - `${API_BASE}/api/v1/directives/${directiveId}/memories/${memoryId}`, - { method: "DELETE" } - ); - if (!res.ok) throw new Error(`Failed to delete directive memory: ${res.statusText}`); -} - -/** - * Batch create multiple memory entries for a directive. - * Useful when a task completes and wants to store multiple learnings at once. - */ -export async function batchCreateDirectiveMemories( - directiveId: string, - memories: CreateDirectiveMemoryRequest[] -): Promise<DirectiveMemory[]> { - const res = await authFetch( - `${API_BASE}/api/v1/directives/${directiveId}/memories/batch`, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(memories), - } - ); - if (!res.ok) throw new Error(`Failed to batch create directive memories: ${res.statusText}`); - return res.json(); -} - -/** - * Get a formatted memory context string for a directive. - * This returns memories formatted for injection into task prompts. - * Optionally filter by category or limit the number of memories returned. - */ -export async function getDirectiveMemoryContext( - directiveId: string, - params?: { category?: MemoryCategory; limit?: number } -): Promise<{ context: string; memoryCount: number }> { - const searchParams = new URLSearchParams(); - if (params?.category) searchParams.set("category", params.category); - if (params?.limit) searchParams.set("limit", params.limit.toString()); - const query = searchParams.toString(); - const url = `${API_BASE}/api/v1/directives/${directiveId}/memories/context${query ? `?${query}` : ""}`; - const res = await authFetch(url); - if (!res.ok) throw new Error(`Failed to get directive memory context: ${res.statusText}`); - return res.json(); -} diff --git a/makima/frontend/tsconfig.tsbuildinfo b/makima/frontend/tsconfig.tsbuildinfo index 210212c..c2bf573 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/directivedag.tsx","./src/components/directives/directivedetail.tsx","./src/components/directives/directivelist.tsx","./src/components/directives/directivelogstream.tsx","./src/components/directives/stepnode.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/usedirectivememories.ts","./src/hooks/usedirectives.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usemultitasksubscription.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/directives/directivedag.tsx","./src/components/directives/directivedetail.tsx","./src/components/directives/directivelist.tsx","./src/components/directives/directivelogstream.tsx","./src/components/directives/stepnode.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/usedirectives.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usemultitasksubscription.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 diff --git a/makima/migrations/20260213000000_drop_directive_memories.sql b/makima/migrations/20260213000000_drop_directive_memories.sql new file mode 100644 index 0000000..8f683e5 --- /dev/null +++ b/makima/migrations/20260213000000_drop_directive_memories.sql @@ -0,0 +1,3 @@ +-- Drop the directive memories system +DROP TABLE IF EXISTS directive_memories; +ALTER TABLE directives DROP COLUMN IF EXISTS memory_enabled; diff --git a/makima/src/bin/makima.rs b/makima/src/bin/makima.rs index d4af878..c2c9beb 100644 --- a/makima/src/bin/makima.rs +++ b/makima/src/bin/makima.rs @@ -825,50 +825,6 @@ async fn run_directive( .await?; println!("{}", serde_json::to_string(&result.0)?); } - DirectiveCommand::MemorySet(args) => { - let client = ApiClient::new(args.common.api_url, args.common.api_key)?; - let result = client - .directive_memory_set(args.common.directive_id, &args.key, &args.value) - .await?; - println!("{}", serde_json::to_string(&result.0)?); - } - DirectiveCommand::MemoryGet(args) => { - let client = ApiClient::new(args.common.api_url, args.common.api_key)?; - let result = client - .directive_memory_get(args.common.directive_id, &args.key) - .await?; - println!("{}", serde_json::to_string(&result.0)?); - } - DirectiveCommand::MemoryList(args) => { - let client = ApiClient::new(args.api_url, args.api_key)?; - let result = client - .directive_memory_list(args.directive_id) - .await?; - println!("{}", serde_json::to_string(&result.0)?); - } - DirectiveCommand::MemoryDelete(args) => { - let client = ApiClient::new(args.common.api_url, args.common.api_key)?; - client - .directive_memory_delete(args.common.directive_id, &args.key) - .await?; - println!(r#"{{"success": true}}"#); - } - DirectiveCommand::MemoryClear(args) => { - let client = ApiClient::new(args.api_url, args.api_key)?; - client - .directive_memory_clear(args.directive_id) - .await?; - println!(r#"{{"success": true}}"#); - } - DirectiveCommand::MemoryBatchSet(args) => { - let client = ApiClient::new(args.common.api_url, args.common.api_key)?; - let entries: serde_json::Value = serde_json::from_str(&args.json) - .map_err(|e| format!("Invalid JSON: {}", e))?; - let result = client - .directive_memory_batch_set(args.common.directive_id, entries) - .await?; - println!("{}", serde_json::to_string(&result.0)?); - } } Ok(()) diff --git a/makima/src/daemon/api/directive.rs b/makima/src/daemon/api/directive.rs index fcc2ca5..a0cdab0 100644 --- a/makima/src/daemon/api/directive.rs +++ b/makima/src/daemon/api/directive.rs @@ -30,54 +30,6 @@ pub struct UpdateStepDepsRequest { pub depends_on: Vec<Uuid>, } -/// Percent-encode a string for use as a URL path segment. -/// -/// Encodes all characters except unreserved characters (alphanumeric, `-`, `.`, `_`, `~`). -fn percent_encode_path(s: &str) -> String { - let mut encoded = String::with_capacity(s.len()); - for byte in s.bytes() { - match byte { - b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => { - encoded.push(byte as char); - } - _ => { - encoded.push_str(&format!("%{:02X}", byte)); - } - } - } - encoded -} - -/// Request body for setting a single memory entry. -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct SetMemoryRequest { - pub key: String, - pub value: String, -} - -/// A single entry within a batch set request. -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct BatchMemoryEntry { - pub key: String, - pub value: String, -} - -/// Request body for setting multiple memory entries at once. -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct BatchSetMemoryRequest { - pub entries: Vec<BatchMemoryEntry>, -} - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct MemorySetRequest { - pub value: String, -} - - impl ApiClient { /// List all directives. pub async fn list_directives(&self) -> Result<JsonValue, ApiError> { @@ -194,157 +146,6 @@ impl ApiClient { self.put(&format!("/api/v1/directives/{}", directive_id), &req).await } - // ── Directive Memory ────────────────────────────────────────────── - - /// List all memory entries for a directive. - pub async fn list_memories(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> { - self.get(&format!("/api/v1/directives/{}/memory", directive_id)) - .await - } - - /// Get a single memory entry by key. - pub async fn get_memory( - &self, - directive_id: Uuid, - key: &str, - ) -> Result<JsonValue, ApiError> { - self.get(&format!( - "/api/v1/directives/{}/memory/{}", - directive_id, - percent_encode_path(key) - )) - .await - } - - /// Set (create or update) a single memory entry. - pub async fn set_memory( - &self, - directive_id: Uuid, - key: &str, - value: &str, - ) -> Result<JsonValue, ApiError> { - let req = SetMemoryRequest { - key: key.to_string(), - value: value.to_string(), - }; - self.put(&format!("/api/v1/directives/{}/memory", directive_id), &req) - .await - } - - /// Set multiple memory entries in a single request. - pub async fn batch_set_memories( - &self, - directive_id: Uuid, - entries: Vec<(String, String)>, - ) -> Result<JsonValue, ApiError> { - let req = BatchSetMemoryRequest { - entries: entries - .into_iter() - .map(|(key, value)| BatchMemoryEntry { key, value }) - .collect(), - }; - self.post( - &format!("/api/v1/directives/{}/memory/batch", directive_id), - &req, - ) - .await - } - - /// Delete a single memory entry by key. - pub async fn delete_memory( - &self, - directive_id: Uuid, - key: &str, - ) -> Result<(), ApiError> { - self.delete(&format!( - "/api/v1/directives/{}/memory/{}", - directive_id, - percent_encode_path(key) - )) - .await - } - - /// Clear all memory entries for a directive. - pub async fn clear_memories(&self, directive_id: Uuid) -> Result<(), ApiError> { - self.delete(&format!("/api/v1/directives/{}/memory", directive_id)) - .await - } - - // ── CLI-facing Directive Memory aliases ────────────────────────── - - /// Set a memory key-value pair for a directive (CLI-facing). - pub async fn directive_memory_set( - &self, - directive_id: Uuid, - key: &str, - value: &str, - ) -> Result<JsonValue, ApiError> { - let req = MemorySetRequest { - value: value.to_string(), - }; - self.put( - &format!("/api/v1/directives/{}/memory/{}", directive_id, key), - &req, - ) - .await - } - - /// Get a memory value by key for a directive (CLI-facing). - pub async fn directive_memory_get( - &self, - directive_id: Uuid, - key: &str, - ) -> Result<JsonValue, ApiError> { - self.get(&format!( - "/api/v1/directives/{}/memory/{}", - directive_id, key - )) - .await - } - - /// List all memory key-value pairs for a directive (CLI-facing). - pub async fn directive_memory_list( - &self, - directive_id: Uuid, - ) -> Result<JsonValue, ApiError> { - self.get(&format!("/api/v1/directives/{}/memory", directive_id)) - .await - } - - /// Delete a memory key for a directive (CLI-facing). - pub async fn directive_memory_delete( - &self, - directive_id: Uuid, - key: &str, - ) -> Result<(), ApiError> { - self.delete(&format!( - "/api/v1/directives/{}/memory/{}", - directive_id, key - )) - .await - } - - /// Clear all memory for a directive (CLI-facing). - pub async fn directive_memory_clear( - &self, - directive_id: Uuid, - ) -> Result<(), ApiError> { - self.delete(&format!("/api/v1/directives/{}/memory", directive_id)) - .await - } - - /// Batch set multiple memory key-value pairs for a directive (CLI-facing). - pub async fn directive_memory_batch_set( - &self, - directive_id: Uuid, - entries: serde_json::Value, - ) -> Result<JsonValue, ApiError> { - self.post( - &format!("/api/v1/directives/{}/memory/batch", directive_id), - &entries, - ) - .await - } } #[derive(Serialize)] diff --git a/makima/src/daemon/cli/directive.rs b/makima/src/daemon/cli/directive.rs index 8eded77..7c50c42 100644 --- a/makima/src/daemon/cli/directive.rs +++ b/makima/src/daemon/cli/directive.rs @@ -126,50 +126,3 @@ pub struct UpdateArgs { pub pr_branch: Option<String>, } -/// Arguments for memory-set command. -#[derive(Args, Debug)] -pub struct MemorySetArgs { - #[command(flatten)] - pub common: DirectiveArgs, - - /// Memory key - pub key: String, - - /// Memory value - pub value: String, -} - -/// Arguments for memory-get command. -#[derive(Args, Debug)] -pub struct MemoryGetArgs { - #[command(flatten)] - pub common: DirectiveArgs, - - /// Memory key - pub key: String, -} - -/// Arguments for memory-list command (uses DirectiveArgs directly). - -/// Arguments for memory-delete command. -#[derive(Args, Debug)] -pub struct MemoryDeleteArgs { - #[command(flatten)] - pub common: DirectiveArgs, - - /// Memory key to delete - pub key: String, -} - -/// Arguments for memory-clear command (uses DirectiveArgs directly). - -/// Arguments for memory-batch-set command. -#[derive(Args, Debug)] -pub struct MemoryBatchSetArgs { - #[command(flatten)] - pub common: DirectiveArgs, - - /// JSON object of key-value pairs: {"key1":"value1","key2":"value2"} - #[arg(long)] - pub json: String, -} diff --git a/makima/src/daemon/cli/mod.rs b/makima/src/daemon/cli/mod.rs index a78e5f8..bcaaa70 100644 --- a/makima/src/daemon/cli/mod.rs +++ b/makima/src/daemon/cli/mod.rs @@ -249,24 +249,6 @@ pub enum DirectiveCommand { /// Update directive metadata (PR URL, etc.) Update(directive::UpdateArgs), - - /// Set a memory key-value pair for the directive - MemorySet(directive::MemorySetArgs), - - /// Get a memory value by key - MemoryGet(directive::MemoryGetArgs), - - /// List all memory key-value pairs - MemoryList(DirectiveArgs), - - /// Delete a memory key - MemoryDelete(directive::MemoryDeleteArgs), - - /// Clear all memory for the directive - MemoryClear(DirectiveArgs), - - /// Batch set multiple memory key-value pairs from JSON - MemoryBatchSet(directive::MemoryBatchSetArgs), } impl Cli { diff --git a/makima/src/daemon/task/manager.rs b/makima/src/daemon/task/manager.rs index 22b41d9..ce5a580 100644 --- a/makima/src/daemon/task/manager.rs +++ b/makima/src/daemon/task/manager.rs @@ -20,7 +20,7 @@ use crate::daemon::error::{DaemonError, TaskError, TaskResult}; use crate::daemon::process::{ClaudeInputMessage, ProcessManager}; use crate::daemon::storage; use crate::daemon::temp::TempManager; -use crate::daemon::worktree::{is_new_repo_request, ConflictResolution, WorktreeInfo, WorktreeManager}; +use crate::daemon::worktree::{is_new_repo_request, ConflictResolution, WorktreeError, WorktreeInfo, WorktreeManager}; use crate::daemon::db::local::LocalDb; use crate::daemon::ws::{BranchInfo, DaemonCommand, DaemonMessage}; @@ -4406,8 +4406,14 @@ impl TaskManagerInner { // Create worktree - either from scratch or copying from another task let task_name = format!("task-{}", &task_id.to_string()[..8]); let worktree_info = if let Some(from_task_id) = continue_from_task_id { - // Try to find the source task's worktree path - match self.find_worktree_for_task(from_task_id).await { + // Fallback chain for continuing from a previous task: + // 1. Try copying from existing worktree (fastest, preserves uncommitted changes) + // 2. Try creating from pushed branch (branch was pushed to remote) + // 3. Try restoring from saved patch data + // 4. Fail if none available + + // Step 1: Try copying from existing worktree + let copy_result = match self.find_worktree_for_task(from_task_id).await { Ok(source_worktree) => { let msg = DaemonMessage::task_output( task_id, @@ -4416,66 +4422,112 @@ impl TaskManagerInner { ); let _ = self.ws_tx.send(msg).await; - // Create worktree by copying from source task self.worktree_manager .create_worktree_from_task(&source_worktree, task_id, &task_name) .await - .map_err(|e| DaemonError::Task(TaskError::SetupFailed(e.to_string())))? } - Err(worktree_err) => { - // Source worktree not found - try to recover using patch data - if let (Some(patch_str), Some(base_sha)) = (&patch_data, &patch_base_sha) { - tracing::info!( - task_id = %task_id, - from_task_id = %from_task_id, - base_sha = %base_sha, - patch_len = patch_str.len(), - "Source worktree not found, attempting to restore from patch" - ); + Err(e) => Err(crate::daemon::worktree::WorktreeError::RepoNotFound(e.to_string())), + }; - let msg = DaemonMessage::task_output( - task_id, - format!("Source worktree unavailable, restoring from checkpoint patch...\n"), - false, - ); - let _ = self.ws_tx.send(msg).await; + match copy_result { + Ok(info) => info, + Err(copy_err) => { + tracing::warn!( + task_id = %task_id, + from_task_id = %from_task_id, + error = %copy_err, + "Failed to copy from source worktree, trying branch fallback" + ); + + // Step 2: Try creating from pushed branch + let msg = DaemonMessage::task_output( + task_id, + format!("Source worktree unavailable, checking for pushed branch...\n"), + false, + ); + let _ = self.ws_tx.send(msg).await; + + match self.worktree_manager + .create_worktree_from_task_branch(&source_repo, from_task_id, task_id, &task_name) + .await + { + Ok(info) => { + tracing::info!( + task_id = %task_id, + from_task_id = %from_task_id, + branch = %info.branch, + "Successfully created worktree from pushed branch" + ); + let msg = DaemonMessage::task_output( + task_id, + format!("Restored from pushed branch {}\n", info.branch), + false, + ); + let _ = self.ws_tx.send(msg).await; + info + } + Err(branch_err) => { + tracing::warn!( + task_id = %task_id, + from_task_id = %from_task_id, + error = %branch_err, + "No pushed branch found, trying patch fallback" + ); + + // Step 3: Try restoring from saved patch data + if let (Some(patch_str), Some(base_sha)) = (&patch_data, &patch_base_sha) { + tracing::info!( + task_id = %task_id, + from_task_id = %from_task_id, + base_sha = %base_sha, + patch_len = patch_str.len(), + "Attempting to restore from checkpoint patch" + ); - // Decode base64 patch data - match base64::Engine::decode(&base64::engine::general_purpose::STANDARD, patch_str) { - Ok(patch_bytes) => { - match self.worktree_manager.restore_from_patch( - source, + let msg = DaemonMessage::task_output( task_id, - &task_name, - base_sha, - &patch_bytes, - ).await { - Ok(worktree_info) => { - tracing::info!( - task_id = %task_id, - path = %worktree_info.path.display(), - "Successfully restored worktree from patch" - ); - worktree_info + format!("Restoring from checkpoint patch...\n"), + false, + ); + let _ = self.ws_tx.send(msg).await; + + match base64::Engine::decode(&base64::engine::general_purpose::STANDARD, patch_str) { + Ok(patch_bytes) => { + match self.worktree_manager.restore_from_patch( + source, + task_id, + &task_name, + base_sha, + &patch_bytes, + ).await { + Ok(worktree_info) => { + tracing::info!( + task_id = %task_id, + path = %worktree_info.path.display(), + "Successfully restored worktree from patch" + ); + worktree_info + } + Err(e) => { + return Err(DaemonError::Task(TaskError::SetupFailed( + format!("Cannot continue from task {}: worktree copy failed ({}), branch not found ({}), patch restore failed ({})", from_task_id, copy_err, branch_err, e) + ))); + } + } } Err(e) => { return Err(DaemonError::Task(TaskError::SetupFailed( - format!("Cannot continue from task {}: {} (patch restore also failed: {})", from_task_id, worktree_err, e) + format!("Cannot continue from task {}: worktree copy failed ({}), branch not found ({}), patch decode failed ({})", from_task_id, copy_err, branch_err, e) ))); } } - } - Err(e) => { + } else { + // Step 4: No fallback available return Err(DaemonError::Task(TaskError::SetupFailed( - format!("Cannot continue from task {}: {} (patch decode failed: {})", from_task_id, worktree_err, e) + format!("Cannot continue from task {}: worktree copy failed ({}), branch not found ({}), no patch data available", from_task_id, copy_err, branch_err) ))); } } - } else { - // No patch data available - fail with original error - return Err(DaemonError::Task(TaskError::SetupFailed( - format!("Cannot continue from task {}: {}", from_task_id, worktree_err) - ))); } } } diff --git a/makima/src/daemon/worktree/manager.rs b/makima/src/daemon/worktree/manager.rs index 20c93b1..04180b8 100644 --- a/makima/src/daemon/worktree/manager.rs +++ b/makima/src/daemon/worktree/manager.rs @@ -692,6 +692,85 @@ impl WorktreeManager { }) } + /// Find and create a worktree from a previously pushed branch for a task. + /// + /// Searches local and remote branches for ones matching the task's UUID, + /// then creates a new worktree based on that branch. + pub async fn create_worktree_from_task_branch( + &self, + source_repo: &Path, + from_task_id: Uuid, + new_task_id: Uuid, + new_task_name: &str, + ) -> Result<WorktreeInfo, WorktreeError> { + let from_short_id = short_uuid(from_task_id); + let from_full_id = from_task_id.to_string(); + + tracing::info!( + from_task_id = %from_task_id, + new_task_id = %new_task_id, + "Searching for pushed branch to continue from" + ); + + // Fetch latest from remote first + let _ = Command::new("git") + .args(["fetch", "--all", "--prune"]) + .current_dir(source_repo) + .output() + .await; + + // Search for branches matching the task ID (both local and remote) + let output = Command::new("git") + .args(["branch", "-a", "--format=%(refname:short)"]) + .current_dir(source_repo) + .output() + .await?; + + if !output.status.success() { + return Err(WorktreeError::GitCommand( + "Failed to list branches".to_string(), + )); + } + + let branches_output = String::from_utf8_lossy(&output.stdout); + let mut found_branch: Option<String> = None; + + for line in branches_output.lines() { + let b = line.trim(); + if b.is_empty() { + continue; + } + // Match branches containing the full UUID or short UUID + if b.contains(&from_full_id) || b.contains(&from_short_id) { + // Prefer local branches, but accept remote + if let Some(remote_branch) = b.strip_prefix("origin/") { + if found_branch.is_none() { + found_branch = Some(remote_branch.to_string()); + } + } else { + found_branch = Some(b.to_string()); + } + } + } + + let from_branch = found_branch.ok_or_else(|| { + WorktreeError::GitCommand(format!( + "No branch found for task {} in repository", + from_short_id + )) + })?; + + tracing::info!( + from_task_id = %from_task_id, + from_branch = %from_branch, + "Found pushed branch, creating worktree from it" + ); + + // Create a worktree using the found branch as the base + self.create_worktree(source_repo, new_task_id, new_task_name, &from_branch) + .await + } + /// Remove a worktree and optionally its branch. pub async fn remove_worktree( &self, diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs index 66c0a30..131dffc 100644 --- a/makima/src/db/models.rs +++ b/makima/src/db/models.rs @@ -2714,7 +2714,6 @@ pub struct Directive { pub pr_url: Option<String>, pub pr_branch: Option<String>, pub completion_task_id: Option<Uuid>, - pub memory_enabled: bool, pub goal_updated_at: DateTime<Utc>, pub started_at: Option<DateTime<Utc>>, pub version: i32, @@ -2764,7 +2763,6 @@ pub struct DirectiveSummary { pub orchestrator_task_id: Option<Uuid>, pub pr_url: Option<String>, pub completion_task_id: Option<Uuid>, - pub memory_enabled: bool, pub version: i32, pub created_at: DateTime<Utc>, pub updated_at: DateTime<Utc>, @@ -2791,8 +2789,6 @@ pub struct CreateDirectiveRequest { pub repository_url: Option<String>, pub local_path: Option<String>, pub base_branch: Option<String>, - #[serde(default)] - pub memory_enabled: bool, } /// Request to update a directive. @@ -2808,7 +2804,6 @@ pub struct UpdateDirectiveRequest { pub orchestrator_task_id: Option<Uuid>, pub pr_url: Option<String>, pub pr_branch: Option<String>, - pub memory_enabled: Option<bool>, pub version: Option<i32>, } @@ -2853,43 +2848,3 @@ pub struct UpdateDirectiveStepRequest { pub order_index: Option<i32>, } -// ============================================================================= -// Directive Memory Types -// ============================================================================= - -/// A memory entry for a directive — key-value context that persists across tasks. -#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct DirectiveMemory { - pub id: Uuid, - pub directive_id: Uuid, - pub key: String, - pub value: String, - pub category: Option<String>, - pub created_at: DateTime<Utc>, - pub updated_at: DateTime<Utc>, -} - -/// Request to set a memory entry (upsert by key). -#[derive(Debug, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct SetDirectiveMemoryRequest { - pub key: String, - pub value: String, - pub category: Option<String>, -} - -/// Request to batch set memory entries. -#[derive(Debug, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct BatchSetDirectiveMemoryRequest { - pub entries: Vec<SetDirectiveMemoryRequest>, -} - -/// Response for listing memories. -#[derive(Debug, Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct DirectiveMemoryListResponse { - pub memories: Vec<DirectiveMemory>, - pub total: i64, -} diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs index 51f49cd..8923f97 100644 --- a/makima/src/db/repository.rs +++ b/makima/src/db/repository.rs @@ -11,10 +11,9 @@ use super::models::{ ContractTypeTemplateRecord, ConversationMessage, ConversationSnapshot, CreateContractRequest, CreateFileRequest, CreateTaskRequest, CreateTemplateRequest, Daemon, DaemonTaskAssignment, DaemonWithCapacity, - DeliverableDefinition, Directive, DirectiveMemory, DirectiveStep, DirectiveSummary, + DeliverableDefinition, Directive, DirectiveStep, DirectiveSummary, CreateDirectiveRequest, CreateDirectiveStepRequest, UpdateDirectiveRequest, - UpdateDirectiveStepRequest, SetDirectiveMemoryRequest, - BatchSetDirectiveMemoryRequest, DirectiveMemoryListResponse, + UpdateDirectiveStepRequest, File, FileSummary, FileVersion, HistoryEvent, HistoryQueryFilters, MeshChatConversation, MeshChatMessageRecord, PhaseChangeResult, PhaseConfig, PhaseDefinition, SupervisorHeartbeatRecord, SupervisorState, @@ -4930,8 +4929,8 @@ pub async fn create_directive_for_owner( ) -> Result<Directive, sqlx::Error> { sqlx::query_as::<_, Directive>( r#" - INSERT INTO directives (owner_id, title, goal, repository_url, local_path, base_branch, memory_enabled) - VALUES ($1, $2, $3, $4, $5, $6, $7) + INSERT INTO directives (owner_id, title, goal, repository_url, local_path, base_branch) + VALUES ($1, $2, $3, $4, $5, $6) RETURNING * "#, ) @@ -4941,7 +4940,6 @@ pub async fn create_directive_for_owner( .bind(&req.repository_url) .bind(&req.local_path) .bind(&req.base_branch) - .bind(req.memory_enabled) .fetch_one(pool) .await } @@ -4994,7 +4992,7 @@ pub async fn list_directives_for_owner( SELECT d.id, d.owner_id, d.title, d.goal, d.status, d.repository_url, d.orchestrator_task_id, d.pr_url, d.completion_task_id, - d.memory_enabled, d.version, d.created_at, d.updated_at, + d.version, d.created_at, d.updated_at, COALESCE((SELECT COUNT(*) FROM directive_steps WHERE directive_id = d.id), 0) as total_steps, COALESCE((SELECT COUNT(*) FROM directive_steps WHERE directive_id = d.id AND status = 'completed'), 0) as completed_steps, COALESCE((SELECT COUNT(*) FROM directive_steps WHERE directive_id = d.id AND status = 'running'), 0) as running_steps, @@ -5048,13 +5046,12 @@ pub async fn update_directive_for_owner( let orchestrator_task_id = req.orchestrator_task_id.or(current.orchestrator_task_id); let pr_url = req.pr_url.as_deref().or(current.pr_url.as_deref()); let pr_branch = req.pr_branch.as_deref().or(current.pr_branch.as_deref()); - let memory_enabled = req.memory_enabled.unwrap_or(current.memory_enabled); let result = sqlx::query_as::<_, Directive>( r#" UPDATE directives SET title = $3, goal = $4, status = $5, repository_url = $6, local_path = $7, - base_branch = $8, orchestrator_task_id = $9, pr_url = $10, pr_branch = $11, memory_enabled = $12, + base_branch = $8, orchestrator_task_id = $9, pr_url = $10, pr_branch = $11, version = version + 1, updated_at = NOW() WHERE id = $1 AND owner_id = $2 RETURNING * @@ -5071,7 +5068,6 @@ pub async fn update_directive_for_owner( .bind(orchestrator_task_id) .bind(pr_url) .bind(pr_branch) - .bind(memory_enabled) .fetch_optional(pool) .await .map_err(RepositoryError::Database)?; @@ -5609,7 +5605,6 @@ pub struct StepForDispatch { pub directive_title: String, pub repository_url: Option<String>, pub base_branch: Option<String>, - pub memory_enabled: bool, /// The directive's PR branch (if a PR has already been created from previous steps). pub pr_branch: Option<String>, } @@ -5633,7 +5628,6 @@ pub async fn get_ready_steps_for_dispatch( d.title AS directive_title, d.repository_url, d.base_branch, - d.memory_enabled, d.pr_branch FROM directive_steps ds JOIN directives d ON d.id = ds.directive_id @@ -5895,128 +5889,3 @@ pub async fn get_directive_max_generation( Ok(row.0.unwrap_or(0)) } -// ============================================================================= -// Directive Memory CRUD -// ============================================================================= - -/// List all memories for a directive, optionally filtered by category. -pub async fn list_directive_memories( - pool: &PgPool, - directive_id: Uuid, - category: Option<&str>, -) -> Result<Vec<DirectiveMemory>, sqlx::Error> { - match category { - Some(cat) => { - sqlx::query_as::<_, DirectiveMemory>( - r#" - SELECT * FROM directive_memories - WHERE directive_id = $1 AND category = $2 - ORDER BY key - "#, - ) - .bind(directive_id) - .bind(cat) - .fetch_all(pool) - .await - } - None => { - sqlx::query_as::<_, DirectiveMemory>( - r#" - SELECT * FROM directive_memories - WHERE directive_id = $1 - ORDER BY key - "#, - ) - .bind(directive_id) - .fetch_all(pool) - .await - } - } -} - -/// Get a single memory entry by directive ID and key. -pub async fn get_directive_memory( - pool: &PgPool, - directive_id: Uuid, - key: &str, -) -> Result<Option<DirectiveMemory>, sqlx::Error> { - sqlx::query_as::<_, DirectiveMemory>( - r#" - SELECT * FROM directive_memories - WHERE directive_id = $1 AND key = $2 - "#, - ) - .bind(directive_id) - .bind(key) - .fetch_optional(pool) - .await -} - -/// Set (upsert) a memory entry for a directive. -pub async fn set_directive_memory( - pool: &PgPool, - directive_id: Uuid, - req: &SetDirectiveMemoryRequest, -) -> Result<DirectiveMemory, sqlx::Error> { - sqlx::query_as::<_, DirectiveMemory>( - r#" - INSERT INTO directive_memories (directive_id, key, value, category) - VALUES ($1, $2, $3, $4) - ON CONFLICT (directive_id, key) - DO UPDATE SET value = EXCLUDED.value, - category = EXCLUDED.category, - updated_at = NOW() - RETURNING * - "#, - ) - .bind(directive_id) - .bind(&req.key) - .bind(&req.value) - .bind(&req.category) - .fetch_one(pool) - .await -} - -/// Batch set memory entries for a directive. -pub async fn batch_set_directive_memories( - pool: &PgPool, - directive_id: Uuid, - memories: &[SetDirectiveMemoryRequest], -) -> Result<Vec<DirectiveMemory>, sqlx::Error> { - let mut results = Vec::with_capacity(memories.len()); - for mem in memories { - let result = set_directive_memory(pool, directive_id, mem).await?; - results.push(result); - } - Ok(results) -} - -/// Delete a single memory entry by key. -pub async fn delete_directive_memory( - pool: &PgPool, - directive_id: Uuid, - key: &str, -) -> Result<bool, sqlx::Error> { - let result = sqlx::query( - r#"DELETE FROM directive_memories WHERE directive_id = $1 AND key = $2"#, - ) - .bind(directive_id) - .bind(key) - .execute(pool) - .await?; - Ok(result.rows_affected() > 0) -} - -/// Delete all memory entries for a directive. -pub async fn clear_directive_memories( - pool: &PgPool, - directive_id: Uuid, -) -> Result<u64, sqlx::Error> { - let result = sqlx::query( - r#"DELETE FROM directive_memories WHERE directive_id = $1"#, - ) - .bind(directive_id) - .execute(pool) - .await?; - Ok(result.rows_affected()) -} diff --git a/makima/src/orchestration/directive.rs b/makima/src/orchestration/directive.rs index 344bdf5..bb573d4 100644 --- a/makima/src/orchestration/directive.rs +++ b/makima/src/orchestration/directive.rs @@ -9,7 +9,7 @@ use sqlx::PgPool; use uuid::Uuid; -use crate::db::models::{CreateTaskRequest, DirectiveMemory, UpdateTaskRequest}; +use crate::db::models::{CreateTaskRequest, UpdateTaskRequest}; use crate::db::repository; use crate::server::state::{DaemonCommand, SharedState}; @@ -44,24 +44,7 @@ impl DirectiveOrchestrator { "Directive needs planning — spawning planning task" ); - // Load memories if memory is enabled for this directive - let memories = if directive.memory_enabled { - match repository::list_directive_memories(&self.pool, directive.id, None).await { - Ok(m) => m, - Err(e) => { - tracing::warn!( - directive_id = %directive.id, - error = %e, - "Failed to load directive memories for planning — continuing without" - ); - vec![] - } - } - } else { - vec![] - }; - - let plan = build_planning_prompt(&directive, &[], 1, &memories); + let plan = build_planning_prompt(&directive, &[], 1); if let Err(e) = self .spawn_orchestrator_task( @@ -143,27 +126,6 @@ impl DirectiveOrchestrator { .as_deref() .unwrap_or("Execute the step described below."); - // Load memories if memory is enabled for this directive - let memory_context = if step.memory_enabled { - match repository::list_directive_memories(&self.pool, step.directive_id, None).await { - Ok(memories) if !memories.is_empty() => { - format!("\n\nMEMORY CONTEXT (from previous planning/execution cycles):\n{}\n", - format_memories_for_prompt(&memories)) - } - Ok(_) => String::new(), - Err(e) => { - tracing::warn!( - directive_id = %step.directive_id, - error = %e, - "Failed to load directive memories for execution — continuing without" - ); - String::new() - } - } - } else { - String::new() - }; - // Build merge instructions for additional dependencies (beyond the first) let merge_preamble = if dep_tasks.len() > 1 { use crate::daemon::worktree::{sanitize_name, short_uuid}; @@ -196,7 +158,6 @@ impl DirectiveOrchestrator { DESCRIPTION: {description}\n\n\ {merge_preamble}\ INSTRUCTIONS:\n{task_plan}\n\ - {memory_context}\ When done, the system will automatically mark this step as completed.\n\ If you cannot complete the task, report the failure clearly.", directive_title = step.directive_title, @@ -204,7 +165,6 @@ impl DirectiveOrchestrator { description = step.step_description.as_deref().unwrap_or("(none)"), merge_preamble = merge_preamble, task_plan = task_plan, - memory_context = memory_context, ); match self @@ -348,24 +308,7 @@ impl DirectiveOrchestrator { let generation = repository::get_directive_max_generation(&self.pool, directive.id).await? + 1; - // Load memories if memory is enabled for this directive - let memories = if directive.memory_enabled { - match repository::list_directive_memories(&self.pool, directive.id, None).await { - Ok(m) => m, - Err(e) => { - tracing::warn!( - directive_id = %directive.id, - error = %e, - "Failed to load directive memories for re-planning — continuing without" - ); - vec![] - } - } - } else { - vec![] - }; - - let plan = build_planning_prompt(&directive, &existing_steps, generation, &memories); + let plan = build_planning_prompt(&directive, &existing_steps, generation); if let Err(e) = self .spawn_orchestrator_task( @@ -747,35 +690,14 @@ impl DirectiveOrchestrator { } } -/// Format memory entries into a readable prompt section. -fn format_memories_for_prompt(memories: &[DirectiveMemory]) -> String { - let mut out = String::new(); - for memory in memories { - let cat = memory.category.as_deref().unwrap_or("other"); - out.push_str(&format!( - "- [{}] {}: {}\n", - cat, memory.key, memory.value - )); - } - out -} - /// Build the planning prompt for a directive. fn build_planning_prompt( directive: &crate::db::models::Directive, existing_steps: &[crate::db::models::DirectiveStep], generation: i32, - memories: &[DirectiveMemory], ) -> String { let mut prompt = String::new(); - // Include memory context if available - if !memories.is_empty() { - prompt.push_str("MEMORY CONTEXT (insights and decisions from previous cycles):\n"); - prompt.push_str(&format_memories_for_prompt(memories)); - prompt.push_str("\nUse these memories to inform your planning. Avoid repeating past mistakes and build on prior insights.\n\n"); - } - if !existing_steps.is_empty() { prompt.push_str(&format!( "EXISTING STEPS (generation {}):\n", diff --git a/makima/src/server/handlers/directives.rs b/makima/src/server/handlers/directives.rs index 9314031..25b2dc4 100644 --- a/makima/src/server/handlers/directives.rs +++ b/makima/src/server/handlers/directives.rs @@ -1,18 +1,17 @@ //! HTTP handlers for directive CRUD and DAG progression. use axum::{ - extract::{Path, Query, State}, + extract::{Path, State}, http::StatusCode, response::IntoResponse, Json, }; -use serde::Deserialize; use uuid::Uuid; use crate::db::models::{ - BatchSetDirectiveMemoryRequest, CleanupTasksResponse, CreateDirectiveRequest, - CreateDirectiveStepRequest, Directive, DirectiveListResponse, DirectiveMemory, - DirectiveMemoryListResponse, DirectiveStep, DirectiveWithSteps, SetDirectiveMemoryRequest, + CleanupTasksResponse, CreateDirectiveRequest, + CreateDirectiveStepRequest, Directive, DirectiveListResponse, + DirectiveStep, DirectiveWithSteps, UpdateDirectiveRequest, UpdateDirectiveStepRequest, UpdateGoalRequest, }; use crate::db::repository; @@ -20,12 +19,6 @@ use crate::server::auth::Authenticated; use crate::server::messages::ApiError; use crate::server::state::SharedState; -/// Query parameters for the memory list endpoint. -#[derive(Debug, Deserialize)] -pub struct MemoryListQuery { - pub category: Option<String>, -} - // ============================================================================= // Directive CRUD // ============================================================================= @@ -849,385 +842,6 @@ pub async fn update_goal( } // ============================================================================= -// Directive Memory CRUD -// ============================================================================= - -/// List all memories for a directive, optionally filtered by category. -#[utoipa::path( - get, - path = "/api/v1/directives/{id}/memories", - params( - ("id" = Uuid, Path, description = "Directive ID"), - ("category" = Option<String>, Query, description = "Filter by category"), - ), - responses( - (status = 200, description = "List of memories", body = DirectiveMemoryListResponse), - (status = 404, description = "Directive not found", body = ApiError), - (status = 503, description = "Database not configured", body = ApiError), - ), - security(("bearer_auth" = []), ("api_key" = [])), - tag = "Directives" -)] -pub async fn list_memories( - State(state): State<SharedState>, - Authenticated(auth): Authenticated, - Path(id): Path<Uuid>, - Query(query): Query<MemoryListQuery>, -) -> impl IntoResponse { - let Some(ref pool) = state.db_pool else { - return ( - StatusCode::SERVICE_UNAVAILABLE, - Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")), - ) - .into_response(); - }; - - // Verify directive ownership - match repository::get_directive_for_owner(pool, auth.owner_id, 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("GET_FAILED", &e.to_string())), - ) - .into_response(); - } - } - - match repository::list_directive_memories(pool, id, query.category.as_deref()).await { - Ok(memories) => { - let total = memories.len() as i64; - Json(DirectiveMemoryListResponse { memories, total }).into_response() - } - Err(e) => { - tracing::error!("Failed to list memories: {}", e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ApiError::new("LIST_FAILED", &e.to_string())), - ) - .into_response() - } - } -} - -/// Get a single memory entry by key. -#[utoipa::path( - get, - path = "/api/v1/directives/{id}/memories/{key}", - params( - ("id" = Uuid, Path, description = "Directive ID"), - ("key" = String, Path, description = "Memory key"), - ), - responses( - (status = 200, description = "Memory entry", body = DirectiveMemory), - (status = 404, description = "Not found", body = ApiError), - (status = 503, description = "Database not configured", body = ApiError), - ), - security(("bearer_auth" = []), ("api_key" = [])), - tag = "Directives" -)] -pub async fn get_memory( - State(state): State<SharedState>, - Authenticated(auth): Authenticated, - Path((id, key)): Path<(Uuid, String)>, -) -> impl IntoResponse { - let Some(ref pool) = state.db_pool else { - return ( - StatusCode::SERVICE_UNAVAILABLE, - Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")), - ) - .into_response(); - }; - - // Verify directive ownership - match repository::get_directive_for_owner(pool, auth.owner_id, 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("GET_FAILED", &e.to_string())), - ) - .into_response(); - } - } - - match repository::get_directive_memory(pool, id, &key).await { - Ok(Some(memory)) => Json(memory).into_response(), - Ok(None) => ( - StatusCode::NOT_FOUND, - Json(ApiError::new("NOT_FOUND", "Memory entry not found")), - ) - .into_response(), - Err(e) => { - tracing::error!("Failed to get memory: {}", e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ApiError::new("GET_FAILED", &e.to_string())), - ) - .into_response() - } - } -} - -/// Set (upsert) a single memory entry. -#[utoipa::path( - post, - path = "/api/v1/directives/{id}/memories", - params(("id" = Uuid, Path, description = "Directive ID")), - request_body = SetDirectiveMemoryRequest, - responses( - (status = 200, description = "Memory entry set", body = DirectiveMemory), - (status = 404, description = "Directive not found", body = ApiError), - (status = 503, description = "Database not configured", body = ApiError), - ), - security(("bearer_auth" = []), ("api_key" = [])), - tag = "Directives" -)] -pub async fn set_memory( - State(state): State<SharedState>, - Authenticated(auth): Authenticated, - Path(id): Path<Uuid>, - Json(req): Json<SetDirectiveMemoryRequest>, -) -> impl IntoResponse { - let Some(ref pool) = state.db_pool else { - return ( - StatusCode::SERVICE_UNAVAILABLE, - Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")), - ) - .into_response(); - }; - - // Verify directive ownership - match repository::get_directive_for_owner(pool, auth.owner_id, 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("GET_FAILED", &e.to_string())), - ) - .into_response(); - } - } - - match repository::set_directive_memory(pool, id, &req).await { - Ok(memory) => Json(memory).into_response(), - Err(e) => { - tracing::error!("Failed to set memory: {}", e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ApiError::new("SET_FAILED", &e.to_string())), - ) - .into_response() - } - } -} - -/// Batch set multiple memory entries. -#[utoipa::path( - post, - path = "/api/v1/directives/{id}/memories/batch", - params(("id" = Uuid, Path, description = "Directive ID")), - request_body = BatchSetDirectiveMemoryRequest, - responses( - (status = 200, description = "Memory entries set", body = Vec<DirectiveMemory>), - (status = 404, description = "Directive not found", body = ApiError), - (status = 503, description = "Database not configured", body = ApiError), - ), - security(("bearer_auth" = []), ("api_key" = [])), - tag = "Directives" -)] -pub async fn batch_set_memories( - State(state): State<SharedState>, - Authenticated(auth): Authenticated, - Path(id): Path<Uuid>, - Json(req): Json<BatchSetDirectiveMemoryRequest>, -) -> impl IntoResponse { - let Some(ref pool) = state.db_pool else { - return ( - StatusCode::SERVICE_UNAVAILABLE, - Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")), - ) - .into_response(); - }; - - // Verify directive ownership - match repository::get_directive_for_owner(pool, auth.owner_id, 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("GET_FAILED", &e.to_string())), - ) - .into_response(); - } - } - - match repository::batch_set_directive_memories(pool, id, &req.entries).await { - Ok(memories) => Json(memories).into_response(), - Err(e) => { - tracing::error!("Failed to batch set memories: {}", e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ApiError::new("SET_FAILED", &e.to_string())), - ) - .into_response() - } - } -} - -/// Delete a single memory entry by key. -#[utoipa::path( - delete, - path = "/api/v1/directives/{id}/memories/{key}", - params( - ("id" = Uuid, Path, description = "Directive ID"), - ("key" = String, Path, description = "Memory key"), - ), - responses( - (status = 204, description = "Deleted"), - (status = 404, description = "Not found", body = ApiError), - (status = 503, description = "Database not configured", body = ApiError), - ), - security(("bearer_auth" = []), ("api_key" = [])), - tag = "Directives" -)] -pub async fn delete_memory( - State(state): State<SharedState>, - Authenticated(auth): Authenticated, - Path((id, key)): Path<(Uuid, String)>, -) -> impl IntoResponse { - let Some(ref pool) = state.db_pool else { - return ( - StatusCode::SERVICE_UNAVAILABLE, - Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")), - ) - .into_response(); - }; - - // Verify directive ownership - match repository::get_directive_for_owner(pool, auth.owner_id, 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("GET_FAILED", &e.to_string())), - ) - .into_response(); - } - } - - match repository::delete_directive_memory(pool, id, &key).await { - Ok(true) => StatusCode::NO_CONTENT.into_response(), - Ok(false) => ( - StatusCode::NOT_FOUND, - Json(ApiError::new("NOT_FOUND", "Memory entry not found")), - ) - .into_response(), - Err(e) => { - tracing::error!("Failed to delete memory: {}", e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ApiError::new("DELETE_FAILED", &e.to_string())), - ) - .into_response() - } - } -} - -/// Clear all memories for a directive. -#[utoipa::path( - delete, - path = "/api/v1/directives/{id}/memories", - params(("id" = Uuid, Path, description = "Directive ID")), - responses( - (status = 204, description = "All memories cleared"), - (status = 404, description = "Directive not found", body = ApiError), - (status = 503, description = "Database not configured", body = ApiError), - ), - security(("bearer_auth" = []), ("api_key" = [])), - tag = "Directives" -)] -pub async fn clear_memories( - State(state): State<SharedState>, - Authenticated(auth): Authenticated, - Path(id): Path<Uuid>, -) -> impl IntoResponse { - let Some(ref pool) = state.db_pool else { - return ( - StatusCode::SERVICE_UNAVAILABLE, - Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")), - ) - .into_response(); - }; - - // Verify directive ownership - match repository::get_directive_for_owner(pool, auth.owner_id, 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("GET_FAILED", &e.to_string())), - ) - .into_response(); - } - } - - match repository::clear_directive_memories(pool, id).await { - Ok(_) => StatusCode::NO_CONTENT.into_response(), - Err(e) => { - tracing::error!("Failed to clear memories: {}", e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ApiError::new("CLEAR_FAILED", &e.to_string())), - ) - .into_response() - } - } -} - -// ============================================================================= // Task Cleanup // ============================================================================= diff --git a/makima/src/server/mod.rs b/makima/src/server/mod.rs index 7110ef8..c1e1309 100644 --- a/makima/src/server/mod.rs +++ b/makima/src/server/mod.rs @@ -238,10 +238,6 @@ pub fn make_router(state: SharedState) -> Router { .route("/directives/{id}/steps/{step_id}/skip", post(directives::skip_step)) .route("/directives/{id}/goal", put(directives::update_goal)) .route("/directives/{id}/cleanup-tasks", post(directives::cleanup_tasks)) - // Directive memory endpoints - .route("/directives/{id}/memories", get(directives::list_memories).post(directives::set_memory).delete(directives::clear_memories)) - .route("/directives/{id}/memories/batch", post(directives::batch_set_memories)) - .route("/directives/{id}/memories/{key}", get(directives::get_memory).delete(directives::delete_memory)) // Timeline endpoint (unified history for user) .route("/timeline", get(history::get_timeline)) // Contract type templates (built-in only) diff --git a/makima/src/server/openapi.rs b/makima/src/server/openapi.rs index f28b105..e68286e 100644 --- a/makima/src/server/openapi.rs +++ b/makima/src/server/openapi.rs @@ -3,7 +3,7 @@ use utoipa::OpenApi; use crate::db::models::{ - AddLocalRepositoryRequest, AddRemoteRepositoryRequest, BatchSetDirectiveMemoryRequest, + AddLocalRepositoryRequest, AddRemoteRepositoryRequest, BranchInfo, BranchListResponse, BranchTaskRequest, BranchTaskResponse, ChangePhaseRequest, Contract, ContractChatHistoryResponse, ContractChatMessageRecord, ContractEvent, @@ -11,14 +11,14 @@ use crate::db::models::{ CleanupTasksResponse, CreateContractRequest, CreateDirectiveRequest, CreateDirectiveStepRequest, CreateFileRequest, CreateManagedRepositoryRequest, CreateTaskRequest, Daemon, DaemonDirectoriesResponse, - DaemonDirectory, DaemonListResponse, Directive, DirectiveListResponse, DirectiveMemory, - DirectiveMemoryListResponse, DirectiveStep, DirectiveSummary, DirectiveWithSteps, + DaemonDirectory, DaemonListResponse, Directive, DirectiveListResponse, + DirectiveStep, DirectiveSummary, DirectiveWithSteps, File, FileListResponse, FileSummary, MergeCommitRequest, MergeCompleteCheckResponse, MergeResolveRequest, MergeResultResponse, MergeSkipRequest, MergeStartRequest, MergeStatusResponse, MeshChatConversation, MeshChatHistoryResponse, MeshChatMessageRecord, RepositoryHistoryEntry, RepositoryHistoryListResponse, RepositorySuggestionsQuery, SendMessageRequest, - SetDirectiveMemoryRequest, Task, + Task, TaskEventListResponse, TaskListResponse, TaskSummary, TaskWithSubtasks, TranscriptEntry, UpdateContractRequest, UpdateDirectiveRequest, UpdateDirectiveStepRequest, UpdateFileRequest, UpdateGoalRequest, UpdateTaskRequest, @@ -125,13 +125,6 @@ use crate::server::messages::{ApiError, AudioEncoding, StartMessage, StopMessage directives::skip_step, directives::update_goal, directives::cleanup_tasks, - // Directive memory endpoints - directives::list_memories, - directives::get_memory, - directives::set_memory, - directives::batch_set_memories, - directives::delete_memory, - directives::clear_memories, // Repository history/settings endpoints repository_history::list_repository_history, repository_history::get_repository_suggestions, @@ -229,10 +222,6 @@ use crate::server::messages::{ApiError, AudioEncoding, StartMessage, StopMessage CreateDirectiveStepRequest, UpdateDirectiveStepRequest, CleanupTasksResponse, - DirectiveMemory, - DirectiveMemoryListResponse, - SetDirectiveMemoryRequest, - BatchSetDirectiveMemoryRequest, // Repository history schemas RepositoryHistoryEntry, RepositoryHistoryListResponse, |
