summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--makima/frontend/src/components/directives/DirectiveDetail.tsx230
-rw-r--r--makima/frontend/src/hooks/useDirectiveMemories.ts86
-rw-r--r--makima/frontend/src/lib/api.ts176
-rw-r--r--makima/frontend/tsconfig.tsbuildinfo2
-rw-r--r--makima/migrations/20260213000000_drop_directive_memories.sql3
-rw-r--r--makima/src/bin/makima.rs44
-rw-r--r--makima/src/daemon/api/directive.rs199
-rw-r--r--makima/src/daemon/cli/directive.rs47
-rw-r--r--makima/src/daemon/cli/mod.rs18
-rw-r--r--makima/src/daemon/task/manager.rs144
-rw-r--r--makima/src/daemon/worktree/manager.rs79
-rw-r--r--makima/src/db/models.rs45
-rw-r--r--makima/src/db/repository.rs143
-rw-r--r--makima/src/orchestration/directive.rs84
-rw-r--r--makima/src/server/handlers/directives.rs394
-rw-r--r--makima/src/server/mod.rs4
-rw-r--r--makima/src/server/openapi.rs19
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,