From d670dcb72984cfa483063d161bb468704038895c Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
Date: Sat, 21 Feb 2026 19:33:44 +0000
Subject: feat: add directive ask command, log backfill & specialized DAG steps
 (#75)

* feat: soryu-co/soryu - makima: Add makima directive ask CLI command

* feat: soryu-co/soryu - makima: Update directive skill docs and planning prompt to support asking questions

* feat: soryu-co/soryu - makima: Add log stream backfill for directive tasks

* feat: soryu-co/soryu - makima: Update planning prompts to inform tasks they can ask questions

* WIP: heartbeat checkpoint

* feat: soryu-co/soryu - makima: Add ask command to directive SKILL.md documentation

* feat: soryu-co/soryu - makima: Add log stream backfill for directive task output history

* feat: soryu-co/soryu - makima: Update planning prompt to tell planning tasks they can ask questions

* WIP: heartbeat checkpoint

* feat: soryu-co/soryu - makima: Show Planning, PR, and Cleanup tasks as specialized steps in DAG
---
 .../src/components/directives/DirectiveDAG.tsx     | 114 +++++++++++++++++++--
 1 file changed, 105 insertions(+), 9 deletions(-)

(limited to 'makima/frontend/src/components/directives/DirectiveDAG.tsx')

diff --git a/makima/frontend/src/components/directives/DirectiveDAG.tsx b/makima/frontend/src/components/directives/DirectiveDAG.tsx
index 27a80ac..8c7def9 100644
--- a/makima/frontend/src/components/directives/DirectiveDAG.tsx
+++ b/makima/frontend/src/components/directives/DirectiveDAG.tsx
@@ -1,9 +1,31 @@
 import { useMemo } from "react";
 import type { DirectiveStep } from "../../lib/api";
 import { StepNode } from "./StepNode";
+import {
+  OrchestratorStepNode,
+  type OrchestratorStepType,
+  type OrchestratorStepStatus,
+} from "./OrchestratorStepNode";
+
+export interface VirtualStep {
+  type: OrchestratorStepType;
+  taskId: string;
+  status: OrchestratorStepStatus;
+  label: string;
+  hasQuestions?: boolean;
+}
+
+export interface SpecializedStep {
+  id: string;
+  name: string;
+  type: "orchestrator" | "completion";
+  taskId: string;
+  status: "running" | "completed";
+}
 
 interface DirectiveDAGProps {
   steps: DirectiveStep[];
+  specializedSteps?: SpecializedStep[];
   onComplete?: (stepId: string) => void;
   onFail?: (stepId: string) => void;
   onSkip?: (stepId: string) => void;
@@ -13,6 +35,13 @@ interface Layer {
   steps: DirectiveStep[];
 }
 
+/** Types that should appear before the regular DAG steps */
+const BEFORE_TYPES = new Set<OrchestratorStepType>([
+  "planning",
+  "replanning",
+  "plan-orders",
+]);
+
 function topoSort(steps: DirectiveStep[]): Layer[] {
   if (steps.length === 0) return [];
 
@@ -31,10 +60,13 @@ function topoSort(steps: DirectiveStep[]): Layer[] {
   }));
 }
 
