summaryrefslogtreecommitdiff
path: root/makima
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-02 02:34:50 +0000
committersoryu <soryu@soryu.co>2026-02-02 02:34:50 +0000
commit151e9d87e117b7980e6aad522ac8f3633eeca87a (patch)
treee80fb4301361b3b12e5abf8e442603db2d0622dc /makima
parenta2c147ddd59f55a07b5be0c8970169726b55c876 (diff)
downloadsoryu-151e9d87e117b7980e6aad522ac8f3633eeca87a.tar.gz
soryu-151e9d87e117b7980e6aad522ac8f3633eeca87a.zip
Make makima more opinionated and structured
Diffstat (limited to 'makima')
-rw-r--r--makima/frontend/src/components/NavStrip.tsx1
-rw-r--r--makima/frontend/src/components/contracts/ContractList.tsx5
-rw-r--r--makima/frontend/src/components/templates/TemplateEditor.tsx257
-rw-r--r--makima/frontend/src/lib/api.ts110
-rw-r--r--makima/frontend/src/main.tsx9
-rw-r--r--makima/frontend/src/routes/contracts.tsx58
-rw-r--r--makima/frontend/src/routes/templates.tsx388
-rw-r--r--makima/frontend/src/types/templates.ts90
-rw-r--r--makima/frontend/tsconfig.tsbuildinfo2
-rw-r--r--makima/migrations/20260202000000_remove_red_team_system.sql15
-rw-r--r--makima/migrations/20260202100000_add_contract_worktree.sql13
-rw-r--r--makima/migrations/20260202200000_remove_custom_templates.sql4
-rw-r--r--makima/src/bin/makima.rs16
-rw-r--r--makima/src/daemon/api/contract.rs4
-rw-r--r--makima/src/daemon/api/mod.rs2
-rw-r--r--makima/src/daemon/api/red_team.rs39
-rw-r--r--makima/src/daemon/api/supervisor.rs4
-rw-r--r--makima/src/daemon/cli/mod.rs57
-rw-r--r--makima/src/daemon/cli/red_team.rs26
-rw-r--r--makima/src/daemon/cli/supervisor.rs4
-rw-r--r--makima/src/daemon/task/manager.rs658
-rw-r--r--makima/src/db/models.rs94
-rw-r--r--makima/src/db/repository.rs131
-rw-r--r--makima/src/server/handlers/contract_chat.rs10
-rw-r--r--makima/src/server/handlers/contracts.rs5
-rw-r--r--makima/src/server/handlers/mesh.rs4
-rw-r--r--makima/src/server/handlers/mesh_chat.rs1
-rw-r--r--makima/src/server/handlers/mesh_daemon.rs62
-rw-r--r--makima/src/server/handlers/mesh_red_team.rs497
-rw-r--r--makima/src/server/handlers/mesh_supervisor.rs306
-rw-r--r--makima/src/server/handlers/mod.rs1
-rw-r--r--makima/src/server/handlers/templates.rs419
-rw-r--r--makima/src/server/handlers/transcript_analysis.rs4
-rw-r--r--makima/src/server/mod.rs18
34 files changed, 297 insertions, 3017 deletions
diff --git a/makima/frontend/src/components/NavStrip.tsx b/makima/frontend/src/components/NavStrip.tsx
index f44799b..fb95c7f 100644
--- a/makima/frontend/src/components/NavStrip.tsx
+++ b/makima/frontend/src/components/NavStrip.tsx
@@ -14,7 +14,6 @@ const NAV_LINKS: NavLink[] = [
{ label: "Board", href: "/workflow", requiresAuth: true },
{ label: "Mesh", href: "/mesh", requiresAuth: true },
{ label: "History", href: "/history", requiresAuth: true },
- { label: "Templates", href: "/templates", requiresAuth: true },
];
export function NavStrip() {
diff --git a/makima/frontend/src/components/contracts/ContractList.tsx b/makima/frontend/src/components/contracts/ContractList.tsx
index 532ab87..98f8ff6 100644
--- a/makima/frontend/src/components/contracts/ContractList.tsx
+++ b/makima/frontend/src/components/contracts/ContractList.tsx
@@ -136,11 +136,6 @@ export function ContractList({
Local
</span>
)}
- {contract.redTeamEnabled && (
- <span className="px-1.5 py-0.5 font-mono text-[9px] uppercase text-cyan-400 border border-cyan-400/30 bg-cyan-400/10 shrink-0" title="Red Team monitoring enabled">
- 🔍 Red Team
- </span>
- )}
</div>
<span
className={`text-[10px] font-mono uppercase shrink-0 ${
diff --git a/makima/frontend/src/components/templates/TemplateEditor.tsx b/makima/frontend/src/components/templates/TemplateEditor.tsx
deleted file mode 100644
index c8e1f98..0000000
--- a/makima/frontend/src/components/templates/TemplateEditor.tsx
+++ /dev/null
@@ -1,257 +0,0 @@
-import { useState } from "react";
-import type { ContractTemplate, Phase, Deliverable } from "../../types/templates";
-
-interface Props {
- template: ContractTemplate;
- onSave: (template: ContractTemplate) => void;
- onCancel: () => void;
- readOnly?: boolean;
-}
-
-export function TemplateEditor({ template, onSave, onCancel, readOnly = false }: Props) {
- const [editedTemplate, setEditedTemplate] = useState<ContractTemplate>({
- ...template,
- phases: template.phases.map((p) => ({
- ...p,
- deliverables: [...p.deliverables],
- })),
- });
- const [newDeliverableName, setNewDeliverableName] = useState<{
- [phaseId: string]: string;
- }>({});
-
- const handlePhaseNameChange = (phaseId: string, newName: string) => {
- setEditedTemplate((prev) => ({
- ...prev,
- phases: prev.phases.map((p) =>
- p.id === phaseId ? { ...p, name: newName } : p
- ),
- }));
- };
-
- const handleDeliverableNameChange = (
- phaseId: string,
- deliverableId: string,
- newName: string
- ) => {
- setEditedTemplate((prev) => ({
- ...prev,
- phases: prev.phases.map((p) =>
- p.id === phaseId
- ? {
- ...p,
- deliverables: p.deliverables.map((d) =>
- d.id === deliverableId ? { ...d, name: newName } : d
- ),
- }
- : p
- ),
- }));
- };
-
- const handleAddDeliverable = (phaseId: string) => {
- const name = newDeliverableName[phaseId]?.trim();
- if (!name) return;
-
- const newDeliverable: Deliverable = {
- id: `deliverable-${Date.now()}`,
- name,
- };
-
- setEditedTemplate((prev) => ({
- ...prev,
- phases: prev.phases.map((p) =>
- p.id === phaseId
- ? { ...p, deliverables: [...p.deliverables, newDeliverable] }
- : p
- ),
- }));
- setNewDeliverableName((prev) => ({ ...prev, [phaseId]: "" }));
- };
-
- const handleRemoveDeliverable = (phaseId: string, deliverableId: string) => {
- setEditedTemplate((prev) => ({
- ...prev,
- phases: prev.phases.map((p) =>
- p.id === phaseId
- ? {
- ...p,
- deliverables: p.deliverables.filter((d) => d.id !== deliverableId),
- }
- : p
- ),
- }));
- };
-
- const handleAddPhase = () => {
- const newPhase: Phase = {
- id: `phase-${Date.now()}`,
- name: "New Phase",
- deliverables: [],
- };
- setEditedTemplate((prev) => ({
- ...prev,
- phases: [...prev.phases, newPhase],
- }));
- };
-
- const handleRemovePhase = (phaseId: string) => {
- setEditedTemplate((prev) => ({
- ...prev,
- phases: prev.phases.filter((p) => p.id !== phaseId),
- }));
- };
-
- return (
- <div className="bg-[#0d1b2d] border border-[rgba(117,170,252,0.35)] p-6">
- {/* Header */}
- <div className="mb-6 pb-4 border-b border-[rgba(117,170,252,0.15)]">
- <h2 className="text-sm font-mono uppercase tracking-wide text-[#9bc3ff] mb-1">
- {readOnly ? "View" : "Edit"} Template: {template.name}
- </h2>
- <p className="text-xs font-mono text-[#75aafc] opacity-70">
- {template.description}
- </p>
- {readOnly && (
- <p className="text-xs font-mono text-amber-400 mt-2">
- Built-in templates are read-only
- </p>
- )}
- </div>
-
- {/* Phases */}
- <div className="space-y-4 mb-6">
- {editedTemplate.phases.map((phase, phaseIndex) => (
- <div
- key={phase.id}
- className="bg-[rgba(0,0,0,0.3)] border border-[rgba(117,170,252,0.2)] p-4"
- >
- {/* Phase Header */}
- <div className="flex items-center gap-3 mb-3">
- <span className="w-6 h-6 flex items-center justify-center bg-[rgba(117,170,252,0.2)] text-[#9bc3ff] text-xs font-mono">
- {phaseIndex + 1}
- </span>
- <input
- type="text"
- className="flex-1 px-3 py-1.5 bg-transparent border border-[rgba(117,170,252,0.25)] text-white font-mono text-sm placeholder-[#556677] focus:outline-none focus:border-[#3f6fb3] disabled:opacity-60"
- value={phase.name}
- onChange={(e) => handlePhaseNameChange(phase.id, e.target.value)}
- placeholder="Phase name"
- disabled={readOnly}
- />
- {!template.isBuiltIn && (
- <button
- type="button"
- onClick={() => handleRemovePhase(phase.id)}
- className="w-7 h-7 flex items-center justify-center border border-[rgba(255,100,100,0.3)] text-[#ff6464] hover:bg-[rgba(255,100,100,0.1)] transition-colors text-sm"
- title="Remove phase"
- >
- x
- </button>
- )}
- </div>
-
- {/* Deliverables */}
- <div className="ml-9 space-y-2">
- {phase.deliverables.length === 0 ? (
- <div className="text-xs font-mono text-[#556677] italic">
- No deliverables
- </div>
- ) : (
- phase.deliverables.map((deliverable) => (
- <div
- key={deliverable.id}
- className="flex items-center gap-2"
- >
- <span className="text-[#75aafc] text-xs">-</span>
- <input
- type="text"
- className="flex-1 px-2 py-1 bg-transparent border border-[rgba(117,170,252,0.15)] text-[#dbe7ff] font-mono text-xs focus:outline-none focus:border-[#3f6fb3]"
- value={deliverable.name}
- onChange={(e) =>
- handleDeliverableNameChange(
- phase.id,
- deliverable.id,
- e.target.value
- )
- }
- />
- <button
- type="button"
- onClick={() =>
- handleRemoveDeliverable(phase.id, deliverable.id)
- }
- className="w-5 h-5 flex items-center justify-center text-[#ff6464] hover:bg-[rgba(255,100,100,0.1)] transition-colors text-xs"
- title="Remove deliverable"
- >
- x
- </button>
- </div>
- ))
- )}
-
- {/* Add Deliverable */}
- <div className="flex items-center gap-2 pt-2">
- <input
- type="text"
- className="flex-1 px-2 py-1 bg-transparent border border-[rgba(117,170,252,0.15)] text-[#dbe7ff] font-mono text-xs placeholder-[#445566] focus:outline-none focus:border-[#3f6fb3]"
- placeholder="New deliverable name..."
- value={newDeliverableName[phase.id] || ""}
- onChange={(e) =>
- setNewDeliverableName((prev) => ({
- ...prev,
- [phase.id]: e.target.value,
- }))
- }
- onKeyPress={(e) => {
- if (e.key === "Enter") {
- handleAddDeliverable(phase.id);
- }
- }}
- />
- <button
- type="button"
- onClick={() => handleAddDeliverable(phase.id)}
- className="px-2 py-1 border border-[rgba(117,170,252,0.3)] text-[#9bc3ff] font-mono text-xs hover:border-[#3f6fb3] hover:bg-[rgba(117,170,252,0.1)] transition-colors"
- >
- + Add
- </button>
- </div>
- </div>
- </div>
- ))}
- </div>
-
- {/* Add Phase (only for custom templates) */}
- {!template.isBuiltIn && (
- <button
- type="button"
- onClick={handleAddPhase}
- className="w-full mb-6 px-4 py-2 border border-dashed border-[rgba(117,170,252,0.3)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:border-[#3f6fb3] hover:bg-[rgba(117,170,252,0.05)] transition-colors"
- >
- + Add Phase
- </button>
- )}
-
- {/* Footer Actions */}
- <div className="flex gap-3 justify-end pt-4 border-t border-[rgba(117,170,252,0.15)]">
- <button
- type="button"
- onClick={onCancel}
- className="px-4 py-2 border border-[rgba(117,170,252,0.25)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:border-[#3f6fb3] transition-colors"
- >
- {readOnly ? "Close" : "Cancel"}
- </button>
- {!readOnly && (
- <button
- type="button"
- onClick={() => onSave(editedTemplate)}
- className="px-4 py-2 border border-[#3f6fb3] bg-[rgba(117,170,252,0.15)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:bg-[rgba(117,170,252,0.25)] transition-colors"
- >
- Save Changes
- </button>
- )}
- </div>
- </div>
- );
-}
diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts
index e8b3d8a..f148d76 100644
--- a/makima/frontend/src/lib/api.ts
+++ b/makima/frontend/src/lib/api.ts
@@ -1659,45 +1659,9 @@ export interface DeliverableDefinition {
priority: "required" | "recommended" | "optional";
}
-/** Request to create a custom contract type template */
-export interface CreateTemplateRequest {
- name: string;
- description?: string;
- phases: PhaseDefinition[];
- defaultPhase: string;
- deliverables?: Record<string, DeliverableDefinition[]>;
-}
-
-/** Request to update a custom contract type template */
-export interface UpdateTemplateRequest {
- name?: string;
- description?: string;
- phases?: PhaseDefinition[];
- defaultPhase?: string;
- deliverables?: Record<string, DeliverableDefinition[]>;
- version?: number;
-}
-
-/** Custom template record from the API */
-export interface ContractTypeTemplateRecord {
- id: string;
- name: string;
- description: string | null;
- phases: PhaseDefinition[];
- defaultPhase: string;
- isBuiltin: boolean;
- version: number;
- createdAt: string;
-}
-
-/** Response for single template operations */
-export interface TemplateResponse {
- template: ContractTypeTemplateRecord;
-}
-
/**
- * List available contract types/templates.
- * Returns built-in types (simple, specification) and any custom types.
+ * List available contract types.
+ * Returns built-in types only (simple, specification, execute).
*/
export async function listContractTypes(): Promise<ListContractTypesResponse> {
const res = await authFetch(`${API_BASE}/api/v1/contract-types`);
@@ -1707,66 +1671,6 @@ export async function listContractTypes(): Promise<ListContractTypesResponse> {
return res.json();
}
-/**
- * Create a new custom contract type template.
- */
-export async function createContractTemplate(
- req: CreateTemplateRequest
-): Promise<TemplateResponse> {
- const res = await authFetch(`${API_BASE}/api/v1/contract-types`, {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(req),
- });
- if (!res.ok) {
- const err = await res.json().catch(() => ({ message: res.statusText }));
- throw new Error(err.message || `Failed to create template: ${res.statusText}`);
- }
- return res.json();
-}
-
-/**
- * Get a custom contract type template by ID.
- */
-export async function getContractTemplate(id: string): Promise<TemplateResponse> {
- const res = await authFetch(`${API_BASE}/api/v1/contract-types/${id}`);
- if (!res.ok) {
- throw new Error(`Failed to get template: ${res.statusText}`);
- }
- return res.json();
-}
-
-/**
- * Update a custom contract type template.
- */
-export async function updateContractTemplate(
- id: string,
- req: UpdateTemplateRequest
-): Promise<TemplateResponse> {
- const res = await authFetch(`${API_BASE}/api/v1/contract-types/${id}`, {
- method: "PUT",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(req),
- });
- if (!res.ok) {
- const err = await res.json().catch(() => ({ message: res.statusText }));
- throw new Error(err.message || `Failed to update template: ${res.statusText}`);
- }
- return res.json();
-}
-
-/**
- * Delete a custom contract type template.
- */
-export async function deleteContractTemplate(id: string): Promise<void> {
- const res = await authFetch(`${API_BASE}/api/v1/contract-types/${id}`, {
- method: "DELETE",
- });
- if (!res.ok) {
- throw new Error(`Failed to delete template: ${res.statusText}`);
- }
-}
-
export interface ContractRepository {
id: string;
contractId: string;
@@ -1792,8 +1696,6 @@ export interface ContractSummary {
supervisorTaskId: string | null;
/** When true, tasks won't auto-push or create PRs - use patch files instead */
localOnly: boolean;
- /** When true, a red team task monitors work output for quality */
- redTeamEnabled: boolean;
fileCount: number;
taskCount: number;
repositoryCount: number;
@@ -1818,10 +1720,6 @@ export interface Contract {
phaseGuard: boolean;
/** When true, tasks won't auto-push or create PRs - use patch files instead */
localOnly: boolean;
- /** When true, a red team task monitors work output for quality */
- redTeamEnabled: boolean;
- /** Custom criteria for the red team to evaluate */
- redTeamPrompt: string | null;
version: number;
createdAt: string;
updatedAt: string;
@@ -1859,10 +1757,6 @@ export interface CreateContractRequest {
initialPhase?: ContractPhase | string;
/** When true, tasks won't auto-push or create PRs - use patch files instead */
localOnly?: boolean;
- /** When true, spawn a red team task to monitor work output */
- redTeamEnabled?: boolean;
- /** Custom criteria for the red team to evaluate */
- redTeamPrompt?: string;
}
export interface UpdateContractRequest {
diff --git a/makima/frontend/src/main.tsx b/makima/frontend/src/main.tsx
index ef1ba5c..50fffe4 100644
--- a/makima/frontend/src/main.tsx
+++ b/makima/frontend/src/main.tsx
@@ -18,7 +18,6 @@ import HistoryPage from "./routes/history";
import LoginPage from "./routes/login";
import SettingsPage from "./routes/settings";
import ContractFilePage from "./routes/contract-file";
-import TemplatesPage from "./routes/templates";
import SpeakPage from "./routes/speak";
createRoot(document.getElementById("root")!).render(
@@ -129,14 +128,6 @@ createRoot(document.getElementById("root")!).render(
}
/>
<Route
- path="/templates"
- element={
- <ProtectedRoute>
- <TemplatesPage />
- </ProtectedRoute>
- }
- />
- <Route
path="/speak"
element={
<ProtectedRoute>
diff --git a/makima/frontend/src/routes/contracts.tsx b/makima/frontend/src/routes/contracts.tsx
index 8dcfe34..dde78b1 100644
--- a/makima/frontend/src/routes/contracts.tsx
+++ b/makima/frontend/src/routes/contracts.tsx
@@ -93,8 +93,6 @@ function ContractsPageContent() {
const [contractTypes, setContractTypes] = useState<ContractTypeTemplate[]>([]);
const [contractTypesLoading, setContractTypesLoading] = useState(false);
const [localOnly, setLocalOnly] = useState(false);
- const [redTeamEnabled, setRedTeamEnabled] = useState(false);
- const [redTeamPrompt, setRedTeamPrompt] = useState("");
// Fetch contract types when modal opens - API returns both built-in and custom templates
useEffect(() => {
@@ -238,8 +236,6 @@ function ContractsPageContent() {
templateId: isCustomTemplate ? contractType : undefined,
initialPhase: initialPhase !== defaultPhaseForType ? initialPhase : undefined,
localOnly: localOnly || undefined,
- redTeamEnabled: redTeamEnabled || undefined,
- redTeamPrompt: redTeamEnabled && redTeamPrompt.trim() ? redTeamPrompt.trim() : undefined,
};
try {
@@ -315,8 +311,6 @@ function ContractsPageContent() {
setRepoUrl("");
setRepoPath("");
setLocalOnly(false);
- setRedTeamEnabled(false);
- setRedTeamPrompt("");
setCreateError(null);
}, []);
@@ -689,58 +683,6 @@ function ContractsPageContent() {
</div>
{/* Red Team Monitoring */}
- <div className="border-t border-[rgba(117,170,252,0.2)] pt-4">
- <div className="flex items-center gap-3">
- <button
- type="button"
- onClick={() => setRedTeamEnabled(!redTeamEnabled)}
- className={`w-5 h-5 flex items-center justify-center border transition-colors ${
- redTeamEnabled
- ? "bg-[#0f3c78] border-[#75aafc] text-[#dbe7ff]"
- : "bg-[#0d1b2d] border-[#3f6fb3] text-transparent"
- }`}
- >
- {redTeamEnabled && (
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="12"
- height="12"
- viewBox="0 0 24 24"
- fill="none"
- stroke="currentColor"
- strokeWidth="3"
- strokeLinecap="round"
- strokeLinejoin="round"
- >
- <polyline points="20 6 9 17 4 12" />
- </svg>
- )}
- </button>
- <label
- className="font-mono text-sm text-[#dbe7ff] cursor-pointer select-none"
- onClick={() => setRedTeamEnabled(!redTeamEnabled)}
- >
- Enable Red Team Monitoring
- </label>
- </div>
- <p className="font-mono text-xs text-[#8b949e] pl-8">
- Spawns a parallel task to monitor work output for quality and compliance.
- </p>
- {redTeamEnabled && (
- <div className="mt-3 pl-8">
- <label className="block font-mono text-xs text-[#75aafc] uppercase mb-2">
- Custom Review Criteria (Optional)
- </label>
- <textarea
- value={redTeamPrompt}
- onChange={(e) => setRedTeamPrompt(e.target.value)}
- placeholder="e.g., 'Focus on security best practices' or 'Ensure all functions have tests'"
- className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] text-sm font-mono h-20 resize-none focus:border-[#75aafc] focus:outline-none"
- />
- </div>
- )}
- </div>
-
{/* Repository Configuration */}
<div className="border-t border-[rgba(117,170,252,0.2)] pt-4">
<label className="block font-mono text-xs text-[#75aafc] uppercase mb-3">
diff --git a/makima/frontend/src/routes/templates.tsx b/makima/frontend/src/routes/templates.tsx
deleted file mode 100644
index b2c9974..0000000
--- a/makima/frontend/src/routes/templates.tsx
+++ /dev/null
@@ -1,388 +0,0 @@
-import { useState, useEffect, useCallback } from "react";
-import { useNavigate } from "react-router";
-import { Masthead } from "../components/Masthead";
-import { TemplateEditor } from "../components/templates/TemplateEditor";
-import { useAuth } from "../contexts/AuthContext";
-import type { ContractTemplate } from "../types/templates";
-import { DEFAULT_TEMPLATES } from "../types/templates";
-import {
- listContractTypes,
- createContractTemplate,
- updateContractTemplate,
- deleteContractTemplate,
- type PhaseDefinition,
- type DeliverableDefinition,
-} from "../lib/api";
-
-export default function TemplatesPage() {
- const navigate = useNavigate();
- const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth();
-
- const [templates, setTemplates] = useState<ContractTemplate[]>(DEFAULT_TEMPLATES);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState<string | null>(null);
- const [saving, setSaving] = useState(false);
-
- const [editingTemplate, setEditingTemplate] = useState<ContractTemplate | null>(
- null
- );
- const [showNewTemplateForm, setShowNewTemplateForm] = useState(false);
- const [newTemplateName, setNewTemplateName] = useState("");
- const [newTemplateDescription, setNewTemplateDescription] = useState("");
-
- // Redirect to login if not authenticated
- useEffect(() => {
- if (!authLoading && isAuthConfigured && !isAuthenticated) {
- navigate("/login");
- }
- }, [authLoading, isAuthConfigured, isAuthenticated, navigate]);
-
- // Fetch templates from API
- const fetchTemplates = useCallback(async () => {
- try {
- setLoading(true);
- setError(null);
- const response = await listContractTypes();
-
- // Convert API response to ContractTemplate format
- const apiTemplates: ContractTemplate[] = response.contractTypes.map((t) => ({
- id: t.id,
- name: t.name,
- description: t.description,
- isBuiltIn: t.isBuiltin,
- phases: t.phases.map((phaseId) => ({
- id: phaseId,
- name: t.phaseNames?.[phaseId] || phaseId.charAt(0).toUpperCase() + phaseId.slice(1),
- deliverables: [], // Deliverables are managed server-side
- })),
- }));
-
- // Merge with DEFAULT_TEMPLATES to ensure we have full phase/deliverable info for built-ins
- const mergedTemplates = apiTemplates.map((apiTemplate) => {
- const defaultTemplate = DEFAULT_TEMPLATES.find((d) => d.id === apiTemplate.id);
- if (defaultTemplate && apiTemplate.isBuiltIn) {
- return defaultTemplate; // Use the richer default template for built-ins
- }
- return apiTemplate;
- });
-
- setTemplates(mergedTemplates);
- } catch (err) {
- console.error("Failed to fetch templates:", err);
- setError(err instanceof Error ? err.message : "Failed to fetch templates");
- // Fall back to default templates
- setTemplates(DEFAULT_TEMPLATES);
- } finally {
- setLoading(false);
- }
- }, []);
-
- useEffect(() => {
- if (!authLoading && isAuthenticated) {
- fetchTemplates();
- } else if (!authLoading && !isAuthConfigured) {
- // No auth configured, just show defaults
- setTemplates(DEFAULT_TEMPLATES);
- setLoading(false);
- }
- }, [authLoading, isAuthenticated, isAuthConfigured, fetchTemplates]);
-
- const handleSaveTemplate = async (updatedTemplate: ContractTemplate) => {
- if (updatedTemplate.isBuiltIn) {
- // Built-in templates are read-only, just close the editor
- setEditingTemplate(null);
- return;
- }
-
- try {
- setSaving(true);
- setError(null);
-
- // Convert to API format
- const phases: PhaseDefinition[] = updatedTemplate.phases.map((p, index) => ({
- id: p.id,
- name: p.name,
- order: index,
- }));
-
- const deliverables: Record<string, DeliverableDefinition[]> = {};
- for (const phase of updatedTemplate.phases) {
- if (phase.deliverables.length > 0) {
- deliverables[phase.id] = phase.deliverables.map((d) => ({
- id: d.id,
- name: d.name,
- priority: "required" as const,
- }));
- }
- }
-
- await updateContractTemplate(updatedTemplate.id, {
- name: updatedTemplate.name,
- description: updatedTemplate.description,
- phases,
- defaultPhase: phases[0]?.id || "execute",
- deliverables: Object.keys(deliverables).length > 0 ? deliverables : undefined,
- });
-
- // Refresh templates from server
- await fetchTemplates();
- setEditingTemplate(null);
- } catch (err) {
- console.error("Failed to update template:", err);
- setError(err instanceof Error ? err.message : "Failed to update template");
- } finally {
- setSaving(false);
- }
- };
-
- const handleCreateTemplate = async () => {
- if (!newTemplateName.trim()) return;
-
- try {
- setSaving(true);
- setError(null);
-
- const phases: PhaseDefinition[] = [
- { id: "execute", name: "Execute", order: 0 },
- ];
-
- await createContractTemplate({
- name: newTemplateName.trim(),
- description: newTemplateDescription.trim() || "Custom contract template",
- phases,
- defaultPhase: "execute",
- });
-
- // Refresh templates from server
- await fetchTemplates();
-
- setNewTemplateName("");
- setNewTemplateDescription("");
- setShowNewTemplateForm(false);
- } catch (err) {
- console.error("Failed to create template:", err);
- setError(err instanceof Error ? err.message : "Failed to create template");
- } finally {
- setSaving(false);
- }
- };
-
- const handleDeleteTemplate = async (templateId: string) => {
- const template = templates.find((t) => t.id === templateId);
- if (template?.isBuiltIn) return;
-
- if (window.confirm(`Are you sure you want to delete "${template?.name}"?`)) {
- try {
- setSaving(true);
- setError(null);
- await deleteContractTemplate(templateId);
- await fetchTemplates();
- } catch (err) {
- console.error("Failed to delete template:", err);
- setError(err instanceof Error ? err.message : "Failed to delete template");
- } finally {
- setSaving(false);
- }
- }
- };
-
- const handleRefresh = () => {
- fetchTemplates();
- };
-
- // Show loading state
- if (authLoading || loading) {
- return (
- <div className="relative z-10 min-h-screen flex items-center justify-center bg-[#0a1628]">
- <div className="text-[#75aafc] font-mono text-sm animate-pulse">
- Loading...
- </div>
- </div>
- );
- }
-
- // Editor view
- if (editingTemplate) {
- return (
- <div className="relative z-10 min-h-screen bg-[#0a1628]">
- <Masthead />
- <main className="max-w-4xl mx-auto px-4 py-6">
- <TemplateEditor
- template={editingTemplate}
- onSave={handleSaveTemplate}
- onCancel={() => setEditingTemplate(null)}
- readOnly={editingTemplate.isBuiltIn}
- />
- </main>
- </div>
- );
- }
-
- return (
- <div className="relative z-10 min-h-screen bg-[#0a1628]">
- <Masthead />
- <main className="max-w-6xl mx-auto px-4 py-6">
- {/* Error display */}
- {error && (
- <div className="mb-4 p-3 bg-red-400/10 border border-red-400/30 text-red-400 font-mono text-xs">
- {error}
- <button
- type="button"
- onClick={() => setError(null)}
- className="ml-2 text-red-400/70 hover:text-red-400"
- >
- Dismiss
- </button>
- </div>
- )}
-
- {/* Header */}
- <div className="flex justify-between items-start mb-6 pb-4 border-b border-[rgba(117,170,252,0.15)]">
- <div>
- <h1 className="text-lg font-mono uppercase tracking-wide text-[#9bc3ff] mb-1">
- Contract Templates
- </h1>
- <p className="text-xs font-mono text-[#75aafc] opacity-70">
- Manage contract types and their phase deliverables
- </p>
- </div>
- <div className="flex gap-3">
- <button
- type="button"
- onClick={handleRefresh}
- disabled={saving}
- className="px-3 py-2 border border-[rgba(117,170,252,0.25)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:border-[#3f6fb3] transition-colors disabled:opacity-50"
- >
- Refresh
- </button>
- <button
- type="button"
- onClick={() => setShowNewTemplateForm(true)}
- disabled={saving}
- className="px-3 py-2 border border-[#3f6fb3] bg-[rgba(117,170,252,0.15)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:bg-[rgba(117,170,252,0.25)] transition-colors disabled:opacity-50"
- >
- + New Template
- </button>
- </div>
- </div>
-
- {/* New Template Form */}
- {showNewTemplateForm && (
- <div className="bg-[#0d1b2d] border border-[rgba(117,170,252,0.35)] p-4 mb-6">
- <div className="flex gap-3 items-center">
- <input
- type="text"
- className="flex-1 px-3 py-2 bg-transparent border border-[rgba(117,170,252,0.25)] text-white font-mono text-sm placeholder-[#556677] focus:outline-none focus:border-[#3f6fb3]"
- placeholder="Template name..."
- value={newTemplateName}
- onChange={(e) => setNewTemplateName(e.target.value)}
- disabled={saving}
- />
- <input
- type="text"
- className="flex-1 px-3 py-2 bg-transparent border border-[rgba(117,170,252,0.25)] text-white font-mono text-sm placeholder-[#556677] focus:outline-none focus:border-[#3f6fb3]"
- placeholder="Description (optional)..."
- value={newTemplateDescription}
- onChange={(e) => setNewTemplateDescription(e.target.value)}
- disabled={saving}
- />
- <button
- type="button"
- onClick={handleCreateTemplate}
- disabled={saving || !newTemplateName.trim()}
- className="px-4 py-2 border border-[#3f6fb3] bg-[rgba(117,170,252,0.15)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:bg-[rgba(117,170,252,0.25)] transition-colors disabled:opacity-50"
- >
- {saving ? "Creating..." : "Create"}
- </button>
- <button
- type="button"
- onClick={() => setShowNewTemplateForm(false)}
- disabled={saving}
- className="px-4 py-2 border border-[rgba(117,170,252,0.25)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:border-[#3f6fb3] transition-colors disabled:opacity-50"
- >
- Cancel
- </button>
- </div>
- </div>
- )}
-
- {/* Templates Grid */}
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
- {templates.map((template) => (
- <div
- key={template.id}
- className="bg-[#0d1b2d] border border-[rgba(117,170,252,0.25)] hover:border-[rgba(117,170,252,0.45)] transition-colors"
- >
- {/* Card Header */}
- <div className="px-4 py-3 border-b border-[rgba(117,170,252,0.15)] flex items-center justify-between">
- <h3 className="font-mono text-sm text-white">{template.name}</h3>
- {template.isBuiltIn ? (
- <span className="px-2 py-0.5 bg-[rgba(117,170,252,0.15)] text-[#75aafc] font-mono text-[10px] uppercase tracking-wide">
- Built-in
- </span>
- ) : (
- <span className="px-2 py-0.5 bg-[rgba(100,200,100,0.15)] text-[#7bc97b] font-mono text-[10px] uppercase tracking-wide">
- Custom
- </span>
- )}
- </div>
-
- {/* Card Body */}
- <div className="px-4 py-3">
- <p className="text-xs font-mono text-[#75aafc] opacity-70 mb-4 min-h-[2.5rem]">
- {template.description}
- </p>
-
- {/* Phases */}
- <div className="space-y-2 mb-4">
- {template.phases.map((phase, index) => (
- <div key={phase.id} className="flex items-start gap-2">
- <div className="flex flex-col items-center">
- <span className="w-2 h-2 rounded-full bg-[#3f6fb3]" />
- {index < template.phases.length - 1 && (
- <span className="w-px h-4 bg-[rgba(117,170,252,0.25)]" />
- )}
- </div>
- <div className="flex-1 min-w-0">
- <span className="text-xs font-mono text-[#9bc3ff]">
- {phase.name}
- </span>
- <span className="text-[10px] font-mono text-[#556677] ml-2">
- {phase.deliverables.length === 0
- ? "(no deliverables)"
- : phase.deliverables.map((d) => d.name).join(", ")}
- </span>
- </div>
- </div>
- ))}
- </div>
- </div>
-
- {/* Card Footer */}
- <div className="px-4 py-3 border-t border-[rgba(117,170,252,0.15)] flex gap-2">
- <button
- type="button"
- onClick={() => setEditingTemplate(template)}
- disabled={saving}
- className="flex-1 px-3 py-1.5 border border-[rgba(117,170,252,0.25)] text-[#9bc3ff] font-mono text-xs uppercase tracking-wide hover:border-[#3f6fb3] hover:bg-[rgba(117,170,252,0.05)] transition-colors disabled:opacity-50"
- >
- {template.isBuiltIn ? "View" : "Edit"}
- </button>
- {!template.isBuiltIn && (
- <button
- type="button"
- onClick={() => handleDeleteTemplate(template.id)}
- disabled={saving}
- className="px-3 py-1.5 border border-[rgba(255,100,100,0.25)] text-[#ff6464] font-mono text-xs uppercase tracking-wide hover:border-[rgba(255,100,100,0.5)] hover:bg-[rgba(255,100,100,0.05)] transition-colors disabled:opacity-50"
- >
- Delete
- </button>
- )}
- </div>
- </div>
- ))}
- </div>
- </main>
- </div>
- );
-}
diff --git a/makima/frontend/src/types/templates.ts b/makima/frontend/src/types/templates.ts
deleted file mode 100644
index ca337c5..0000000
--- a/makima/frontend/src/types/templates.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-// Contract Template types
-export interface Deliverable {
- id: string;
- name: string;
-}
-
-export interface Phase {
- id: string;
- name: string;
- deliverables: Deliverable[];
-}
-
-export interface ContractTemplate {
- id: string;
- name: string;
- description: string;
- phases: Phase[];
- isBuiltIn: boolean;
-}
-
-// Default built-in templates
-// NOTE: Deliverable IDs must match backend phase_guidance.rs exactly
-export const DEFAULT_TEMPLATES: ContractTemplate[] = [
- {
- id: "simple",
- name: "Simple",
- description: "A simple contract with plan and execute phases.",
- isBuiltIn: true,
- phases: [
- {
- id: "plan",
- name: "Plan",
- deliverables: [{ id: "plan-document", name: "Plan" }],
- },
- {
- id: "execute",
- name: "Execute",
- deliverables: [{ id: "pull-request", name: "Pull Request" }],
- },
- ],
- },
- {
- id: "specification",
- name: "Specification",
- description:
- "A comprehensive contract with research, specification, planning, execution, and review phases.",
- isBuiltIn: true,
- phases: [
- {
- id: "research",
- name: "Research",
- deliverables: [{ id: "research-notes", name: "Research Notes" }],
- },
- {
- id: "specify",
- name: "Specify",
- deliverables: [{ id: "requirements-document", name: "Requirements Document" }],
- },
- {
- id: "plan",
- name: "Plan",
- deliverables: [{ id: "plan-document", name: "Plan" }],
- },
- {
- id: "execute",
- name: "Execute",
- deliverables: [{ id: "pull-request", name: "Pull Request" }],
- },
- {
- id: "review",
- name: "Review",
- deliverables: [{ id: "release-notes", name: "Release Notes" }],
- },
- ],
- },
- {
- id: "execute",
- name: "Execute",
- description:
- "A minimal contract with only an execute phase and no deliverables.",
- isBuiltIn: true,
- phases: [
- {
- id: "execute",
- name: "Execute",
- deliverables: [],
- },
- ],
- },
-];
diff --git a/makima/frontend/tsconfig.tsbuildinfo b/makima/frontend/tsconfig.tsbuildinfo
index 3aa9cbf..afddaf9 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/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/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/templates/templateeditor.tsx","./src/components/workflow/phasecolumn.tsx","./src/components/workflow/workflowboard.tsx","./src/components/workflow/workflowcontractcard.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usespeakwebsocket.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/settings.tsx","./src/routes/speak.tsx","./src/routes/templates.tsx","./src/routes/workflow.tsx","./src/types/messages.ts","./src/types/templates.ts"],"version":"5.9.3"} \ No newline at end of file
+{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/gridoverlay.tsx","./src/components/japanesehovertext.tsx","./src/components/logo.tsx","./src/components/masthead.tsx","./src/components/navstrip.tsx","./src/components/phaseconfirmationnotification.tsx","./src/components/protectedroute.tsx","./src/components/rewritelink.tsx","./src/components/simplemarkdown.tsx","./src/components/supervisorquestionnotification.tsx","./src/components/charts/chartrenderer.tsx","./src/components/contracts/commandmodepanel.tsx","./src/components/contracts/contractcliinput.tsx","./src/components/contracts/contractcontextmenu.tsx","./src/components/contracts/contractdetail.tsx","./src/components/contracts/contractlist.tsx","./src/components/contracts/phasebadge.tsx","./src/components/contracts/phaseconfirmationmodal.tsx","./src/components/contracts/phasedeliverablespanel.tsx","./src/components/contracts/phasehint.tsx","./src/components/contracts/phaseprogressbar.tsx","./src/components/contracts/quickactionbuttons.tsx","./src/components/contracts/repositorypanel.tsx","./src/components/contracts/taskderivationpreview.tsx","./src/components/files/bodyrenderer.tsx","./src/components/files/cliinput.tsx","./src/components/files/conflictnotification.tsx","./src/components/files/elementcontextmenu.tsx","./src/components/files/filedetail.tsx","./src/components/files/filelist.tsx","./src/components/files/reposyncindicator.tsx","./src/components/files/updatenotification.tsx","./src/components/files/versionhistorydropdown.tsx","./src/components/history/checkpointcard.tsx","./src/components/history/checkpointlist.tsx","./src/components/history/conversationmessage.tsx","./src/components/history/conversationview.tsx","./src/components/history/historyfilters.tsx","./src/components/history/resumecontrols.tsx","./src/components/history/timelineeventcard.tsx","./src/components/history/timelinelist.tsx","./src/components/history/index.ts","./src/components/listen/contractpickermodal.tsx","./src/components/listen/controlpanel.tsx","./src/components/listen/speakerpanel.tsx","./src/components/listen/transcriptanalysispanel.tsx","./src/components/listen/transcriptpanel.tsx","./src/components/mesh/branchtaskmodal.tsx","./src/components/mesh/contractcompletequestion.tsx","./src/components/mesh/directoryinput.tsx","./src/components/mesh/gitactionspanel.tsx","./src/components/mesh/inlinesubtaskeditor.tsx","./src/components/mesh/mergeconflictresolver.tsx","./src/components/mesh/overlaydiffviewer.tsx","./src/components/mesh/prpreview.tsx","./src/components/mesh/patcheslistpanel.tsx","./src/components/mesh/subtasktree.tsx","./src/components/mesh/taskdetail.tsx","./src/components/mesh/tasklist.tsx","./src/components/mesh/taskoutput.tsx","./src/components/mesh/tasktree.tsx","./src/components/mesh/unifiedmeshchatinput.tsx","./src/components/mesh/worktreefilespanel.tsx","./src/components/workflow/phasecolumn.tsx","./src/components/workflow/workflowboard.tsx","./src/components/workflow/workflowcontractcard.tsx","./src/contexts/authcontext.tsx","./src/contexts/supervisorquestionscontext.tsx","./src/hooks/usecontracts.ts","./src/hooks/usefilesubscription.ts","./src/hooks/usefiles.ts","./src/hooks/usemeshchathistory.ts","./src/hooks/usemicrophone.ts","./src/hooks/usespeakwebsocket.ts","./src/hooks/usetasksubscription.ts","./src/hooks/usetasks.ts","./src/hooks/usetextscramble.ts","./src/hooks/useversionhistory.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/listenapi.ts","./src/lib/markdown.ts","./src/lib/supabase.ts","./src/routes/_index.tsx","./src/routes/contract-file.tsx","./src/routes/contracts.tsx","./src/routes/files.tsx","./src/routes/history.tsx","./src/routes/listen.tsx","./src/routes/login.tsx","./src/routes/mesh.tsx","./src/routes/settings.tsx","./src/routes/speak.tsx","./src/routes/workflow.tsx","./src/types/messages.ts"],"version":"5.9.3"} \ No newline at end of file
diff --git a/makima/migrations/20260202000000_remove_red_team_system.sql b/makima/migrations/20260202000000_remove_red_team_system.sql
new file mode 100644
index 0000000..0a5bbf8
--- /dev/null
+++ b/makima/migrations/20260202000000_remove_red_team_system.sql
@@ -0,0 +1,15 @@
+-- Remove red team system
+-- This migration drops all red team related tables, columns, and indexes
+
+-- Drop red team notifications table
+DROP TABLE IF EXISTS red_team_notifications;
+
+-- Drop red team index on tasks
+DROP INDEX IF EXISTS idx_tasks_contract_red_team;
+
+-- Remove red team column from tasks
+ALTER TABLE tasks DROP COLUMN IF EXISTS is_red_team;
+
+-- Remove red team columns from contracts
+ALTER TABLE contracts DROP COLUMN IF EXISTS red_team_enabled;
+ALTER TABLE contracts DROP COLUMN IF EXISTS red_team_prompt;
diff --git a/makima/migrations/20260202100000_add_contract_worktree.sql b/makima/migrations/20260202100000_add_contract_worktree.sql
new file mode 100644
index 0000000..26c54ba
--- /dev/null
+++ b/makima/migrations/20260202100000_add_contract_worktree.sql
@@ -0,0 +1,13 @@
+-- Add worktree tracking to contracts
+-- Each contract now has a single worktree managed by a specific daemon
+
+ALTER TABLE contracts
+ ADD COLUMN worktree_path VARCHAR(512),
+ ADD COLUMN worktree_daemon_id UUID REFERENCES daemons(id),
+ ADD COLUMN worktree_base_branch VARCHAR(255),
+ ADD COLUMN worktree_branch VARCHAR(255);
+
+COMMENT ON COLUMN contracts.worktree_path IS 'Path to the worktree directory on the assigned daemon';
+COMMENT ON COLUMN contracts.worktree_daemon_id IS 'The daemon that owns/manages this contract worktree';
+COMMENT ON COLUMN contracts.worktree_base_branch IS 'The base branch the worktree was created from';
+COMMENT ON COLUMN contracts.worktree_branch IS 'The working branch in the worktree';
diff --git a/makima/migrations/20260202200000_remove_custom_templates.sql b/makima/migrations/20260202200000_remove_custom_templates.sql
new file mode 100644
index 0000000..0c80110
--- /dev/null
+++ b/makima/migrations/20260202200000_remove_custom_templates.sql
@@ -0,0 +1,4 @@
+-- Remove custom templates system
+-- Only built-in contract types (simple, specification, execute) are supported now
+
+DROP TABLE IF EXISTS contract_type_templates;
diff --git a/makima/src/bin/makima.rs b/makima/src/bin/makima.rs
index 753f60e..af9832b 100644
--- a/makima/src/bin/makima.rs
+++ b/makima/src/bin/makima.rs
@@ -6,7 +6,7 @@ use std::sync::Arc;
use makima::daemon::api::{ApiClient, CreateContractRequest};
use makima::daemon::cli::{
- Cli, CliConfig, Commands, ConfigCommand, ContractCommand, RedTeamCommand, SupervisorCommand, ViewArgs,
+ Cli, CliConfig, Commands, ConfigCommand, ContractCommand, SupervisorCommand, ViewArgs,
};
use makima::daemon::tui::{self, Action, App, ListItem, ViewType, TuiWsClient, WsEvent, OutputLine, OutputMessageType, WsConnectionState, RepositorySuggestion};
use makima::daemon::config::{DaemonConfig, RepoEntry};
@@ -30,7 +30,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Commands::Contract(cmd) => run_contract(cmd).await,
Commands::View(args) => run_view(args).await,
Commands::Config(cmd) => run_config(cmd).await,
- Commands::RedTeam(cmd) => run_red_team(cmd).await,
}
}
@@ -352,7 +351,6 @@ async fn run_supervisor(
contract_id: args.common.contract_id,
parent_task_id: args.parent,
checkpoint_sha: args.checkpoint,
- use_own_worktree: args.own_worktree,
};
let result = client.supervisor_spawn(req).await?;
println!("{}", serde_json::to_string(&result.0)?);
@@ -795,16 +793,6 @@ async fn run_config(cmd: ConfigCommand) -> Result<(), Box<dyn std::error::Error
}
}
-/// Run red team commands.
-async fn run_red_team(cmd: RedTeamCommand) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
- match cmd {
- RedTeamCommand::Notify(args) => {
- makima::daemon::cli::handle_notify(args).await?;
- Ok(())
- }
- }
-}
-
/// Load contracts from API
async fn load_contracts(client: &ApiClient) -> Result<Vec<ListItem>, Box<dyn std::error::Error + Send + Sync>> {
let result = client.list_contracts().await?;
@@ -1115,8 +1103,6 @@ async fn run_tui_loop(
phase_guard: None,
local_only: None,
auto_merge_local: None,
- red_team_enabled: None,
- red_team_prompt: None,
};
match client.create_contract(req).await {
diff --git a/makima/src/daemon/api/contract.rs b/makima/src/daemon/api/contract.rs
index 7c76b40..e128318 100644
--- a/makima/src/daemon/api/contract.rs
+++ b/makima/src/daemon/api/contract.rs
@@ -70,10 +70,6 @@ pub struct CreateContractRequest {
pub local_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_merge_local: Option<bool>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub red_team_enabled: Option<bool>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub red_team_prompt: Option<String>,
}
impl ApiClient {
diff --git a/makima/src/daemon/api/mod.rs b/makima/src/daemon/api/mod.rs
index 92e34e9..49d80e0 100644
--- a/makima/src/daemon/api/mod.rs
+++ b/makima/src/daemon/api/mod.rs
@@ -2,9 +2,7 @@
pub mod client;
pub mod contract;
-pub mod red_team;
pub mod supervisor;
pub use client::ApiClient;
pub use contract::CreateContractRequest;
-pub use red_team::RedTeamNotifyRequest;
diff --git a/makima/src/daemon/api/red_team.rs b/makima/src/daemon/api/red_team.rs
deleted file mode 100644
index 6d3c969..0000000
--- a/makima/src/daemon/api/red_team.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-//! Red team API methods.
-
-use serde::Serialize;
-use uuid::Uuid;
-
-use super::client::{ApiClient, ApiError};
-use super::supervisor::JsonValue;
-
-/// Request body for red team notify endpoint.
-#[derive(Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct RedTeamNotifyRequest {
- /// The issue message
- pub message: String,
-
- /// Severity level: low, medium, high, critical
- pub severity: String,
-
- /// The specific task this relates to (optional)
- #[serde(skip_serializing_if = "Option::is_none")]
- pub related_task_id: Option<Uuid>,
-
- /// The file path where the issue was detected (optional)
- #[serde(skip_serializing_if = "Option::is_none")]
- pub file_path: Option<String>,
-
- /// Additional context about the issue (optional)
- #[serde(skip_serializing_if = "Option::is_none")]
- pub context: Option<String>,
-}
-
-impl ApiClient {
- /// Send a red team notification about an issue found during adversarial review.
- ///
- /// POST /api/v1/mesh/red-team/notify
- pub async fn red_team_notify(&self, req: RedTeamNotifyRequest) -> Result<JsonValue, ApiError> {
- self.post("/api/v1/mesh/red-team/notify", &req).await
- }
-}
diff --git a/makima/src/daemon/api/supervisor.rs b/makima/src/daemon/api/supervisor.rs
index c2da1db..c67c9ca 100644
--- a/makima/src/daemon/api/supervisor.rs
+++ b/makima/src/daemon/api/supervisor.rs
@@ -17,10 +17,6 @@ pub struct SpawnTaskRequest {
pub parent_task_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none")]
pub checkpoint_sha: Option<String>,
- /// If true, create a separate worktree for the task (requires merge after).
- /// If false (default), the task shares the supervisor's worktree.
- #[serde(default)]
- pub use_own_worktree: bool,
}
#[derive(Serialize)]
diff --git a/makima/src/daemon/cli/mod.rs b/makima/src/daemon/cli/mod.rs
index c848e8e..0805edd 100644
--- a/makima/src/daemon/cli/mod.rs
+++ b/makima/src/daemon/cli/mod.rs
@@ -3,18 +3,15 @@
pub mod config;
pub mod contract;
pub mod daemon;
-pub mod red_team;
pub mod server;
pub mod supervisor;
pub mod view;
-use clap::{Args, Parser, Subcommand};
-use uuid::Uuid;
+use clap::{Parser, Subcommand};
pub use config::CliConfig;
pub use contract::ContractArgs;
pub use daemon::DaemonArgs;
-pub use red_team::handle_notify;
pub use server::ServerArgs;
pub use supervisor::SupervisorArgs;
pub use view::ViewArgs;
@@ -61,10 +58,6 @@ pub enum Commands {
/// Saves configuration to ~/.makima/config.toml for use by CLI commands.
#[command(subcommand)]
Config(ConfigCommand),
-
- /// Red team commands for adversarial monitoring
- #[command(name = "red-team", subcommand)]
- RedTeam(RedTeamCommand),
}
/// Config subcommands for CLI configuration.
@@ -203,54 +196,6 @@ pub enum ContractCommand {
CreateFile(contract::CreateFileArgs),
}
-/// Red team subcommands for adversarial monitoring.
-#[derive(Subcommand, Debug)]
-pub enum RedTeamCommand {
- /// Send a notification to the supervisor about a detected issue.
- /// Only available to red team tasks.
- Notify(RedTeamNotifyArgs),
-}
-
-/// Arguments for red-team notify command.
-#[derive(Args, Debug)]
-pub struct RedTeamNotifyArgs {
- /// API URL
- #[arg(long, env = "MAKIMA_API_URL", default_value = "https://api.makima.jp")]
- pub api_url: String,
-
- /// API key for authentication
- #[arg(long, env = "MAKIMA_API_KEY")]
- pub api_key: String,
-
- /// Current task ID (must be a red team task)
- #[arg(long, env = "MAKIMA_TASK_ID")]
- pub task_id: Uuid,
-
- /// Contract ID
- #[arg(long, env = "MAKIMA_CONTRACT_ID")]
- pub contract_id: Uuid,
-
- /// The notification message
- #[arg(index = 1)]
- pub message: String,
-
- /// Severity level: low, medium, high, critical
- #[arg(long, default_value = "medium")]
- pub severity: String,
-
- /// Related task ID (optional)
- #[arg(long)]
- pub task: Option<Uuid>,
-
- /// Related file path (optional)
- #[arg(long)]
- pub file: Option<String>,
-
- /// Additional context (optional)
- #[arg(long)]
- pub context: Option<String>,
-}
-
impl Cli {
/// Parse command-line arguments
pub fn parse_args() -> Self {
diff --git a/makima/src/daemon/cli/red_team.rs b/makima/src/daemon/cli/red_team.rs
deleted file mode 100644
index 771aae4..0000000
--- a/makima/src/daemon/cli/red_team.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-//! Red Team subcommand - adversarial review notification commands.
-
-use crate::daemon::api::{ApiClient, RedTeamNotifyRequest};
-use super::RedTeamNotifyArgs;
-
-/// Handle the red-team notify command.
-pub async fn handle_notify(args: RedTeamNotifyArgs) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
- let client = ApiClient::new(args.api_url, args.api_key)?;
-
- // Use --task if provided, otherwise fall back to MAKIMA_TASK_ID
- let related_task_id = args.task;
-
- let req = RedTeamNotifyRequest {
- message: args.message,
- severity: args.severity,
- related_task_id,
- file_path: args.file,
- context: args.context,
- };
-
- eprintln!("Sending red team notification...");
- let result = client.red_team_notify(req).await?;
- println!("{}", serde_json::to_string(&result.0)?);
-
- Ok(())
-}
diff --git a/makima/src/daemon/cli/supervisor.rs b/makima/src/daemon/cli/supervisor.rs
index cb84ffa..6f19697 100644
--- a/makima/src/daemon/cli/supervisor.rs
+++ b/makima/src/daemon/cli/supervisor.rs
@@ -48,10 +48,6 @@ pub struct SpawnArgs {
/// Repository URL (local path or remote URL). If not provided, will try to detect from current directory.
#[arg(long)]
pub repo: Option<String>,
-
- /// Create a separate worktree for the task (requires merge after). By default, tasks share the supervisor's worktree.
- #[arg(long)]
- pub own_worktree: bool,
}
/// Arguments for wait command.
diff --git a/makima/src/daemon/task/manager.rs b/makima/src/daemon/task/manager.rs
index bf495d9..f921d50 100644
--- a/makima/src/daemon/task/manager.rs
+++ b/makima/src/daemon/task/manager.rs
@@ -363,24 +363,29 @@ fn strip_ansi_codes(s: &str) -> String {
}
/// System prompt for regular (non-orchestrator) subtasks.
-/// This ensures subtasks work only within their isolated worktree directory.
-const SUBTASK_SYSTEM_PROMPT: &str = r#"You are working in an isolated worktree directory that contains a snapshot of the codebase.
+/// This tells subtasks they share a worktree with the supervisor and other tasks.
+const SUBTASK_SYSTEM_PROMPT: &str = r#"You are working in a shared worktree directory with other tasks in this contract.
-## IMPORTANT: Directory Restrictions
+## IMPORTANT: Shared Worktree
-**You MUST only work within the current working directory (your worktree).**
+**You share this worktree with the supervisor and other tasks in the contract.**
-- DO NOT use `cd` to navigate to directories outside your worktree
-- DO NOT use absolute paths that point outside your worktree (e.g., don't write to ~/some/path, /tmp, or the original repository)
-- DO NOT modify files in parent directories or sibling directories
-- All your file operations should be relative to the current directory
+- Work within your assigned area (files/modules specified in your task plan)
+- Be aware other tasks may be modifying other parts of the codebase
+- Your changes will be auto-committed when your task completes
+- DO NOT make commits yourself - the system handles this
-Your working directory is your sandboxed workspace. When you complete your task, your changes will be reviewed and integrated by the orchestrator.
+## Directory Restrictions
-**Why?** Your worktree is isolated so that:
-1. Your changes don't affect other running tasks
-2. Changes can be reviewed before integration
-3. Multiple tasks can work on the codebase in parallel without conflicts
+- DO NOT use `cd` to navigate outside your worktree
+- DO NOT use absolute paths pointing outside the worktree
+- All file operations should be relative to the current directory
+
+## Your Role
+
+1. Complete the specific task assigned to you
+2. Stay focused on your task plan
+3. The system will commit and integrate your changes automatically
---
@@ -597,368 +602,91 @@ rsync -av --exclude='.git' --exclude='.makima' "$FINAL_TASK_PATH/" ./
/// System prompt for supervisor tasks (contract orchestrators).
-/// Supervisors monitor all tasks in a contract, create new tasks, and drive the contract to completion.
-const SUPERVISOR_SYSTEM_PROMPT: &str = r###"You are the SUPERVISOR for this contract. Your ONLY job is to coordinate work by spawning tasks, waiting for them to complete, and managing git operations.
-
-## CRITICAL RULES - READ CAREFULLY
-
-1. **NEVER write code or edit files yourself** - you are a coordinator ONLY
-2. **NEVER make commits yourself** - tasks do their own commits
-3. **ALWAYS spawn tasks** for ANY work that involves:
- - Writing or editing code
- - Creating or modifying files
- - Making implementation changes
- - Any actual development work
-4. **ALWAYS wait for tasks to complete** - you MUST use `wait` after spawning
-5. **Your role is ONLY to**:
- - Analyze the contract goal and break it into tasks
- - Spawn tasks AND wait for them to complete
- - Review completed task results
- - Merge completed work using `merge`
- - Create PRs when ready using `pr`
-
-## REQUIRED WORKFLOW - Follow This Pattern
-
-For EVERY task you spawn, you MUST:
-1. Spawn the task with `spawn`
-2. IMMEDIATELY call `wait` to block until completion
-3. Check the result and handle success/failure
-4. Merge if successful
-
-```bash
-# CORRECT PATTERN - spawn then wait
-RESULT=$(makima supervisor spawn "Task Name" "Detailed plan...")
-TASK_ID=$(echo "$RESULT" | jq -r '.taskId')
-echo "Spawned task: $TASK_ID"
-
-# MUST wait for the task - DO NOT skip this step!
-makima supervisor wait "$TASK_ID"
+/// Supervisors coordinate work by spawning tasks and responding to user questions.
+/// Git operations and phase advancement are handled automatically by the system.
+const SUPERVISOR_SYSTEM_PROMPT: &str = r###"You are the SUPERVISOR for this contract. Your job is to coordinate work by spawning tasks and responding to user questions.
-# Check result, view diff, merge if successful
-makima supervisor diff "$TASK_ID"
-makima supervisor merge "$TASK_ID"
-```
+## WHAT YOU DO
+1. Break down the contract goal into actionable tasks
+2. Spawn tasks using `makima supervisor spawn "Task Name" "Detailed plan..."`
+3. Wait for tasks to complete using `makima supervisor wait <task_id>`
+4. Respond to user questions when asked
-## Example - Full Workflow
+## WHAT THE SYSTEM HANDLES AUTOMATICALLY
+- **Phase advancement** - When deliverables are complete, the system advances the phase
+- **Git commits** - Tasks auto-commit their changes on completion
+- **Pull requests** - System auto-creates PR when execute phase completes
+- **You will be notified** when phases advance so you know to continue
-Goal: "Add user authentication"
+## CRITICAL RULES
-```bash
-# Step 1: Create a makima branch for this work (use makima/{name} convention)
-makima supervisor branch "makima/user-authentication"
-
-# Step 2: Spawn tasks, wait for each, and merge to the branch
-
-# Task 1: Research (spawn and wait)
-RESULT=$(makima supervisor spawn "Research auth patterns" "Explore the codebase for existing authentication. Document findings.")
-TASK_ID=$(echo "$RESULT" | jq -r '.taskId')
-makima supervisor wait "$TASK_ID"
-# Review findings before continuing
-
-# Task 2: Login endpoint (spawn and wait)
-RESULT=$(makima supervisor spawn "Implement login" "Create POST /api/login endpoint...")
-TASK_ID=$(echo "$RESULT" | jq -r '.taskId')
-makima supervisor wait "$TASK_ID"
-makima supervisor diff "$TASK_ID"
-makima supervisor merge "$TASK_ID" --to "makima/user-authentication"
-
-# Task 3: Logout endpoint (spawn and wait)
-RESULT=$(makima supervisor spawn "Implement logout" "Create POST /api/logout endpoint...")
-TASK_ID=$(echo "$RESULT" | jq -r '.taskId')
-makima supervisor wait "$TASK_ID"
-makima supervisor merge "$TASK_ID" --to "makima/user-authentication"
-
-# Step 3: All tasks complete - create PR from makima branch
-makima supervisor pr "makima/user-authentication" --title "Add user authentication"
-```
+1. **NEVER write code or edit files yourself** - you are a coordinator ONLY
+2. **ALWAYS spawn tasks** for ANY work that involves writing or editing code
+3. **ALWAYS wait for tasks to complete** - you MUST use `wait` after spawning
-## Available Tools (via makima supervisor)
+## AVAILABLE COMMANDS
### Task Management
```bash
-# List all tasks in this contract
-makima supervisor tasks
-
-# Spawn a new task (returns JSON with taskId)
-makima supervisor spawn "Task Name" "Detailed plan..."
-
-# IMPORTANT: Wait for task to complete (blocks until done/failed)
-makima supervisor wait <task_id> [timeout_seconds]
-
-# Read a file from any task's worktree
-makima supervisor read-file <task_id> <file_path>
-
-# Get the full task tree structure
-makima supervisor tree
-```
-
-### Git Operations
-```bash
-# Create a new branch
-makima supervisor branch <branch_name> [--from <task_id|sha>]
-
-# Merge a task's changes to a branch
-makima supervisor merge <task_id> [--to <branch>] [--squash]
-
-# Create a pull request
-makima supervisor pr <branch> --title "Title" [--body "Body"]
-
-# View a task's diff
-makima supervisor diff <task_id>
-
-# Create a git checkpoint
-makima supervisor checkpoint "Checkpoint message"
-
-# List checkpoints for a task
-makima supervisor checkpoints [task_id]
-```
-
-### Contract & Phase Management
-```bash
-# Get contract status (including current phase)
-makima supervisor status
-
-# Advance to the next phase (specify, plan, execute, review)
-makima supervisor advance-phase <phase>
-
-# Mark a phase deliverable as complete (e.g., 'plan-document', 'pull-request')
-makima supervisor mark-deliverable <deliverable_id> [--phase <phase>]
-```
-
-### User Feedback
-```bash
-# Ask a free-form question
-makima supervisor ask "Your question here"
-
-# Ask with choices (comma-separated)
-makima supervisor ask "Choose an option" --choices "Option A,Option B,Option C"
-
-# Ask with context
-makima supervisor ask "Ready to proceed?" --context "After completing task X"
-
-# Ask with custom timeout (default 1 hour)
-makima supervisor ask "Question" --timeout 3600
+makima supervisor spawn "Task Name" "Detailed plan..." # Create and start a task
+makima supervisor wait <task_id> [timeout_seconds] # Wait for task completion
+makima supervisor tasks # List all tasks
+makima supervisor tree # View task tree
+makima supervisor diff <task_id> # View task changes
+makima supervisor read-file <task_id> <file_path> # Read file from task
```
-## User Feedback (Ask Command)
-
-You can ask the user questions when you need clarification or approval:
-
+### User Interaction
```bash
-# Ask a free-form question (waits for user to respond)
-makima supervisor ask "What authentication method should I use?"
-
-# Ask with predefined choices
-makima supervisor ask "Ready to create PR?" --choices "Yes,No,Need more changes"
-
-# Ask with context
-makima supervisor ask "Should I proceed?" --context "Plan phase complete"
+makima supervisor ask "Your question" [--choices "A,B,C"] # Ask user
+makima supervisor status # Contract status (read-only)
```
-The ask command will block until the user responds (or timeout). Use this to:
-- Clarify requirements before starting work
-- Get approval before creating PRs
-- Ask for guidance when tasks fail
-
-## Contract Phase Progression
-
-### For "Simple" contracts (Plan → Execute):
-1. **Plan Phase**: Review the plan document and understand the goal
-2. **Execute Phase**: Spawn tasks to implement the plan, then create PR
-3. Mark contract as complete when PR is created
-
-### For "Specification" contracts (Research → Specify → Plan → Execute → Review):
-Progress through each phase, spawning tasks as needed and asking for user feedback.
-
-## Multi-Phase Plan Execution (CRITICAL)
-
-Plan documents often contain MULTIPLE implementation phases (e.g., "Phase 1: Foundation", "Phase 2: Core Features", "Phase 3: Integration"). You MUST implement ALL phases, not just the first one!
-
-### Detecting Implementation Phases
-
-At the START of the Execute phase:
-1. Read the plan document using `makima contract files` and `makima contract file <id>`
-2. Look for implementation phase sections like:
- - "## Phase 1: ..." / "## Phase 2: ..."
- - "## Step 1: ..." / "## Step 2: ..."
- - "## Part 1: ..." / "## Part 2: ..."
- - Any numbered sections that represent sequential work
-3. Create a mental list of ALL implementation phases that need to be completed
-
-### Executing Multi-Phase Plans
-
-1. **Execute phases SEQUENTIALLY** - complete ALL tasks for Phase 1 before starting Phase 2
-2. **Track your progress** - keep track of which phases are done vs remaining
-3. **Confirm between phases** - use `makima supervisor ask` to confirm: "Phase N complete. Ready for Phase N+1?"
-4. **ONLY create PR when ALL phases are done** - DO NOT create a PR after just the first phase!
-
-### Multi-Phase Workflow Example
+## WORKFLOW PATTERN
```bash
-# 1. First, read the plan to understand all phases
-makima contract files # List files to find plan document
-makima contract file <plan-file-id> # Read the plan content
-
-# 2. Identify phases (example shows 3 phases)
-# Found:
-# - Phase 1: Setup and Dependencies
-# - Phase 2: Core Implementation
-# - Phase 3: Testing and Documentation
-
-# 3. Execute Phase 1 completely
-makima supervisor spawn "Phase 1: Setup" "Details from plan..."
-makima supervisor wait <task_id>
-makima supervisor merge <task_id> --to "makima/feature-name"
-
-# 4. Confirm before moving to Phase 2
-makima supervisor ask "Phase 1 (Setup) complete. Ready to proceed to Phase 2 (Core Implementation)?" --choices "Yes,Need changes,Stop"
-
-# 5. Execute Phase 2 completely
-makima supervisor spawn "Phase 2: Core Implementation" "Details from plan..."
-makima supervisor wait <task_id>
-makima supervisor merge <task_id> --to "makima/feature-name"
-
-# 6. Confirm before Phase 3
-makima supervisor ask "Phase 2 (Core Implementation) complete. Ready to proceed to Phase 3 (Testing)?" --choices "Yes,Need changes,Stop"
-
-# 7. Execute Phase 3
-makima supervisor spawn "Phase 3: Testing" "Details from plan..."
-makima supervisor wait <task_id>
-makima supervisor merge <task_id> --to "makima/feature-name"
-
-# 8. ONLY NOW create the PR (all phases complete!)
-makima supervisor pr "makima/feature-name" --title "Complete feature implementation"
-```
-
-### Common Multi-Phase Mistakes
-
-- ❌ Creating a PR after only the first phase completes
-- ❌ Not reading the plan document to identify all phases
-- ❌ Trying to implement all phases in a single giant task
-- ❌ Skipping the confirmation step between phases
-
-### Correct Multi-Phase Behavior
-
-- ✅ Read plan document first to identify ALL implementation phases
-- ✅ Execute each phase as separate task(s)
-- ✅ Wait for each phase to complete before starting the next
-- ✅ Confirm with user between phases
-- ✅ Create PR ONLY after ALL phases are complete
-- ✅ The PR title/description should mention all completed phases
-
-## Phase Management Commands
-
-Check contract status (including current phase):
-```bash
-makima supervisor status
-```
-
-Advance to the next phase:
-```bash
-makima supervisor advance-phase <phase>
-```
-
-Valid phases: `specify`, `plan`, `execute`, `review`
-
-### Marking Deliverables Complete
+# 1. Spawn a task
+RESULT=$(makima supervisor spawn "Implement feature X" "Details...")
+TASK_ID=$(echo "$RESULT" | jq -r '.taskId')
-Each phase has deliverables that must be completed before advancing. Use `mark-deliverable` to explicitly mark them as complete when you've verified the requirement is satisfied:
+# 2. Wait for it
+makima supervisor wait "$TASK_ID"
-```bash
-# Mark a deliverable complete (defaults to current phase)
-makima supervisor mark-deliverable plan-document
+# 3. Check result
+makima supervisor diff "$TASK_ID"
-# Mark a deliverable for a specific phase
-makima supervisor mark-deliverable pull-request --phase execute
+# 4. Repeat for more tasks
+# System handles commits, merging, and PR creation automatically
```
-Common deliverable IDs by phase:
-- **plan**: `plan-document`, `requirements-document`
-- **execute**: `pull-request`
-- **review**: `release-notes`, `retrospective`
-
-**Use `status` to see which deliverables are pending for the current phase.**
-
-## When to Advance Phases
-
-**IMPORTANT**: You MUST advance the contract phase as you complete work in each phase!
-
-### Simple Contracts (Plan → Execute)
-- **Plan → Execute**: When you understand the plan and are ready to spawn tasks
-- **Complete contract**: When all tasks are done/merged and PR is created
-
-### Specification Contracts (Research → Specify → Plan → Execute → Review)
-- **Research → Specify**: When requirements are understood
-- **Specify → Plan**: When specifications are written
-- **Plan → Execute**: When implementation plan is ready
-- **Execute → Review**: When all tasks are done/merged
-- **Complete contract**: After review is done and PR is created
-
-## Phase Advancement Workflow
-
-1. Complete work for current phase (spawn tasks, wait, merge)
-2. Check status: `makima supervisor status`
-3. Ask user for confirmation (recommended):
- ```bash
- makima supervisor ask "Ready to advance to execute phase?" --choices "Yes,Not yet"
- ```
-4. Advance: `makima supervisor advance-phase execute`
-5. Continue with next phase work
-
-**DO NOT forget to advance phases!** The user needs to see the contract progressing.
+## MULTI-PHASE PLANS
-## Key Points
+When the plan document contains multiple implementation phases (Phase 1, Phase 2, etc.):
-1. **Create a makima branch first** - use `branch "makima/{name}"` for the contract's work
-2. **spawn returns immediately** - the task runs in the background
-3. **wait blocks until complete** - you MUST call this to know when a task finishes
-4. **Never fire-and-forget** - always wait for each task before moving on
-5. **Merge to your makima branch** - use `merge <task_id> --to "makima/{name}"` to collect completed work
-6. **Create PR when done** - use `pr "makima/{name}" --title "..."`
-7. **Ask when unsure** - use `ask` to get user feedback on decisions
+1. **Read the plan** to identify ALL phases
+2. **Execute phases SEQUENTIALLY** - complete Phase 1 before Phase 2
+3. **Track your progress** - keep track of which phases are done
+4. **Confirm between phases** - use `ask` to confirm before proceeding
+5. The system will auto-create PR when ALL phases are complete
-## Standard Workflow
+## IMPORTANT NOTES
+- DO NOT call advance-phase - the system does this automatically
+- DO NOT manage git operations (branch, merge, pr) - the system handles this
+- Focus ONLY on spawning tasks and responding to users
+- You share a worktree with all tasks - changes are visible immediately
+- If you need user input, use `makima supervisor ask`
+- When all work is complete, use `makima supervisor complete` to finish
-1. `branch "makima/{name}"` - Create branch (e.g., "makima/add-auth")
-2. For each piece of work:
- - `spawn` - Create task
- - `wait` - Block until complete
- - `merge --to "makima/{name}"` - Merge to branch
-3. `ask "Ready to create PR?"` - Get user approval
-4. `pr "makima/{name}" --title "..."` - Create PR
+## WHEN TASKS COMPLETE
-## Important Reminders
+When a task completes:
+1. Check the result with `makima supervisor diff <task_id>`
+2. If more work needed, spawn another task
+3. The system automatically commits changes
-- **ONLY YOU can spawn tasks** - regular tasks cannot create children
-- **NEVER implement anything yourself** - always spawn tasks
-- **ALWAYS create a makima branch** - use `makima/{name}` naming convention
-- **ASK for feedback** when you need clarification or approval
-- Tasks run independently - you just coordinate
-- You will be resumed if interrupted - your conversation is preserved
-- Create checkpoints before major transitions
-- **Mark contract complete** when PR is created by updating status
-
-## MANDATORY ACTIONS
-
-### [ACTION REQUIRED] Messages
-When you receive an `[ACTION REQUIRED]` message from the system:
-1. Read the directive carefully
-2. Execute the specified command IMMEDIATELY
-3. Do NOT ignore or defer these actions
-4. These messages indicate conditions have been met and action is needed NOW
-
-### Phase Completion Checklist
-After all tasks are "done" and merged, you MUST take the following actions:
-
-**If in execute phase:**
-1. Create PR immediately: `makima supervisor pr "makima/{name}" --title "..."`
-2. After PR created:
- - Simple contract: Mark complete with `makima supervisor complete`
- - Specification contract: Advance to review with `makima supervisor advance-phase review`
-
-**Never leave a contract hanging** - when work is done, create the PR and complete/advance.
-
----
+When ALL work is complete:
+- Use `makima supervisor complete` to mark the contract done
+- The system will auto-create PR (for remote repos)
"###;
@@ -5308,20 +5036,19 @@ impl TaskManagerInner {
}
}
_ = heartbeat_interval.tick(), if heartbeat_enabled => {
- // Create periodic heartbeat commit to preserve work-in-progress
- match self.create_heartbeat_commit(task_id, &working_dir).await {
- Ok((sha, pushed)) => {
- let status = if pushed { "pushed" } else { "local only" };
+ // Create periodic ephemeral patch to preserve work-in-progress
+ match self.create_ephemeral_patch(task_id, &working_dir).await {
+ Ok(files_count) => {
let msg = DaemonMessage::task_output(
task_id,
- format!("[Heartbeat] WIP checkpoint {} ({})\n", &sha[..8], status),
+ format!("[Heartbeat] Patch saved ({} files)\n", files_count),
false,
);
let _ = ws_tx.send(msg).await;
}
Err(e) => {
- // No changes to commit or git error - this is fine, just log at debug level
- tracing::debug!(task_id = %task_id, error = %e, "Heartbeat commit skipped");
+ // No changes to patch or error - this is fine, just log at debug level
+ tracing::debug!(task_id = %task_id, error = %e, "Heartbeat patch skipped");
}
}
}
@@ -5907,24 +5634,28 @@ impl TaskManagerInner {
}
}
- /// Create a heartbeat commit with all uncommitted changes (WIP checkpoint).
- /// Returns (commit SHA, push succeeded) on success, or an error message if nothing to commit.
- /// Also creates a patch and sends it to the server for recovery purposes.
- async fn create_heartbeat_commit(
+ /// Create an ephemeral patch of uncommitted changes and send to the server.
+ /// This does NOT create git commits or push - patches are stored in PostgreSQL only.
+ /// Returns the number of files changed on success, or an error message if nothing to patch.
+ async fn create_ephemeral_patch(
&self,
task_id: Uuid,
worktree_path: &std::path::Path,
- ) -> Result<(String, bool), String> {
- // 1. Get parent SHA BEFORE committing (for patch creation)
- let parent_sha_output = tokio::process::Command::new("git")
+ ) -> Result<i32, String> {
+ // 1. Get current HEAD SHA (base for the patch)
+ let base_sha_output = tokio::process::Command::new("git")
.current_dir(worktree_path)
.args(["rev-parse", "HEAD"])
.output()
- .await;
- let parent_sha = parent_sha_output
- .ok()
- .filter(|o| o.status.success())
- .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string());
+ .await
+ .map_err(|e| format!("Failed to run git rev-parse: {}", e))?;
+
+ if !base_sha_output.status.success() {
+ let stderr = String::from_utf8_lossy(&base_sha_output.stderr);
+ return Err(format!("git rev-parse failed: {}", stderr));
+ }
+
+ let base_sha = String::from_utf8_lossy(&base_sha_output.stdout).trim().to_string();
// 2. Check for uncommitted changes using git status --porcelain
let status_output = tokio::process::Command::new("git")
@@ -5941,10 +5672,13 @@ impl TaskManagerInner {
let status = String::from_utf8_lossy(&status_output.stdout);
if status.trim().is_empty() {
- return Err("No changes to commit".into());
+ return Err("No changes to patch".into());
}
- // 3. Stage all changes
+ // Count files with changes
+ let files_count = status.lines().count() as i32;
+
+ // 3. Stage all changes (required for diff to include untracked files)
let add_output = tokio::process::Command::new("git")
.current_dir(worktree_path)
.args(["add", "-A"])
@@ -5957,137 +5691,79 @@ impl TaskManagerInner {
return Err(format!("git add failed: {}", stderr));
}
- // 4. Create WIP commit with timestamp
- let timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC");
- let commit_msg = format!("[WIP] Heartbeat checkpoint - {}", timestamp);
-
- let commit_output = tokio::process::Command::new("git")
- .current_dir(worktree_path)
- .args(["commit", "-m", &commit_msg])
- .output()
- .await
- .map_err(|e| format!("Failed to run git commit: {}", e))?;
-
- if !commit_output.status.success() {
- let stderr = String::from_utf8_lossy(&commit_output.stderr);
- return Err(format!("git commit failed: {}", stderr));
+ // 4. Create patch (diff of staged changes against HEAD)
+ if !self.checkpoint_patches.enabled {
+ // Reset staged changes and return
+ let _ = tokio::process::Command::new("git")
+ .current_dir(worktree_path)
+ .args(["reset", "HEAD"])
+ .output()
+ .await;
+ return Err("Checkpoint patches disabled".into());
}
- // 5. Get the commit SHA
- let sha_output = tokio::process::Command::new("git")
- .current_dir(worktree_path)
- .args(["rev-parse", "HEAD"])
- .output()
- .await
- .map_err(|e| format!("Failed to run git rev-parse: {}", e))?;
+ match storage::create_patch(worktree_path, &base_sha).await {
+ Ok((compressed_patch, patch_files_count)) => {
+ // Reset staged changes (we don't want to commit)
+ let _ = tokio::process::Command::new("git")
+ .current_dir(worktree_path)
+ .args(["reset", "HEAD"])
+ .output()
+ .await;
- if !sha_output.status.success() {
- let stderr = String::from_utf8_lossy(&sha_output.stderr);
- return Err(format!("git rev-parse failed: {}", stderr));
- }
+ // Check size limit
+ if compressed_patch.len() > self.checkpoint_patches.max_patch_size_bytes {
+ tracing::warn!(
+ task_id = %task_id,
+ patch_size = compressed_patch.len(),
+ max_size = self.checkpoint_patches.max_patch_size_bytes,
+ "Patch exceeds size limit"
+ );
+ return Err("Patch exceeds size limit".into());
+ }
- let sha = String::from_utf8_lossy(&sha_output.stdout).trim().to_string();
- tracing::info!(task_id = %task_id, sha = %sha, "Created heartbeat commit");
+ // Encode as base64 for JSON transport
+ let patch_data = base64::engine::general_purpose::STANDARD.encode(&compressed_patch);
- // 6. Get current branch name
- let branch_output = tokio::process::Command::new("git")
- .current_dir(worktree_path)
- .args(["branch", "--show-current"])
- .output()
- .await;
- let branch_name = branch_output
- .ok()
- .filter(|o| o.status.success())
- .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
- .unwrap_or_else(|| "unknown".to_string());
-
- // 7. Push to remote (best effort - don't fail if push fails)
- // Use -u origin HEAD to set upstream if not already set (new branches won't have upstream)
- let push_output = tokio::process::Command::new("git")
- .current_dir(worktree_path)
- .args(["push", "-u", "origin", "HEAD"])
- .output()
- .await;
+ tracing::debug!(
+ task_id = %task_id,
+ base_sha = %base_sha,
+ patch_size = compressed_patch.len(),
+ files_count = patch_files_count,
+ "Created ephemeral patch"
+ );
- let pushed = match push_output {
- Ok(output) if output.status.success() => {
- tracing::info!(task_id = %task_id, sha = %sha, "Pushed heartbeat commit to remote");
- true
- }
- Ok(output) => {
- let stderr = String::from_utf8_lossy(&output.stderr);
- tracing::warn!(task_id = %task_id, sha = %sha, error = %stderr, "Failed to push heartbeat commit (commit saved locally)");
- false
+ // Send CheckpointCreated message to server (patch-only, no commit)
+ let timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC");
+ let msg = DaemonMessage::CheckpointCreated {
+ task_id,
+ success: true,
+ commit_sha: None, // No git commit
+ branch_name: None,
+ checkpoint_number: None, // Server will assign
+ files_changed: None, // Detailed file info not tracked for ephemeral patches
+ lines_added: None,
+ lines_removed: None,
+ error: None,
+ message: format!("Ephemeral patch - {}", timestamp),
+ patch_data: Some(patch_data),
+ patch_base_sha: Some(base_sha),
+ patch_files_count: Some(patch_files_count as i32),
+ };
+ let _ = self.ws_tx.send(msg).await;
+
+ Ok(files_count)
}
Err(e) => {
- tracing::warn!(task_id = %task_id, sha = %sha, error = %e, "Failed to run git push (commit saved locally)");
- false
- }
- };
-
- // 8. Create patch and send CheckpointCreated message to server
- let mut patch_data: Option<String> = None;
- let mut patch_base_sha: Option<String> = None;
- let mut patch_files_count: Option<i32> = None;
-
- if self.checkpoint_patches.enabled {
- if let Some(ref base_sha) = parent_sha {
- match storage::create_patch(worktree_path, base_sha).await {
- Ok((compressed_patch, files_count)) => {
- // Check size limit
- if compressed_patch.len() <= self.checkpoint_patches.max_patch_size_bytes {
- // Encode as base64 for JSON transport
- patch_data = Some(base64::engine::general_purpose::STANDARD.encode(&compressed_patch));
- patch_base_sha = Some(base_sha.clone());
- patch_files_count = Some(files_count as i32);
- tracing::debug!(
- task_id = %task_id,
- sha = %sha,
- patch_size = compressed_patch.len(),
- files_count = files_count,
- "Created checkpoint patch"
- );
- } else {
- tracing::warn!(
- task_id = %task_id,
- sha = %sha,
- patch_size = compressed_patch.len(),
- max_size = self.checkpoint_patches.max_patch_size_bytes,
- "Patch exceeds size limit, not including in checkpoint"
- );
- }
- }
- Err(e) => {
- tracing::warn!(
- task_id = %task_id,
- sha = %sha,
- error = %e,
- "Failed to create patch for heartbeat commit"
- );
- }
- }
+ // Reset staged changes
+ let _ = tokio::process::Command::new("git")
+ .current_dir(worktree_path)
+ .args(["reset", "HEAD"])
+ .output()
+ .await;
+ Err(format!("Failed to create patch: {}", e))
}
}
-
- // Send CheckpointCreated message to server (so it stores the checkpoint and patch)
- let msg = DaemonMessage::CheckpointCreated {
- task_id,
- success: true,
- commit_sha: Some(sha.clone()),
- branch_name: Some(branch_name),
- checkpoint_number: None, // Server will assign
- files_changed: None, // Could get from git diff --name-status if needed
- lines_added: None,
- lines_removed: None,
- error: None,
- message: commit_msg,
- patch_data,
- patch_base_sha,
- patch_files_count,
- };
- let _ = self.ws_tx.send(msg).await;
-
- Ok((sha, pushed))
}
}
diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs
index abdcce6..cef0a22 100644
--- a/makima/src/db/models.rs
+++ b/makima/src/db/models.rs
@@ -440,11 +440,6 @@ pub struct Task {
/// True for contract supervisor tasks. Only supervisors can spawn new tasks.
#[serde(default)]
pub is_supervisor: bool,
- /// Whether this is a red team monitoring task.
- /// Red team tasks monitor work task outputs and can notify
- /// the supervisor about potential issues.
- #[serde(default)]
- pub is_red_team: bool,
// Daemon/container info
pub daemon_id: Option<Uuid>,
@@ -575,9 +570,6 @@ pub struct TaskSummary {
/// True for contract supervisor tasks
#[serde(default)]
pub is_supervisor: bool,
- /// True for red team tasks that monitor and review other tasks' work
- #[serde(default)]
- pub is_red_team: bool,
/// Whether this task is hidden from the UI (user dismissed it)
#[serde(default)]
pub hidden: bool,
@@ -603,7 +595,6 @@ impl From<Task> for TaskSummary {
subtask_count: 0, // Would need separate query
version: task.version,
is_supervisor: task.is_supervisor,
- is_red_team: task.is_red_team,
hidden: task.hidden,
created_at: task.created_at,
updated_at: task.updated_at,
@@ -636,9 +627,6 @@ pub struct CreateTaskRequest {
/// True for contract supervisor tasks. Only supervisors can spawn new tasks.
#[serde(default)]
pub is_supervisor: bool,
- /// True for red team tasks that monitor and review other tasks' work.
- #[serde(default)]
- pub is_red_team: bool,
/// Priority (higher = more urgent)
#[serde(default)]
pub priority: i32,
@@ -1453,15 +1441,6 @@ pub struct Contract {
/// automatically merged to the master/main branch locally (without pushing or creating PRs).
#[serde(default)]
pub auto_merge_local: bool,
- /// Whether to spawn a red team task to monitor work tasks.
- /// When enabled, a parallel task monitors outputs and can alert
- /// the supervisor about potential issues.
- #[serde(default)]
- pub red_team_enabled: bool,
- /// Optional custom prompt/criteria for the red team to use
- /// when evaluating task outputs.
- #[serde(skip_serializing_if = "Option::is_none")]
- pub red_team_prompt: Option<String>,
/// Phase configuration copied from template at contract creation (raw JSON).
/// When present, this overrides the built-in contract type phases.
/// Use `get_phase_config()` to get the parsed PhaseConfig.
@@ -1649,9 +1628,6 @@ pub struct ContractSummary {
/// When true with local_only, automatically merge completed tasks to target branch locally.
#[serde(default)]
pub auto_merge_local: bool,
- /// Whether red team monitoring is enabled for this contract.
- #[serde(default)]
- pub red_team_enabled: bool,
pub file_count: i64,
pub task_count: i64,
pub repository_count: i64,
@@ -1723,15 +1699,6 @@ pub struct CreateContractRequest {
/// automatically merged to the master/main branch locally (without pushing or creating PRs).
#[serde(default)]
pub auto_merge_local: Option<bool>,
- /// Enable red team monitoring for this contract.
- /// When enabled, a parallel task monitors work task outputs
- /// and can alert the supervisor about potential issues.
- #[serde(default)]
- pub red_team_enabled: Option<bool>,
- /// Optional custom criteria for the red team to evaluate.
- /// Examples: "Focus on security vulnerabilities",
- /// "Ensure all functions have tests", etc.
- pub red_team_prompt: Option<String>,
}
/// Request payload for updating a contract
@@ -2542,67 +2509,6 @@ pub struct SupervisorHeartbeatRequest {
pub pending_task_ids: Vec<Uuid>,
}
-// =============================================================================
-// Red Team Types
-// =============================================================================
-
-/// Red Team notification record
-#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct RedTeamNotification {
- pub id: Uuid,
- pub contract_id: Uuid,
- pub red_team_task_id: Uuid,
- pub related_task_id: Option<Uuid>,
-
- pub message: String,
- pub severity: String,
- pub file_path: Option<String>,
- pub context: Option<String>,
-
- pub delivered: bool,
- pub delivered_at: Option<DateTime<Utc>>,
- pub acknowledged: bool,
- pub acknowledged_at: Option<DateTime<Utc>>,
-
- pub created_at: DateTime<Utc>,
-}
-
-/// Severity levels for red team notifications
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(rename_all = "lowercase")]
-pub enum NotificationSeverity {
- Low,
- Medium,
- High,
- Critical,
-}
-
-impl std::fmt::Display for NotificationSeverity {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- Self::Low => write!(f, "low"),
- Self::Medium => write!(f, "medium"),
- Self::High => write!(f, "high"),
- Self::Critical => write!(f, "critical"),
- }
- }
-}
-
-impl std::str::FromStr for NotificationSeverity {
- type Err = String;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- match s.to_lowercase().as_str() {
- "low" => Ok(Self::Low),
- "medium" => Ok(Self::Medium),
- "high" => Ok(Self::High),
- "critical" => Ok(Self::Critical),
- _ => Err(format!("Invalid severity: {}", s)),
- }
- }
-}
-
// ============================================================================
// Supervisor Status API Types
// ============================================================================
diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs
index e308df7..2ecbc4a 100644
--- a/makima/src/db/repository.rs
+++ b/makima/src/db/repository.rs
@@ -12,7 +12,7 @@ use super::models::{
CreateFileRequest, CreateTaskRequest, CreateTemplateRequest, Daemon, DaemonTaskAssignment,
DaemonWithCapacity, DeliverableDefinition, File, FileSummary, FileVersion, HistoryEvent,
HistoryQueryFilters, MeshChatConversation, MeshChatMessageRecord, PhaseChangeResult,
- PhaseConfig, PhaseDefinition, RedTeamNotification, SupervisorHeartbeatRecord, SupervisorState,
+ PhaseConfig, PhaseDefinition, SupervisorHeartbeatRecord, SupervisorState,
Task, TaskCheckpoint, TaskEvent, TaskSummary, UpdateContractRequest, UpdateFileRequest,
UpdateTaskRequest, UpdateTemplateRequest,
};
@@ -691,11 +691,11 @@ pub async fn create_task(pool: &PgPool, req: CreateTaskRequest) -> Result<Task,
r#"
INSERT INTO tasks (
contract_id, parent_task_id, depth, name, description, plan, priority,
- is_supervisor, is_red_team, repository_url, base_branch, target_branch, merge_mode,
+ is_supervisor, repository_url, base_branch, target_branch, merge_mode,
target_repo_path, completion_action, continue_from_task_id, copy_files,
branched_from_task_id, conversation_state, supervisor_worktree_task_id
)
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)
RETURNING *
"#,
)
@@ -707,7 +707,6 @@ pub async fn create_task(pool: &PgPool, req: CreateTaskRequest) -> Result<Task,
.bind(&req.plan)
.bind(req.priority)
.bind(req.is_supervisor)
- .bind(req.is_red_team)
.bind(&repo_url)
.bind(&base_branch)
.bind(&target_branch)
@@ -748,8 +747,7 @@ pub async fn list_tasks(pool: &PgPool) -> Result<Vec<TaskSummary>, sqlx::Error>
t.parent_task_id, t.depth, t.name, t.status, t.priority,
t.progress_summary,
(SELECT COUNT(*) FROM tasks WHERE parent_task_id = t.id) as subtask_count,
- t.version, t.is_supervisor, COALESCE(t.is_red_team, false) as is_red_team,
- COALESCE(t.hidden, false) as hidden, t.created_at, t.updated_at
+ t.version, t.is_supervisor, COALESCE(t.hidden, false) as hidden, t.created_at, t.updated_at
FROM tasks t
LEFT JOIN contracts c ON t.contract_id = c.id
WHERE t.parent_task_id IS NULL AND COALESCE(t.hidden, false) = false
@@ -770,8 +768,7 @@ pub async fn list_subtasks(pool: &PgPool, parent_id: Uuid) -> Result<Vec<TaskSum
t.parent_task_id, t.depth, t.name, t.status, t.priority,
t.progress_summary,
(SELECT COUNT(*) FROM tasks WHERE parent_task_id = t.id) as subtask_count,
- t.version, t.is_supervisor, COALESCE(t.is_red_team, false) as is_red_team,
- COALESCE(t.hidden, false) as hidden, t.created_at, t.updated_at
+ t.version, t.is_supervisor, COALESCE(t.hidden, false) as hidden, t.created_at, t.updated_at
FROM tasks t
LEFT JOIN contracts c ON t.contract_id = c.id
WHERE t.parent_task_id = $1
@@ -1106,11 +1103,11 @@ pub async fn create_task_for_owner(
r#"
INSERT INTO tasks (
owner_id, contract_id, parent_task_id, depth, name, description, plan, priority,
- is_supervisor, is_red_team, repository_url, base_branch, target_branch, merge_mode,
+ is_supervisor, repository_url, base_branch, target_branch, merge_mode,
target_repo_path, completion_action, continue_from_task_id, copy_files,
branched_from_task_id, conversation_state, supervisor_worktree_task_id
)
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)
RETURNING *
"#,
)
@@ -1123,7 +1120,6 @@ pub async fn create_task_for_owner(
.bind(&req.plan)
.bind(req.priority)
.bind(req.is_supervisor)
- .bind(req.is_red_team)
.bind(&repo_url)
.bind(&base_branch)
.bind(&target_branch)
@@ -1172,8 +1168,7 @@ pub async fn list_tasks_for_owner(
t.parent_task_id, t.depth, t.name, t.status, t.priority,
t.progress_summary,
(SELECT COUNT(*) FROM tasks WHERE parent_task_id = t.id) as subtask_count,
- t.version, t.is_supervisor, COALESCE(t.is_red_team, false) as is_red_team,
- COALESCE(t.hidden, false) as hidden, t.created_at, t.updated_at
+ t.version, t.is_supervisor, COALESCE(t.hidden, false) as hidden, t.created_at, t.updated_at
FROM tasks t
LEFT JOIN contracts c ON t.contract_id = c.id
WHERE t.owner_id = $1 AND t.parent_task_id IS NULL AND COALESCE(t.hidden, false) = false
@@ -1199,8 +1194,7 @@ pub async fn list_subtasks_for_owner(
t.parent_task_id, t.depth, t.name, t.status, t.priority,
t.progress_summary,
(SELECT COUNT(*) FROM tasks WHERE parent_task_id = t.id) as subtask_count,
- t.version, t.is_supervisor, COALESCE(t.is_red_team, false) as is_red_team,
- COALESCE(t.hidden, false) as hidden, t.created_at, t.updated_at
+ t.version, t.is_supervisor, COALESCE(t.hidden, false) as hidden, t.created_at, t.updated_at
FROM tasks t
LEFT JOIN contracts c ON t.contract_id = c.id
WHERE t.owner_id = $1 AND t.parent_task_id = $2
@@ -1721,8 +1715,7 @@ pub async fn list_sibling_tasks(
t.parent_task_id, t.depth, t.name, t.status, t.priority,
t.progress_summary,
(SELECT COUNT(*) FROM tasks WHERE parent_task_id = t.id) as subtask_count,
- t.version, t.is_supervisor, COALESCE(t.is_red_team, false) as is_red_team,
- COALESCE(t.hidden, false) as hidden, t.created_at, t.updated_at
+ t.version, t.is_supervisor, COALESCE(t.hidden, false) as hidden, t.created_at, t.updated_at
FROM tasks t
LEFT JOIN contracts c ON t.contract_id = c.id
WHERE t.parent_task_id = $1 AND t.id != $2
@@ -1744,8 +1737,7 @@ pub async fn list_sibling_tasks(
t.parent_task_id, t.depth, t.name, t.status, t.priority,
t.progress_summary,
(SELECT COUNT(*) FROM tasks WHERE parent_task_id = t.id) as subtask_count,
- t.version, t.is_supervisor, COALESCE(t.is_red_team, false) as is_red_team,
- COALESCE(t.hidden, false) as hidden, t.created_at, t.updated_at
+ t.version, t.is_supervisor, COALESCE(t.hidden, false) as hidden, t.created_at, t.updated_at
FROM tasks t
LEFT JOIN contracts c ON t.contract_id = c.id
WHERE t.parent_task_id IS NULL AND t.id != $1
@@ -2467,15 +2459,14 @@ pub async fn create_contract_for_owner(
let phase_guard = req.phase_guard.unwrap_or(false);
let local_only = req.local_only.unwrap_or(false);
let auto_merge_local = req.auto_merge_local.unwrap_or(false);
- let red_team_enabled = req.red_team_enabled.unwrap_or(false);
// Serialize phase_config to JSON
let phase_config_json = serde_json::to_value(&phase_config).ok();
sqlx::query_as::<_, Contract>(
r#"
- INSERT INTO contracts (owner_id, name, description, contract_type, phase, autonomous_loop, phase_guard, local_only, auto_merge_local, red_team_enabled, red_team_prompt, phase_config)
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
+ INSERT INTO contracts (owner_id, name, description, contract_type, phase, autonomous_loop, phase_guard, local_only, auto_merge_local, phase_config)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
RETURNING *
"#,
)
@@ -2488,8 +2479,6 @@ pub async fn create_contract_for_owner(
.bind(phase_guard)
.bind(local_only)
.bind(auto_merge_local)
- .bind(red_team_enabled)
- .bind(&req.red_team_prompt)
.bind(phase_config_json)
.fetch_one(pool)
.await
@@ -2523,7 +2512,7 @@ pub async fn list_contracts_for_owner(
r#"
SELECT
c.id, c.name, c.description, c.contract_type, c.phase, c.status,
- c.supervisor_task_id, c.local_only, c.auto_merge_local, c.red_team_enabled, c.version, c.created_at,
+ c.supervisor_task_id, c.local_only, c.auto_merge_local, c.version, c.created_at,
(SELECT COUNT(*) FROM files WHERE contract_id = c.id) as file_count,
(SELECT COUNT(*) FROM tasks WHERE contract_id = c.id) as task_count,
(SELECT COUNT(*) FROM contract_repositories WHERE contract_id = c.id) as repository_count
@@ -2547,7 +2536,7 @@ pub async fn get_contract_summary_for_owner(
r#"
SELECT
c.id, c.name, c.description, c.contract_type, c.phase, c.status,
- c.supervisor_task_id, c.local_only, c.auto_merge_local, c.red_team_enabled, c.version, c.created_at,
+ c.supervisor_task_id, c.local_only, c.auto_merge_local, c.version, c.created_at,
(SELECT COUNT(*) FROM files WHERE contract_id = c.id) as file_count,
(SELECT COUNT(*) FROM tasks WHERE contract_id = c.id) as task_count,
(SELECT COUNT(*) FROM contract_repositories WHERE contract_id = c.id) as repository_count
@@ -3118,8 +3107,7 @@ pub async fn list_tasks_in_contract(
t.parent_task_id, t.depth, t.name, t.status, t.priority,
t.progress_summary,
(SELECT COUNT(*) FROM tasks WHERE parent_task_id = t.id) as subtask_count,
- t.version, t.is_supervisor, COALESCE(t.is_red_team, false) as is_red_team,
- COALESCE(t.hidden, false) as hidden, t.created_at, t.updated_at
+ t.version, t.is_supervisor, COALESCE(t.hidden, false) as hidden, t.created_at, t.updated_at
FROM tasks t
LEFT JOIN contracts c ON t.contract_id = c.id
WHERE t.contract_id = $1 AND t.owner_id = $2
@@ -4774,93 +4762,6 @@ pub async fn delete_checkpoint_patches_for_task(
// =============================================================================
// Red Team Notifications
// =============================================================================
-
-/// Create a red team notification.
-/// Red team tasks use this to report issues found during implementation review.
-pub async fn create_red_team_notification(
- pool: &PgPool,
- contract_id: Uuid,
- red_team_task_id: Uuid,
- message: &str,
- severity: &str,
- related_task_id: Option<Uuid>,
- file_path: Option<&str>,
- context: Option<&str>,
-) -> Result<RedTeamNotification, RepositoryError> {
- sqlx::query_as::<_, RedTeamNotification>(
- r#"
- INSERT INTO red_team_notifications
- (contract_id, red_team_task_id, related_task_id, message, severity, file_path, context)
- VALUES ($1, $2, $3, $4, $5, $6, $7)
- RETURNING *
- "#,
- )
- .bind(contract_id)
- .bind(red_team_task_id)
- .bind(related_task_id)
- .bind(message)
- .bind(severity)
- .bind(file_path)
- .bind(context)
- .fetch_one(pool)
- .await
- .map_err(RepositoryError::Database)
-}
-
-/// Mark a notification as delivered to the supervisor.
-pub async fn mark_notification_delivered(
- pool: &PgPool,
- notification_id: Uuid,
-) -> Result<RedTeamNotification, RepositoryError> {
- sqlx::query_as::<_, RedTeamNotification>(
- r#"
- UPDATE red_team_notifications
- SET delivered = TRUE, delivered_at = NOW()
- WHERE id = $1
- RETURNING *
- "#,
- )
- .bind(notification_id)
- .fetch_one(pool)
- .await
- .map_err(RepositoryError::Database)
-}
-
-/// Get the red team task for a contract (if one exists).
-/// Returns the most recently created red team task for the contract.
-pub async fn get_red_team_task_for_contract(
- pool: &PgPool,
- contract_id: Uuid,
-) -> Result<Option<Task>, RepositoryError> {
- sqlx::query_as::<_, Task>(
- r#"
- SELECT * FROM tasks
- WHERE contract_id = $1 AND is_red_team = TRUE
- ORDER BY created_at DESC
- LIMIT 1
- "#,
- )
- .bind(contract_id)
- .fetch_optional(pool)
- .await
- .map_err(RepositoryError::Database)
-}
-
-/// Get the count of notifications for a red team task.
-pub async fn get_notification_count_for_task(
- pool: &PgPool,
- red_team_task_id: Uuid,
-) -> Result<i64, RepositoryError> {
- let result: (i64,) = sqlx::query_as(
- "SELECT COUNT(*) FROM red_team_notifications WHERE red_team_task_id = $1",
- )
- .bind(red_team_task_id)
- .fetch_one(pool)
- .await
- .map_err(RepositoryError::Database)?;
- Ok(result.0)
-}
-
// =============================================================================
// Supervisor Status API Helpers
// =============================================================================
diff --git a/makima/src/server/handlers/contract_chat.rs b/makima/src/server/handlers/contract_chat.rs
index b025485..2d54894 100644
--- a/makima/src/server/handlers/contract_chat.rs
+++ b/makima/src/server/handlers/contract_chat.rs
@@ -1362,7 +1362,6 @@ async fn handle_contract_request(
continue_from_task_id: None,
copy_files: None,
is_supervisor: false,
- is_red_team: false,
checkpoint_sha: None,
branched_from_task_id: None,
conversation_history: None,
@@ -1460,7 +1459,6 @@ async fn handle_contract_request(
continue_from_task_id: None,
copy_files: None,
is_supervisor: false,
- is_red_team: false,
checkpoint_sha: None,
branched_from_task_id: None,
conversation_history: None,
@@ -2213,8 +2211,7 @@ async fn handle_contract_request(
continue_from_task_id: previous_task_id,
copy_files: None,
is_supervisor: false,
- is_red_team: false,
- checkpoint_sha: None,
+ checkpoint_sha: None,
branched_from_task_id: None,
conversation_history: None,
supervisor_worktree_task_id: None, // Not spawned by supervisor
@@ -2612,8 +2609,6 @@ async fn handle_contract_request(
phase_guard: None,
local_only: None,
auto_merge_local: None,
- red_team_enabled: None,
- red_team_prompt: None,
template_id: None,
};
@@ -2736,8 +2731,7 @@ async fn handle_contract_request(
continue_from_task_id: None,
copy_files: None,
is_supervisor: false,
- is_red_team: false,
- checkpoint_sha: None,
+ checkpoint_sha: None,
branched_from_task_id: None,
conversation_history: None,
supervisor_worktree_task_id: None, // Not spawned by supervisor
diff --git a/makima/src/server/handlers/contracts.rs b/makima/src/server/handlers/contracts.rs
index 01b4610..8c8cabf 100644
--- a/makima/src/server/handlers/contracts.rs
+++ b/makima/src/server/handlers/contracts.rs
@@ -363,7 +363,6 @@ pub async fn create_contract(
continue_from_task_id: None,
copy_files: None,
is_supervisor: true,
- is_red_team: false,
checkpoint_sha: None,
priority: 0,
merge_mode: None,
@@ -438,7 +437,6 @@ pub async fn create_contract(
supervisor_task_id: contract.supervisor_task_id,
local_only: contract.local_only,
auto_merge_local: contract.auto_merge_local,
- red_team_enabled: contract.red_team_enabled,
file_count: 0,
task_count: 0,
repository_count: 0,
@@ -462,7 +460,6 @@ pub async fn create_contract(
supervisor_task_id: contract.supervisor_task_id,
local_only: contract.local_only,
auto_merge_local: contract.auto_merge_local,
- red_team_enabled: contract.red_team_enabled,
file_count: 0,
task_count: 0,
repository_count: 0,
@@ -593,7 +590,6 @@ pub async fn update_contract(
supervisor_task_id: contract.supervisor_task_id,
local_only: contract.local_only,
auto_merge_local: contract.auto_merge_local,
- red_team_enabled: contract.red_team_enabled,
file_count: 0,
task_count: 0,
repository_count: 0,
@@ -1523,7 +1519,6 @@ pub async fn change_phase(
supervisor_task_id: updated_contract.supervisor_task_id,
local_only: updated_contract.local_only,
auto_merge_local: updated_contract.auto_merge_local,
- red_team_enabled: updated_contract.red_team_enabled,
file_count: 0,
task_count: 0,
repository_count: 0,
diff --git a/makima/src/server/handlers/mesh.rs b/makima/src/server/handlers/mesh.rs
index af77b56..fe9ffc0 100644
--- a/makima/src/server/handlers/mesh.rs
+++ b/makima/src/server/handlers/mesh.rs
@@ -2613,7 +2613,6 @@ pub async fn reassign_task(
plan: updated_plan.clone(),
parent_task_id: task.parent_task_id,
is_supervisor: task.is_supervisor,
- is_red_team: task.is_red_team,
priority: task.priority,
repository_url: task.repository_url.clone(),
base_branch: task.base_branch.clone(),
@@ -3390,7 +3389,6 @@ pub async fn fork_task(
plan: req.new_task_plan.clone(),
parent_task_id: None, // Forked tasks are independent
is_supervisor: false,
- is_red_team: false,
priority: task.priority,
repository_url: task.repository_url.clone(),
base_branch: task.base_branch.clone(),
@@ -3549,7 +3547,6 @@ pub async fn resume_from_checkpoint(
plan: req.plan,
parent_task_id: None,
is_supervisor: false,
- is_red_team: false,
priority: task.priority,
repository_url: task.repository_url.clone(),
base_branch: task.base_branch.clone(),
@@ -3886,7 +3883,6 @@ pub async fn branch_task(
plan: req.message,
parent_task_id: None,
is_supervisor: false,
- is_red_team: false,
priority: source_task.priority,
repository_url: source_task.repository_url.clone(),
base_branch: source_task.base_branch.clone(),
diff --git a/makima/src/server/handlers/mesh_chat.rs b/makima/src/server/handlers/mesh_chat.rs
index eee899f..a6a3a3c 100644
--- a/makima/src/server/handlers/mesh_chat.rs
+++ b/makima/src/server/handlers/mesh_chat.rs
@@ -1017,7 +1017,6 @@ async fn handle_mesh_request(
continue_from_task_id: None,
copy_files: None,
is_supervisor: false,
- is_red_team: false,
checkpoint_sha: None,
branched_from_task_id: None,
conversation_history: None,
diff --git a/makima/src/server/handlers/mesh_daemon.rs b/makima/src/server/handlers/mesh_daemon.rs
index 34e2cc3..cb929ea 100644
--- a/makima/src/server/handlers/mesh_daemon.rs
+++ b/makima/src/server/handlers/mesh_daemon.rs
@@ -1870,6 +1870,68 @@ async fn handle_daemon_connection(socket: WebSocket, state: SharedState, auth_re
}
}
}
+ } else if let (Some(patch_b64), Some(base_sha)) = (&patch_data, &patch_base_sha) {
+ // Ephemeral patch-only checkpoint (no git commit)
+ // Store patch directly in checkpoint_patches without a task_checkpoint
+ if let Some(pool) = state.db_pool.as_ref() {
+ match base64::engine::general_purpose::STANDARD.decode(patch_b64) {
+ Ok(patch_bytes) => {
+ let files_count = patch_files_count.unwrap_or(0);
+ // Default TTL: 7 days (168 hours)
+ let ttl_hours = 168i64;
+ match repository::create_checkpoint_patch(
+ pool,
+ task_id,
+ None, // No checkpoint_id for ephemeral patches
+ base_sha,
+ &patch_bytes,
+ files_count,
+ ttl_hours,
+ ).await {
+ Ok(patch) => {
+ tracing::info!(
+ task_id = %task_id,
+ patch_id = %patch.id,
+ patch_size = patch_bytes.len(),
+ files_count = files_count,
+ "Ephemeral patch stored for recovery"
+ );
+
+ state.broadcast_task_output(TaskOutputNotification {
+ task_id,
+ owner_id: Some(owner_id),
+ message_type: "system".to_string(),
+ content: format!(
+ "✓ Patch saved: {} ({} files)",
+ message,
+ files_count
+ ),
+ tool_name: None,
+ tool_input: None,
+ is_error: Some(false),
+ cost_usd: None,
+ duration_ms: None,
+ is_partial: false,
+ });
+ }
+ Err(e) => {
+ tracing::warn!(
+ task_id = %task_id,
+ error = %e,
+ "Failed to store ephemeral patch"
+ );
+ }
+ }
+ }
+ Err(e) => {
+ tracing::warn!(
+ task_id = %task_id,
+ error = %e,
+ "Failed to decode ephemeral patch base64 data"
+ );
+ }
+ }
+ }
}
} else {
// Broadcast failure
diff --git a/makima/src/server/handlers/mesh_red_team.rs b/makima/src/server/handlers/mesh_red_team.rs
deleted file mode 100644
index c5af60e..0000000
--- a/makima/src/server/handlers/mesh_red_team.rs
+++ /dev/null
@@ -1,497 +0,0 @@
-//! HTTP handlers for red team mesh operations.
-//!
-//! These endpoints are used by red team tasks (via the makima CLI) to notify
-//! supervisors of potential issues and query their own status.
-
-use axum::{
- extract::State,
- http::{HeaderMap, StatusCode},
- response::IntoResponse,
- Json,
-};
-use serde::{Deserialize, Serialize};
-use utoipa::ToSchema;
-use uuid::Uuid;
-
-use crate::db::repository;
-use crate::server::handlers::mesh::{extract_auth, AuthSource};
-use crate::server::messages::ApiError;
-use crate::server::state::{DaemonCommand, SharedState};
-
-// =============================================================================
-// Request/Response Types
-// =============================================================================
-
-/// Severity level for red team notifications.
-#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
-#[serde(rename_all = "lowercase")]
-pub enum RedTeamSeverity {
- /// Informational notice - minor issue or suggestion
- Info,
- /// Warning - potential problem that should be reviewed
- Warning,
- /// Critical - serious issue requiring immediate attention
- Critical,
-}
-
-impl Default for RedTeamSeverity {
- fn default() -> Self {
- Self::Warning
- }
-}
-
-impl std::fmt::Display for RedTeamSeverity {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- Self::Info => write!(f, "INFO"),
- Self::Warning => write!(f, "WARNING"),
- Self::Critical => write!(f, "CRITICAL"),
- }
- }
-}
-
-/// Request to notify the supervisor of a potential issue.
-#[derive(Debug, Deserialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct RedTeamNotifyRequest {
- /// The issue description/message to send to the supervisor
- pub message: String,
- /// Severity level of the issue
- #[serde(default)]
- pub severity: RedTeamSeverity,
- /// ID of the task being reviewed (optional - if not provided, assumes general contract concern)
- pub related_task_id: Option<Uuid>,
- /// File path related to the issue (optional)
- pub file_path: Option<String>,
- /// Additional context about the issue
- pub context: Option<String>,
-}
-
-/// Response from the notify endpoint.
-#[derive(Debug, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct RedTeamNotifyResponse {
- /// Unique ID for this notification
- pub notification_id: Uuid,
- /// Whether the notification was successfully delivered to the supervisor
- pub delivered: bool,
- /// The supervisor task ID that received the notification
- pub supervisor_task_id: Option<Uuid>,
-}
-
-/// Response from the status endpoint.
-#[derive(Debug, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct RedTeamStatusResponse {
- /// Contract ID being monitored
- pub contract_id: Uuid,
- /// Red team task ID
- pub red_team_task_id: Uuid,
- /// Current task status
- pub status: String,
- /// Number of notifications sent so far
- pub notifications_sent: i64,
-}
-
-/// Red team notification record stored in database.
-#[derive(Debug, Clone, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct RedTeamNotification {
- pub id: Uuid,
- pub red_team_task_id: Uuid,
- pub contract_id: Uuid,
- pub message: String,
- pub severity: String,
- pub related_task_id: Option<Uuid>,
- pub file_path: Option<String>,
- pub context: Option<String>,
- pub delivered: bool,
- pub created_at: chrono::DateTime<chrono::Utc>,
-}
-
-// =============================================================================
-// Helper Functions
-// =============================================================================
-
-/// Verify the request comes from a red team task and extract ownership info.
-///
-/// Returns (task_id, owner_id, contract_id) on success.
-async fn verify_red_team_auth(
- state: &SharedState,
- headers: &HeaderMap,
-) -> Result<(Uuid, Uuid, Uuid), (StatusCode, Json<ApiError>)> {
- let auth = extract_auth(state, headers);
-
- let task_id = match auth {
- AuthSource::ToolKey(task_id) => task_id,
- _ => {
- return Err((
- StatusCode::UNAUTHORIZED,
- Json(ApiError::new(
- "UNAUTHORIZED",
- "Red team endpoints require tool key auth",
- )),
- ));
- }
- };
-
- // Get the task to verify it's a red team task and get owner_id
- let pool = state.db_pool.as_ref().ok_or_else(|| {
- (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- })?;
-
- let task = repository::get_task(pool, task_id)
- .await
- .map_err(|e| {
- tracing::error!(error = %e, "Failed to get red team task");
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", "Failed to verify red team task")),
- )
- })?
- .ok_or_else(|| {
- (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Task not found")),
- )
- })?;
-
- // Verify task is a red team task
- // NOTE: This requires the is_red_team field to be added to the Task struct.
- // For now, we check if the task name contains "red-team" or "red_team" as a fallback.
- let is_red_team = task.name.to_lowercase().contains("red-team")
- || task.name.to_lowercase().contains("red_team")
- || task.name.to_lowercase().contains("redteam");
-
- if !is_red_team {
- return Err((
- StatusCode::FORBIDDEN,
- Json(ApiError::new(
- "NOT_RED_TEAM",
- "Only red team tasks can use these endpoints",
- )),
- ));
- }
-
- // Red team tasks must be associated with a contract
- let contract_id = task.contract_id.ok_or_else(|| {
- (
- StatusCode::BAD_REQUEST,
- Json(ApiError::new(
- "NO_CONTRACT",
- "Red team task must be associated with a contract",
- )),
- )
- })?;
-
- Ok((task_id, task.owner_id, contract_id))
-}
-
-/// Format an alert message for the supervisor.
-///
-/// Creates a formatted alert with clear visual markers to grab attention.
-fn format_alert_message(
- severity: &RedTeamSeverity,
- message: &str,
- related_task_id: Option<Uuid>,
- file_path: Option<&str>,
- context: Option<&str>,
-) -> String {
- let severity_marker = match severity {
- RedTeamSeverity::Info => "ℹ️",
- RedTeamSeverity::Warning => "⚠️",
- RedTeamSeverity::Critical => "🚨",
- };
-
- let border = match severity {
- RedTeamSeverity::Info => "─".repeat(60),
- RedTeamSeverity::Warning => "━".repeat(60),
- RedTeamSeverity::Critical => "═".repeat(60),
- };
-
- let mut alert = format!(
- r#"
-{}
-{} [RED TEAM ALERT] - {}
-{}
-
-Issue: {}
-"#,
- border, severity_marker, severity, border, message
- );
-
- if let Some(task_id) = related_task_id {
- alert.push_str(&format!("\nRelated Task: {}\n", task_id));
- }
-
- if let Some(path) = file_path {
- alert.push_str(&format!("File: {}\n", path));
- }
-
- if let Some(ctx) = context {
- alert.push_str(&format!("\nContext:\n{}\n", ctx));
- }
-
- // Add action suggestions based on severity
- let actions = match severity {
- RedTeamSeverity::Info => {
- "Suggested Actions:\n- Review when convenient\n- Consider if changes are needed"
- }
- RedTeamSeverity::Warning => {
- "Suggested Actions:\n- Review the flagged item soon\n- Check if this deviates from the contract\n- Consider pausing related work until reviewed"
- }
- RedTeamSeverity::Critical => {
- "Suggested Actions:\n- STOP related work immediately\n- Review the flagged item urgently\n- Verify compliance with contract requirements\n- Consider reverting recent changes if necessary"
- }
- };
-
- alert.push_str(&format!("\n{}\n{}\n", actions, border));
-
- alert
-}
-
-// =============================================================================
-// Handlers
-// =============================================================================
-
-/// Notify the supervisor of a potential issue.
-///
-/// POST /api/v1/mesh/red-team/notify
-///
-/// This endpoint allows red team tasks to alert supervisors about issues they've
-/// identified during code review. The notification is sent as a message to the
-/// supervisor task.
-#[utoipa::path(
- post,
- path = "/api/v1/mesh/red-team/notify",
- request_body = RedTeamNotifyRequest,
- responses(
- (status = 200, description = "Notification sent", body = RedTeamNotifyResponse),
- (status = 401, description = "Unauthorized - tool key required"),
- (status = 403, description = "Forbidden - not a red team task"),
- (status = 404, description = "Task not found"),
- (status = 503, description = "Database not available"),
- (status = 500, description = "Internal server error"),
- ),
- security(
- ("tool_key" = [])
- ),
- tag = "Mesh Red Team"
-)]
-pub async fn notify_supervisor(
- State(state): State<SharedState>,
- headers: HeaderMap,
- Json(request): Json<RedTeamNotifyRequest>,
-) -> impl IntoResponse {
- let (red_team_task_id, owner_id, contract_id) =
- match verify_red_team_auth(&state, &headers).await {
- Ok(ids) => ids,
- Err(e) => return e.into_response(),
- };
-
- let pool = state.db_pool.as_ref().unwrap();
-
- // Generate notification ID
- let notification_id = Uuid::new_v4();
-
- // Get the contract to find the supervisor task
- let contract = match repository::get_contract_for_owner(pool, contract_id, owner_id).await {
- Ok(Some(c)) => c,
- Ok(None) => {
- return (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Contract not found")),
- )
- .into_response();
- }
- Err(e) => {
- tracing::error!(error = %e, "Failed to get contract");
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", "Failed to get contract")),
- )
- .into_response();
- }
- };
-
- let supervisor_task_id = contract.supervisor_task_id;
-
- // Format the alert message
- let alert_message = format_alert_message(
- &request.severity,
- &request.message,
- request.related_task_id,
- request.file_path.as_deref(),
- request.context.as_deref(),
- );
-
- // Record the notification in the database as a history event
- let event_data = serde_json::json!({
- "notification_id": notification_id.to_string(),
- "red_team_task_id": red_team_task_id.to_string(),
- "severity": request.severity.to_string(),
- "message": request.message,
- "related_task_id": request.related_task_id.map(|id| id.to_string()),
- "file_path": request.file_path,
- "context": request.context,
- });
-
- let _ = repository::record_history_event(
- pool,
- owner_id,
- Some(contract_id),
- Some(red_team_task_id),
- "red_team_alert",
- Some(&request.severity.to_string().to_lowercase()),
- Some(&request.message),
- event_data,
- )
- .await;
-
- // Try to send the message to the supervisor
- let mut delivered = false;
- if let Some(sup_task_id) = supervisor_task_id {
- // Get the supervisor task to find its daemon
- if let Ok(Some(supervisor_task)) = repository::get_task(pool, sup_task_id).await {
- if let Some(daemon_id) = supervisor_task.daemon_id {
- // Send the alert message to the supervisor
- let cmd = DaemonCommand::SendMessage {
- task_id: sup_task_id,
- message: alert_message.clone(),
- };
-
- if let Err(e) = state.send_daemon_command(daemon_id, cmd).await {
- tracing::warn!(
- error = %e,
- supervisor_task_id = %sup_task_id,
- daemon_id = %daemon_id,
- "Failed to send red team alert to supervisor"
- );
- } else {
- delivered = true;
- tracing::info!(
- notification_id = %notification_id,
- red_team_task_id = %red_team_task_id,
- supervisor_task_id = %sup_task_id,
- severity = %request.severity,
- "Red team alert delivered to supervisor"
- );
- }
- } else {
- tracing::warn!(
- supervisor_task_id = %sup_task_id,
- "Supervisor task has no assigned daemon - alert not delivered"
- );
- }
- }
- } else {
- tracing::warn!(
- contract_id = %contract_id,
- "Contract has no supervisor task - alert not delivered"
- );
- }
-
- (
- StatusCode::OK,
- Json(RedTeamNotifyResponse {
- notification_id,
- delivered,
- supervisor_task_id,
- }),
- )
- .into_response()
-}
-
-/// Get the status of the red team task.
-///
-/// GET /api/v1/mesh/red-team/status
-///
-/// Returns information about the current red team task including the contract
-/// being monitored and notification statistics.
-#[utoipa::path(
- get,
- path = "/api/v1/mesh/red-team/status",
- responses(
- (status = 200, description = "Red team status", body = RedTeamStatusResponse),
- (status = 401, description = "Unauthorized - tool key required"),
- (status = 403, description = "Forbidden - not a red team task"),
- (status = 404, description = "Task not found"),
- (status = 503, description = "Database not available"),
- (status = 500, description = "Internal server error"),
- ),
- security(
- ("tool_key" = [])
- ),
- tag = "Mesh Red Team"
-)]
-pub async fn get_status(
- State(state): State<SharedState>,
- headers: HeaderMap,
-) -> impl IntoResponse {
- let (red_team_task_id, owner_id, contract_id) =
- match verify_red_team_auth(&state, &headers).await {
- Ok(ids) => ids,
- Err(e) => return e.into_response(),
- };
-
- let pool = state.db_pool.as_ref().unwrap();
-
- // Get the red team task status
- let task = match repository::get_task(pool, red_team_task_id).await {
- Ok(Some(t)) => t,
- Ok(None) => {
- return (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", "Red team task not found")),
- )
- .into_response();
- }
- Err(e) => {
- tracing::error!(error = %e, "Failed to get red team task");
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", "Failed to get task")),
- )
- .into_response();
- }
- };
-
- // Count notifications sent by this red team task
- // Query history_events for red_team_alert events from this task
- let notifications_sent = match sqlx::query_scalar::<_, i64>(
- r#"
- SELECT COUNT(*)
- FROM history_events
- WHERE owner_id = $1
- AND contract_id = $2
- AND task_id = $3
- AND event_type = 'red_team_alert'
- "#,
- )
- .bind(owner_id)
- .bind(contract_id)
- .bind(red_team_task_id)
- .fetch_one(pool)
- .await
- {
- Ok(count) => count,
- Err(e) => {
- tracing::warn!(error = %e, "Failed to count red team notifications");
- 0
- }
- };
-
- (
- StatusCode::OK,
- Json(RedTeamStatusResponse {
- contract_id,
- red_team_task_id,
- status: task.status,
- notifications_sent,
- }),
- )
- .into_response()
-}
diff --git a/makima/src/server/handlers/mesh_supervisor.rs b/makima/src/server/handlers/mesh_supervisor.rs
index a29b666..43388a8 100644
--- a/makima/src/server/handlers/mesh_supervisor.rs
+++ b/makima/src/server/handlers/mesh_supervisor.rs
@@ -37,10 +37,6 @@ pub struct SpawnTaskRequest {
pub checkpoint_sha: Option<String>,
/// Repository URL for the task (optional - if not provided, will be looked up from contract).
pub repository_url: Option<String>,
- /// If true, create a separate worktree for the task (requires merge after).
- /// If false (default), the task shares the supervisor's worktree.
- #[serde(default)]
- pub use_own_worktree: bool,
}
/// Request to wait for task completion.
@@ -610,8 +606,8 @@ pub async fn spawn_task(
}
// Create task request
- // Share supervisor's worktree by default; separate worktree only when explicitly requested
- let supervisor_worktree_task_id = if request.use_own_worktree { None } else { Some(supervisor_id) };
+ // All tasks share the supervisor's worktree
+ let supervisor_worktree_task_id = Some(supervisor_id);
let create_req = CreateTaskRequest {
name: request.name.clone(),
@@ -621,7 +617,6 @@ pub async fn spawn_task(
contract_id: Some(request.contract_id),
parent_task_id: request.parent_task_id,
is_supervisor: false,
- is_red_team: false,
checkpoint_sha: request.checkpoint_sha.clone(),
merge_mode: Some("manual".to_string()),
priority: 0,
@@ -733,8 +728,8 @@ pub async fn spawn_task(
patch_base_sha: None,
local_only: contract.local_only,
auto_merge_local: contract.auto_merge_local,
- // Share supervisor's worktree by default; separate worktree only when explicitly requested
- supervisor_worktree_task_id: if request.use_own_worktree { None } else { Some(supervisor_id) },
+ // All tasks share the supervisor's worktree
+ supervisor_worktree_task_id: Some(supervisor_id),
};
if let Err(e) = state.send_daemon_command(daemon.id, cmd).await {
@@ -762,66 +757,6 @@ pub async fn spawn_task(
updated_by: "supervisor".to_string(),
});
- // Check if we should spawn a red team task
- // Conditions:
- // 1. This is not a supervisor task
- // 2. This is not already a red team task
- // 3. Contract has red_team_enabled = true
- // 4. No red team task exists for this contract yet
- if !updated_task.is_supervisor && !updated_task.is_red_team && contract.red_team_enabled {
- if let Some(contract_id) = updated_task.contract_id {
- // Check if a red team task already exists
- match repository::get_red_team_task_for_contract(pool, contract_id).await {
- Ok(None) => {
- // No red team task exists, spawn one
- tracing::info!(
- contract_id = %contract_id,
- work_task_id = %updated_task.id,
- "Spawning red team task for contract (first work task started)"
- );
- match spawn_red_team_task(
- pool,
- &state,
- contract_id,
- owner_id,
- &contract.name,
- &contract.phase,
- contract.red_team_prompt.as_deref(),
- ).await {
- Ok(red_team_task) => {
- tracing::info!(
- contract_id = %contract_id,
- red_team_task_id = %red_team_task.id,
- "Red team task spawned successfully"
- );
- }
- Err(e) => {
- // Log error but don't fail the work task spawn
- tracing::error!(
- contract_id = %contract_id,
- error = %e,
- "Failed to spawn red team task"
- );
- }
- }
- }
- Ok(Some(existing)) => {
- tracing::debug!(
- contract_id = %contract_id,
- red_team_task_id = %existing.id,
- "Red team task already exists for contract"
- );
- }
- Err(e) => {
- tracing::error!(
- contract_id = %contract_id,
- error = %e,
- "Error checking for existing red team task"
- );
- }
- }
- }
- }
}
break;
}
@@ -2584,239 +2519,6 @@ pub async fn rewind_conversation(
}
// =============================================================================
-// Red Team Task Spawning
-// =============================================================================
-
-/// Generate the system prompt/plan for a red team task.
-///
-/// This creates detailed instructions for the red team monitor, including
-/// what to look for, severity levels, and how to report issues.
-pub fn generate_red_team_plan(
- contract_name: &str,
- contract_phase: &str,
- custom_prompt: Option<&str>,
-) -> String {
- let custom_criteria = if let Some(prompt) = custom_prompt {
- format!(
- r#"
-
-## Custom Review Criteria
-
-The contract owner has specified additional review criteria:
-{}
-"#,
- prompt
- )
- } else {
- String::new()
- };
-
- format!(
- r#"# Red Team Monitor
-
-You are an adversarial quality reviewer for a software development contract. Your role is to monitor work task outputs in real-time and flag potential issues BEFORE they compound into larger problems.
-
-## Your Mission
-
-Monitor all task outputs and verify:
-1. **Plan Adherence**: Are tasks following the implementation plan?
-2. **Code Quality**: Does the code meet repository standards?
-3. **Contract Requirements**: Does the implementation match the specification?
-4. **Best Practices**: Are there obvious anti-patterns or issues?
-
-## Access Available
-
-You have read-only access to:
-- Task outputs (streamed in real-time)
-- Task diffs (code changes)
-- Contract specifications and plan documents
-- Repository configuration files (CONTRIBUTING.md, linting configs, etc.)
-
-## How to Monitor
-
-1. **Subscribe to task outputs**: You'll receive outputs from all work tasks
-2. **Analyze code changes**: Request diffs for completed tasks
-3. **Cross-reference**: Compare outputs against the plan and specifications
-4. **Report issues**: Use `makima red-team notify` when you detect problems
-
-## When to Notify
-
-NOTIFY the supervisor when you observe:
-- **Critical**: Security vulnerabilities, data loss risks, breaking changes
-- **High**: Significant deviations from the plan, major code quality issues
-- **Medium**: Missing tests, suboptimal implementations, minor standard violations
-- **Low**: Style inconsistencies, documentation gaps (use sparingly)
-
-## What NOT to Do
-
-- Do NOT nitpick minor style issues (that's what linters are for)
-- Do NOT block progress for trivial concerns
-- Do NOT write code or make changes yourself
-- Do NOT notify for things that are already in progress and being addressed
-- Do NOT create duplicate notifications for the same issue
-
-## Notification Format
-
-When notifying, always include:
-1. A clear, concise description of the issue
-2. The severity level (critical/high/medium/low)
-3. The related task ID if applicable
-4. The specific file or code location if known
-5. Why this matters (reference to plan, spec, or standards)
-
-## Example Notification
-
-```
-makima red-team notify "Task is implementing authentication with plaintext password storage, which contradicts the security requirements in the specification document" \
- --severity critical \
- --task <task_id> \
- --file "src/auth/user.rs" \
- --context "Specification section 3.2 requires bcrypt hashing for all passwords"
-```
-{}
-## Contract Context
-
-Contract: {}
-Phase: {}
-
-Focus your monitoring on outputs that relate to the active work tasks. Prioritize issues that could affect the success of the contract or introduce technical debt.
-"#,
- custom_criteria, contract_name, contract_phase
- )
-}
-
-/// Spawn a red team task for a contract.
-///
-/// This creates a red team monitor task that will observe work task outputs
-/// and can notify the supervisor about potential issues.
-pub async fn spawn_red_team_task(
- pool: &sqlx::PgPool,
- state: &SharedState,
- contract_id: Uuid,
- owner_id: Uuid,
- contract_name: &str,
- contract_phase: &str,
- red_team_prompt: Option<&str>,
-) -> Result<Task, String> {
- // Generate the red team plan/prompt
- let plan = generate_red_team_plan(contract_name, contract_phase, red_team_prompt);
-
- // Create task request
- let create_req = CreateTaskRequest {
- name: "Red Team Monitor".to_string(),
- description: Some("Adversarial review task monitoring work task outputs".to_string()),
- plan,
- contract_id: Some(contract_id),
- parent_task_id: None,
- is_supervisor: false,
- is_red_team: true,
- priority: 0,
- repository_url: None, // Red team doesn't need a repo
- base_branch: None,
- target_branch: None,
- merge_mode: None,
- target_repo_path: None,
- completion_action: None,
- continue_from_task_id: None,
- copy_files: None,
- checkpoint_sha: None,
- branched_from_task_id: None,
- conversation_history: None,
- supervisor_worktree_task_id: None, // Red team uses its own working area
- };
-
- // Create task in DB
- let task = repository::create_task_for_owner(pool, owner_id, create_req)
- .await
- .map_err(|e| format!("Failed to create red team task: {}", e))?;
-
- tracing::info!(
- contract_id = %contract_id,
- red_team_task_id = %task.id,
- "Created red team task for contract"
- );
-
- // Find a daemon to run the red team task
- for entry in state.daemon_connections.iter() {
- let daemon = entry.value();
- if daemon.owner_id == owner_id {
- // Update task with daemon assignment
- let update_req = UpdateTaskRequest {
- status: Some("starting".to_string()),
- daemon_id: Some(daemon.id),
- version: Some(task.version),
- ..Default::default()
- };
-
- match repository::update_task_for_owner(pool, task.id, owner_id, update_req).await {
- Ok(Some(updated_task)) => {
- // Send spawn command to daemon
- let cmd = DaemonCommand::SpawnTask {
- task_id: updated_task.id,
- task_name: updated_task.name.clone(),
- plan: updated_task.plan.clone(),
- repo_url: None, // Red team doesn't need a repo
- base_branch: None,
- target_branch: None,
- parent_task_id: None,
- depth: 0,
- is_orchestrator: false,
- target_repo_path: None,
- completion_action: None,
- continue_from_task_id: None,
- copy_files: None,
- contract_id: Some(contract_id),
- is_supervisor: false,
- autonomous_loop: false,
- resume_session: false,
- conversation_history: None,
- patch_data: None,
- patch_base_sha: None,
- local_only: true, // Red team is always local-only
- auto_merge_local: false, // Red team doesn't auto-merge
- supervisor_worktree_task_id: None,
- };
-
- if let Err(e) = state.send_daemon_command(daemon.id, cmd).await {
- tracing::warn!(
- error = %e,
- daemon_id = %daemon.id,
- red_team_task_id = %task.id,
- "Failed to send red team spawn command"
- );
- // Rollback
- let rollback_req = UpdateTaskRequest {
- status: Some("pending".to_string()),
- clear_daemon_id: true,
- ..Default::default()
- };
- let _ = repository::update_task_for_owner(pool, task.id, owner_id, rollback_req).await;
- } else {
- tracing::info!(
- red_team_task_id = %task.id,
- daemon_id = %daemon.id,
- "Red team task spawn command sent"
- );
- return Ok(updated_task);
- }
- }
- Ok(None) => {
- tracing::warn!(red_team_task_id = %task.id, "Red team task not found when updating daemon_id");
- }
- Err(e) => {
- tracing::error!(red_team_task_id = %task.id, error = %e, "Failed to update red team task with daemon_id");
- }
- }
- break;
- }
- }
-
- // Return the task even if we couldn't start it on a daemon
- // It will remain pending and can be started later
- Ok(task)
-}
-
-// =============================================================================
// Supervisor State Persistence Helpers (Task 3.3)
// =============================================================================
diff --git a/makima/src/server/handlers/mod.rs b/makima/src/server/handlers/mod.rs
index 8af2a37..a14c4f7 100644
--- a/makima/src/server/handlers/mod.rs
+++ b/makima/src/server/handlers/mod.rs
@@ -13,7 +13,6 @@ pub mod mesh;
pub mod mesh_chat;
pub mod mesh_daemon;
pub mod mesh_merge;
-pub mod mesh_red_team;
pub mod mesh_supervisor;
pub mod mesh_ws;
pub mod repository_history;
diff --git a/makima/src/server/handlers/templates.rs b/makima/src/server/handlers/templates.rs
index 0cc5657..aa97876 100644
--- a/makima/src/server/handlers/templates.rs
+++ b/makima/src/server/handlers/templates.rs
@@ -1,27 +1,19 @@
//! Contract types API handler.
+//! Only returns built-in contract types (simple, specification, execute).
use axum::{
- extract::{Path, State},
http::StatusCode,
response::IntoResponse,
Json,
};
use serde::Serialize;
use utoipa::ToSchema;
-use uuid::Uuid;
-use crate::db::models::{
- ContractTypeTemplateRecord, ContractTypeTemplateSummary, CreateTemplateRequest,
- UpdateTemplateRequest,
-};
-use crate::db::repository;
use crate::llm::templates;
use crate::llm::templates::ContractTypeTemplate;
-use crate::server::auth::{Authenticated, MaybeAuthenticated};
-use crate::server::state::SharedState;
// =============================================================================
-// Contract Type Templates (Workflow Definitions)
+// Contract Type Templates (Built-in Only)
// =============================================================================
/// Response for listing contract types
@@ -31,14 +23,7 @@ pub struct ListContractTypesResponse {
pub contract_types: Vec<ContractTypeTemplate>,
}
-/// Response for a single custom template
-#[derive(Debug, Serialize, ToSchema)]
-#[serde(rename_all = "camelCase")]
-pub struct TemplateResponse {
- pub template: ContractTypeTemplateSummary,
-}
-
-/// List all available contract type templates (built-in + custom)
+/// List all available contract type templates (built-in only)
#[utoipa::path(
get,
path = "/api/v1/contract-types",
@@ -47,404 +32,12 @@ pub struct TemplateResponse {
),
tag = "templates"
)]
-pub async fn list_contract_types(
- State(state): State<SharedState>,
- MaybeAuthenticated(auth): MaybeAuthenticated,
-) -> impl IntoResponse {
- // Start with built-in types
- let mut contract_types = templates::all_contract_types();
-
- // If authenticated, also fetch custom templates for this owner
- if let Some(user) = auth {
- if let Some(ref pool) = state.db_pool {
- if let Ok(custom_templates) =
- repository::list_templates_for_owner(pool, user.owner_id).await
- {
- for template in custom_templates {
- contract_types.push(template_record_to_api(&template));
- }
- }
- }
- }
-
+pub async fn list_contract_types() -> impl IntoResponse {
+ // Only return built-in types (simple, specification, execute)
+ let contract_types = templates::all_contract_types();
(
StatusCode::OK,
Json(ListContractTypesResponse { contract_types }),
)
.into_response()
}
-
-/// Create a new custom contract type template
-#[utoipa::path(
- post,
- path = "/api/v1/contract-types",
- request_body = CreateTemplateRequest,
- responses(
- (status = 201, description = "Template created successfully", body = TemplateResponse),
- (status = 400, description = "Invalid request"),
- (status = 401, description = "Unauthorized"),
- (status = 409, description = "Template with this name already exists")
- ),
- tag = "templates"
-)]
-pub async fn create_template(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Json(req): Json<CreateTemplateRequest>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(serde_json::json!({
- "code": "DB_ERROR",
- "message": "Database not configured"
- })),
- )
- .into_response();
- };
-
- // Validate request
- if req.name.trim().is_empty() {
- return (
- StatusCode::BAD_REQUEST,
- Json(serde_json::json!({
- "code": "INVALID_REQUEST",
- "message": "Template name cannot be empty"
- })),
- )
- .into_response();
- }
-
- if req.phases.is_empty() {
- return (
- StatusCode::BAD_REQUEST,
- Json(serde_json::json!({
- "code": "INVALID_REQUEST",
- "message": "Template must have at least one phase"
- })),
- )
- .into_response();
- }
-
- // Validate default_phase is in the phases list
- if !req.phases.iter().any(|p| p.id == req.default_phase) {
- return (
- StatusCode::BAD_REQUEST,
- Json(serde_json::json!({
- "code": "INVALID_REQUEST",
- "message": format!("Default phase '{}' is not in the phases list", req.default_phase)
- })),
- )
- .into_response();
- }
-
- // Check that template name doesn't conflict with built-in types
- let builtin_names = ["simple", "specification", "execute"];
- if builtin_names.contains(&req.name.to_lowercase().as_str()) {
- return (
- StatusCode::CONFLICT,
- Json(serde_json::json!({
- "code": "NAME_CONFLICT",
- "message": "Cannot create a template with the same name as a built-in type"
- })),
- )
- .into_response();
- }
-
- match repository::create_template_for_owner(pool, auth.owner_id, req).await {
- Ok(template) => (
- StatusCode::CREATED,
- Json(serde_json::json!({
- "template": template_record_to_summary(&template)
- })),
- )
- .into_response(),
- Err(e) => {
- // Check for unique constraint violation
- let error_str = e.to_string();
- if error_str.contains("unique") || error_str.contains("duplicate") {
- return (
- StatusCode::CONFLICT,
- Json(serde_json::json!({
- "code": "NAME_CONFLICT",
- "message": "A template with this name already exists"
- })),
- )
- .into_response();
- }
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(serde_json::json!({
- "code": "DB_ERROR",
- "message": format!("Failed to create template: {}", e)
- })),
- )
- .into_response()
- }
- }
-}
-
-/// Get a specific contract type template by ID
-#[utoipa::path(
- get,
- path = "/api/v1/contract-types/{id}",
- params(
- ("id" = Uuid, Path, description = "Template ID")
- ),
- responses(
- (status = 200, description = "Template retrieved successfully", body = TemplateResponse),
- (status = 401, description = "Unauthorized"),
- (status = 404, description = "Template not found")
- ),
- tag = "templates"
-)]
-pub async fn get_template(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path(id): Path<Uuid>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(serde_json::json!({
- "code": "DB_ERROR",
- "message": "Database not configured"
- })),
- )
- .into_response();
- };
-
- match repository::get_template_for_owner(pool, id, auth.owner_id).await {
- Ok(Some(template)) => (
- StatusCode::OK,
- Json(serde_json::json!({
- "template": template_record_to_summary(&template)
- })),
- )
- .into_response(),
- Ok(None) => (
- StatusCode::NOT_FOUND,
- Json(serde_json::json!({
- "code": "NOT_FOUND",
- "message": "Template not found"
- })),
- )
- .into_response(),
- Err(e) => (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(serde_json::json!({
- "code": "DB_ERROR",
- "message": format!("Failed to get template: {}", e)
- })),
- )
- .into_response(),
- }
-}
-
-/// Update a contract type template
-#[utoipa::path(
- put,
- path = "/api/v1/contract-types/{id}",
- params(
- ("id" = Uuid, Path, description = "Template ID")
- ),
- request_body = UpdateTemplateRequest,
- responses(
- (status = 200, description = "Template updated successfully", body = TemplateResponse),
- (status = 400, description = "Invalid request"),
- (status = 401, description = "Unauthorized"),
- (status = 404, description = "Template not found"),
- (status = 409, description = "Version conflict")
- ),
- tag = "templates"
-)]
-pub async fn update_template(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path(id): Path<Uuid>,
- Json(req): Json<UpdateTemplateRequest>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(serde_json::json!({
- "code": "DB_ERROR",
- "message": "Database not configured"
- })),
- )
- .into_response();
- };
-
- // Validate phases if provided
- if let Some(ref phases) = req.phases {
- if phases.is_empty() {
- return (
- StatusCode::BAD_REQUEST,
- Json(serde_json::json!({
- "code": "INVALID_REQUEST",
- "message": "Template must have at least one phase"
- })),
- )
- .into_response();
- }
-
- // If default_phase is also provided, validate it's in the phases
- if let Some(ref default_phase) = req.default_phase {
- if !phases.iter().any(|p| &p.id == default_phase) {
- return (
- StatusCode::BAD_REQUEST,
- Json(serde_json::json!({
- "code": "INVALID_REQUEST",
- "message": format!("Default phase '{}' is not in the phases list", default_phase)
- })),
- )
- .into_response();
- }
- }
- }
-
- // Check that template name doesn't conflict with built-in types
- if let Some(ref name) = req.name {
- let builtin_names = ["simple", "specification", "execute"];
- if builtin_names.contains(&name.to_lowercase().as_str()) {
- return (
- StatusCode::CONFLICT,
- Json(serde_json::json!({
- "code": "NAME_CONFLICT",
- "message": "Cannot rename template to a built-in type name"
- })),
- )
- .into_response();
- }
- }
-
- match repository::update_template_for_owner(pool, id, auth.owner_id, req).await {
- Ok(Some(template)) => (
- StatusCode::OK,
- Json(serde_json::json!({
- "template": template_record_to_summary(&template)
- })),
- )
- .into_response(),
- Ok(None) => (
- StatusCode::NOT_FOUND,
- Json(serde_json::json!({
- "code": "NOT_FOUND",
- "message": "Template not found"
- })),
- )
- .into_response(),
- Err(repository::RepositoryError::VersionConflict { expected, actual }) => (
- StatusCode::CONFLICT,
- Json(serde_json::json!({
- "code": "VERSION_CONFLICT",
- "message": format!("Version conflict: expected {}, found {}", expected, actual),
- "expectedVersion": expected,
- "actualVersion": actual
- })),
- )
- .into_response(),
- Err(e) => {
- let error_str = e.to_string();
- if error_str.contains("unique") || error_str.contains("duplicate") {
- return (
- StatusCode::CONFLICT,
- Json(serde_json::json!({
- "code": "NAME_CONFLICT",
- "message": "A template with this name already exists"
- })),
- )
- .into_response();
- }
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(serde_json::json!({
- "code": "DB_ERROR",
- "message": format!("Failed to update template: {}", e)
- })),
- )
- .into_response()
- }
- }
-}
-
-/// Delete a contract type template
-#[utoipa::path(
- delete,
- path = "/api/v1/contract-types/{id}",
- params(
- ("id" = Uuid, Path, description = "Template ID")
- ),
- responses(
- (status = 204, description = "Template deleted successfully"),
- (status = 401, description = "Unauthorized"),
- (status = 404, description = "Template not found")
- ),
- tag = "templates"
-)]
-pub async fn delete_template(
- State(state): State<SharedState>,
- Authenticated(auth): Authenticated,
- Path(id): Path<Uuid>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(serde_json::json!({
- "code": "DB_ERROR",
- "message": "Database not configured"
- })),
- )
- .into_response();
- };
-
- match repository::delete_template_for_owner(pool, id, auth.owner_id).await {
- Ok(true) => StatusCode::NO_CONTENT.into_response(),
- Ok(false) => (
- StatusCode::NOT_FOUND,
- Json(serde_json::json!({
- "code": "NOT_FOUND",
- "message": "Template not found"
- })),
- )
- .into_response(),
- Err(e) => (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(serde_json::json!({
- "code": "DB_ERROR",
- "message": format!("Failed to delete template: {}", e)
- })),
- )
- .into_response(),
- }
-}
-
-// =============================================================================
-// Helper Functions
-// =============================================================================
-
-/// Convert a database template record to the API template format
-fn template_record_to_api(template: &ContractTypeTemplateRecord) -> ContractTypeTemplate {
- ContractTypeTemplate {
- id: template.id.to_string(),
- name: template.name.clone(),
- description: template.description.clone().unwrap_or_default(),
- phases: template.phases.iter().map(|p| p.id.clone()).collect(),
- default_phase: template.default_phase.clone(),
- is_builtin: false,
- }
-}
-
-/// Convert a database template record to the summary format
-fn template_record_to_summary(template: &ContractTypeTemplateRecord) -> ContractTypeTemplateSummary {
- ContractTypeTemplateSummary {
- id: template.id,
- name: template.name.clone(),
- description: template.description.clone(),
- phases: template.phases.clone(),
- default_phase: template.default_phase.clone(),
- is_builtin: false,
- version: template.version,
- created_at: template.created_at,
- }
-}
diff --git a/makima/src/server/handlers/transcript_analysis.rs b/makima/src/server/handlers/transcript_analysis.rs
index d987d08..62c65a6 100644
--- a/makima/src/server/handlers/transcript_analysis.rs
+++ b/makima/src/server/handlers/transcript_analysis.rs
@@ -280,8 +280,6 @@ pub async fn create_contract_from_analysis(
phase_guard: None,
local_only: None,
auto_merge_local: None,
- red_team_enabled: None,
- red_team_prompt: None,
template_id: None,
};
@@ -362,7 +360,6 @@ pub async fn create_contract_from_analysis(
continue_from_task_id: None,
copy_files: None,
is_supervisor: false,
- is_red_team: false,
checkpoint_sha: None,
priority: match item.priority.as_deref() {
Some("high") => 10,
@@ -537,7 +534,6 @@ pub async fn update_contract_from_analysis(
continue_from_task_id: None,
copy_files: None,
is_supervisor: false,
- is_red_team: false,
checkpoint_sha: None,
priority: 0,
merge_mode: None,
diff --git a/makima/src/server/mod.rs b/makima/src/server/mod.rs
index e5415ae..b351ac1 100644
--- a/makima/src/server/mod.rs
+++ b/makima/src/server/mod.rs
@@ -18,7 +18,7 @@ use tower_http::trace::TraceLayer;
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
-use crate::server::handlers::{api_keys, chat, contract_chat, contract_daemon, contracts, file_ws, files, history, listen, mesh, mesh_chat, mesh_daemon, mesh_merge, mesh_red_team, mesh_supervisor, mesh_ws, repository_history, speak, templates, transcript_analysis, users, versions};
+use crate::server::handlers::{api_keys, chat, contract_chat, contract_daemon, contracts, file_ws, files, history, listen, mesh, mesh_chat, mesh_daemon, mesh_merge, mesh_supervisor, mesh_ws, repository_history, speak, templates, transcript_analysis, users, versions};
use crate::server::openapi::ApiDoc;
use crate::server::state::SharedState;
@@ -132,9 +132,6 @@ pub fn make_router(state: SharedState) -> Router {
.route("/mesh/supervisor/questions", post(mesh_supervisor::ask_question))
.route("/mesh/questions", get(mesh_supervisor::list_pending_questions))
.route("/mesh/questions/{question_id}/answer", post(mesh_supervisor::answer_question))
- // Red team endpoints (for red team tasks to notify supervisors)
- .route("/mesh/red-team/notify", post(mesh_red_team::notify_supervisor))
- .route("/mesh/red-team/status", get(mesh_red_team::get_status))
// Mesh WebSocket endpoints
.route("/mesh/tasks/subscribe", get(mesh_ws::task_subscription_handler))
.route("/mesh/daemons/connect", get(mesh_daemon::daemon_handler))
@@ -216,17 +213,8 @@ pub fn make_router(state: SharedState) -> Router {
)
// Timeline endpoint (unified history for user)
.route("/timeline", get(history::get_timeline))
- // Contract type templates (workflow definitions)
- .route(
- "/contract-types",
- get(templates::list_contract_types).post(templates::create_template),
- )
- .route(
- "/contract-types/{id}",
- get(templates::get_template)
- .put(templates::update_template)
- .delete(templates::delete_template),
- )
+ // Contract type templates (built-in only)
+ .route("/contract-types", get(templates::list_contract_types))
// Settings endpoints
.route(
"/settings/repository-history",