-export function DirectiveDAG({ steps, onComplete, onFail, onSkip }: DirectiveDAGProps) {
+export function DirectiveDAG({ steps, specializedSteps, onComplete, onFail, onSkip }: DirectiveDAGProps) {
   const layers = useMemo(() => topoSort(steps), [steps]);
 
-  if (steps.length === 0) {
+  const orchestratorSteps = specializedSteps?.filter(s => s.type === "orchestrator") ?? [];
+  const completionSteps = specializedSteps?.filter(s => s.type === "completion") ?? [];
+
+  if (steps.length === 0 && orchestratorSteps.length === 0 && completionSteps.length === 0) {
     return (
       <div className="text-center py-8 text-[#7788aa] font-mono text-sm">
         No steps yet. Add steps to build the DAG.
@@ -44,6 +76,19 @@ export function DirectiveDAG({ steps, onComplete, onFail, onSkip }: DirectiveDAG
 
   return (
     <div className="flex flex-col gap-4 items-center py-4">
+      {/* Orchestrator steps (Planning/Cleanup/Orders) - rendered above regular steps */}
+      {orchestratorSteps.map(step => (
+        <SpecializedStepNode key={step.id} step={step} />
+      ))}
+
+      {/* Connector line if both orchestrator step and regular steps exist */}
+      {orchestratorSteps.length > 0 && layers.length > 0 && (
+        <div className="flex justify-center py-1">
+          <div className="w-px h-4 bg-[rgba(117,170,252,0.2)]" />
+        </div>
+      )}
+
+      {/* Regular step layers */}
       {layers.map((layer, layerIdx) => (
         <div key={layerIdx}>
           {layerIdx > 0 && (
@@ -52,18 +97,69 @@ export function DirectiveDAG({ steps, onComplete, onFail, onSkip }: DirectiveDAG
             </div>
           )}
           <div className="flex flex-wrap gap-3 justify-center">
-            {layer.steps.map((step) => (
-              <StepNode
-                key={step.id}
-                step={step}
-                onComplete={onComplete ? () => onComplete(step.id) : undefined}
-                onFail={onFail ? () => onFail(step.id) : undefined}
-                onSkip={onSkip ? () => onSkip(step.id) : undefined}
+            {afterSteps.map((vs) => (
+              <OrchestratorStepNode
+                key={`${vs.type}-${vs.taskId}`}
+                type={vs.type}
+                taskId={vs.taskId}
+                status={vs.status}
+                label={vs.label}
+                hasQuestions={vs.hasQuestions}
               />
             ))}
           </div>
         </div>
       ))}
+
+      {/* Connector line if both regular steps and completion step exist */}
+      {completionSteps.length > 0 && layers.length > 0 && (
+        <div className="flex justify-center py-1">
+          <div className="w-px h-4 bg-[rgba(117,170,252,0.2)]" />
+        </div>
+      )}
+
+      {/* Completion steps (PR creation) - rendered below regular steps */}
+      {completionSteps.map(step => (
+        <SpecializedStepNode key={step.id} step={step} />
+      ))}
+    </div>
+  );
+}
+
+function SpecializedStepNode({ step }: { step: SpecializedStep }) {
+  const themeColors = step.type === "orchestrator"
+    ? {
+        bg: "bg-[#1a1a30]",
+        border: "border-[rgba(117,170,252,0.3)]",
+        text: "text-[#75aafc]",
+        dot: "bg-[#75aafc]",
+        label: step.name.startsWith("Cleanup") ? "CLEANUP"
+             : step.name.startsWith("Pick up") ? "ORDERS"
+             : "PLANNING",
+      }
+    : {
+        bg: "bg-[#1a1a10]",
+        border: "border-yellow-900/50",
+        text: "text-yellow-400",
+        dot: "bg-yellow-400",
+        label: "PR",
+      };
+
+  return (
+    <div className={`flex items-center gap-2 px-3 py-2 ${themeColors.bg} border ${themeColors.border} rounded-lg mx-2`}>
+      <span className={`inline-block w-2 h-2 rounded-full ${themeColors.dot} animate-pulse`} />
+      <span className={`text-[9px] font-mono uppercase tracking-wide ${themeColors.text} opacity-60`}>
+        {themeColors.label}
+      </span>
+      <span className={`text-[11px] font-mono ${themeColors.text} flex-1 truncate`}>
+        {step.name}
+      </span>
+      <a
+        href={`/mesh/${step.taskId}`}
+        className="text-[9px] font-mono text-[#556677] hover:text-white underline"
+      >
+        View task
+      </a>
     </div>
   );
 }
-- 
cgit v1.2.3

