mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
feat: unify CLI output handling and enhance theme variables
- Updated `CliStreamMonitorNew`, `CliStreamMonitorLegacy`, and `CliViewerPage` components to prioritize `unitContent` from payloads, falling back to `data` when necessary. - Enhanced `colorGenerator` to include legacy variables for compatibility with shadcn/ui. - Refactored orchestrator index to unify node exports under a single module. - Improved `appStore` to clear both new and legacy CSS variables when applying themes. - Added new options to CLI execution for raw and final output modes, improving programmatic output handling. - Enhanced `cli-output-converter` to normalize cumulative delta frames and avoid duplication in streaming outputs. - Introduced a new unified workflow specification for prompt template-based workflows, replacing the previous multi-type node system. - Added tests for CLI final output handling and streaming output converter to ensure correct behavior in various scenarios.
This commit is contained in:
@@ -1,37 +1,56 @@
|
||||
---
|
||||
name: flow-coordinator
|
||||
description: Template-driven workflow coordinator with minimal state tracking. Executes command chains from workflow templates with slash-command execution (mainprocess/async). Triggers on "flow-coordinator", "workflow template", "orchestrate".
|
||||
description: Template-driven workflow coordinator with minimal state tracking. Executes command chains from workflow templates OR unified PromptTemplate workflows. Supports slash-command and DAG-based execution. Triggers on "flow-coordinator", "workflow template", "orchestrate".
|
||||
allowed-tools: Task, AskUserQuestion, Read, Write, Bash, Glob, Grep
|
||||
---
|
||||
|
||||
# Flow Coordinator
|
||||
|
||||
Lightweight workflow coordinator that executes command chains from predefined templates, supporting slash-command execution with mainprocess (blocking) and async (background) modes.
|
||||
Lightweight workflow coordinator supporting two workflow formats:
|
||||
1. **Legacy Templates**: Command chains with slash-command execution
|
||||
2. **Unified Workflows**: DAG-based PromptTemplate nodes (spec: `spec/unified-workflow-spec.md`)
|
||||
|
||||
## Specification Reference
|
||||
|
||||
- **Unified Workflow Spec**: @spec/unified-workflow-spec.md
|
||||
- **Demo Workflow**: `ccw/data/flows/demo-unified-workflow.json`
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
User Task → Select Template → status.json Init → Execute Steps → Complete
|
||||
↑ │
|
||||
└──────────────── Resume (from status.json) ─────┘
|
||||
User Task → Detect Format → Select Workflow → Init Status → Execute → Complete
|
||||
│ │
|
||||
├─ Legacy Template │
|
||||
│ └─ Sequential cmd execution │
|
||||
│ │
|
||||
└─ Unified Workflow │
|
||||
└─ DAG traversal with contextRefs │
|
||||
│
|
||||
└──────────────── Resume (from status.json) ──────────────┘
|
||||
|
||||
Step Execution:
|
||||
execution mode?
|
||||
├─ mainprocess → SlashCommand (blocking, main process)
|
||||
└─ async → ccw cli --tool claude --mode write (background)
|
||||
Execution Modes:
|
||||
├─ analysis → Read-only, CLI --mode analysis
|
||||
├─ write → File changes, CLI --mode write
|
||||
├─ mainprocess → Blocking, synchronous
|
||||
└─ async → Background, ccw cli
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
**Template-Driven**: Workflows defined as JSON templates in `templates/`, decoupled from coordinator logic.
|
||||
**Dual Format Support**:
|
||||
- Legacy: `templates/*.json` with `cmd`, `args`, `execution`
|
||||
- Unified: `ccw/data/flows/*.json` with `nodes`, `edges`, `contextRefs`
|
||||
|
||||
**Execution Type**: `slash-command` only
|
||||
- ALL workflow commands (`/workflow:*`) use `slash-command` type
|
||||
- Two execution modes:
|
||||
- `mainprocess`: SlashCommand (blocking, main process)
|
||||
- `async`: CLI background (ccw cli with claude tool)
|
||||
**Unified PromptTemplate Model**: All workflow steps are natural language instructions with:
|
||||
- `instruction`: What to execute (natural language)
|
||||
- `outputName`: Name for output reference
|
||||
- `contextRefs`: References to previous step outputs
|
||||
- `tool`: Optional CLI tool (gemini/qwen/codex/claude)
|
||||
- `mode`: Execution mode (analysis/write/mainprocess/async)
|
||||
|
||||
**Dynamic Discovery**: Templates discovered at runtime via Glob, not hardcoded.
|
||||
**DAG Execution**: Unified workflows execute as directed acyclic graphs with parallel branches and conditional edges.
|
||||
|
||||
**Dynamic Discovery**: Both formats discovered at runtime via Glob.
|
||||
|
||||
---
|
||||
|
||||
@@ -83,7 +102,139 @@ async function executeSteps(status, statusPath) {
|
||||
|
||||
---
|
||||
|
||||
## Template Discovery
|
||||
## Unified Workflow Execution
|
||||
|
||||
For workflows using the unified PromptTemplate format (`ccw/data/flows/*.json`):
|
||||
|
||||
```javascript
|
||||
async function executeUnifiedWorkflow(workflow, task) {
|
||||
// 1. Initialize execution state
|
||||
const sessionId = `ufc-${timestamp()}`;
|
||||
const statusPath = `.workflow/.flow-coordinator/${sessionId}/status.json`;
|
||||
const state = {
|
||||
id: sessionId,
|
||||
workflow: workflow.id,
|
||||
goal: task,
|
||||
nodeStates: {}, // nodeId -> { status, result, error }
|
||||
outputs: {}, // outputName -> result
|
||||
complete: false
|
||||
};
|
||||
|
||||
// 2. Topological sort for execution order
|
||||
const executionOrder = topologicalSort(workflow.nodes, workflow.edges);
|
||||
|
||||
// 3. Execute nodes respecting DAG dependencies
|
||||
await executeDAG(workflow, executionOrder, state, statusPath);
|
||||
}
|
||||
|
||||
async function executeDAG(workflow, order, state, statusPath) {
|
||||
for (const nodeId of order) {
|
||||
const node = workflow.nodes.find(n => n.id === nodeId);
|
||||
const data = node.data;
|
||||
|
||||
// Check if all dependencies are satisfied
|
||||
if (!areDependenciesSatisfied(nodeId, workflow.edges, state)) {
|
||||
continue; // Will be executed when dependencies complete
|
||||
}
|
||||
|
||||
// Resolve context references
|
||||
const resolvedInstruction = resolveContextRefs(
|
||||
data.instruction,
|
||||
data.contextRefs || [],
|
||||
state.outputs
|
||||
);
|
||||
|
||||
// Execute based on mode
|
||||
state.nodeStates[nodeId] = { status: 'running' };
|
||||
write(statusPath, JSON.stringify(state, null, 2));
|
||||
|
||||
const result = await executeNode(resolvedInstruction, data.tool, data.mode);
|
||||
|
||||
// Store output for downstream nodes
|
||||
state.nodeStates[nodeId] = { status: 'completed', result };
|
||||
if (data.outputName) {
|
||||
state.outputs[data.outputName] = result;
|
||||
}
|
||||
write(statusPath, JSON.stringify(state, null, 2));
|
||||
}
|
||||
|
||||
state.complete = true;
|
||||
write(statusPath, JSON.stringify(state, null, 2));
|
||||
}
|
||||
|
||||
function resolveContextRefs(instruction, refs, outputs) {
|
||||
let resolved = instruction;
|
||||
for (const ref of refs) {
|
||||
const value = outputs[ref];
|
||||
const placeholder = `{{${ref}}}`;
|
||||
resolved = resolved.replace(new RegExp(placeholder, 'g'),
|
||||
typeof value === 'object' ? JSON.stringify(value) : String(value));
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
async function executeNode(instruction, tool, mode) {
|
||||
// Build CLI command based on tool and mode
|
||||
const cliTool = tool || 'gemini';
|
||||
const cliMode = mode === 'write' ? 'write' : 'analysis';
|
||||
|
||||
if (mode === 'async') {
|
||||
// Background execution
|
||||
return Bash(
|
||||
`ccw cli -p "${escapePrompt(instruction)}" --tool ${cliTool} --mode ${cliMode}`,
|
||||
{ run_in_background: true }
|
||||
);
|
||||
} else {
|
||||
// Synchronous execution
|
||||
return Bash(
|
||||
`ccw cli -p "${escapePrompt(instruction)}" --tool ${cliTool} --mode ${cliMode}`
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Unified Workflow Discovery
|
||||
|
||||
```javascript
|
||||
async function discoverUnifiedWorkflows() {
|
||||
const files = Glob('*.json', { path: 'ccw/data/flows/' });
|
||||
|
||||
const workflows = [];
|
||||
for (const file of files) {
|
||||
const content = JSON.parse(Read(file));
|
||||
// Detect unified format by checking for 'nodes' array
|
||||
if (content.nodes && Array.isArray(content.nodes)) {
|
||||
workflows.push({
|
||||
id: content.id,
|
||||
name: content.name,
|
||||
description: content.description,
|
||||
nodeCount: content.nodes.length,
|
||||
format: 'unified',
|
||||
file: file
|
||||
});
|
||||
}
|
||||
}
|
||||
return workflows;
|
||||
}
|
||||
```
|
||||
|
||||
### Format Detection
|
||||
|
||||
```javascript
|
||||
function detectWorkflowFormat(content) {
|
||||
if (content.nodes && content.edges) {
|
||||
return 'unified'; // PromptTemplate DAG format
|
||||
}
|
||||
if (content.steps && content.steps[0]?.cmd) {
|
||||
return 'legacy'; // Command chain format
|
||||
}
|
||||
throw new Error('Unknown workflow format');
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Legacy Template Discovery
|
||||
|
||||
**Dynamic query** - never hardcode template list:
|
||||
|
||||
@@ -391,4 +542,14 @@ Templates discovered from `templates/*.json`:
|
||||
|
||||
| Document | Purpose |
|
||||
|----------|---------|
|
||||
| templates/*.json | Workflow templates (dynamic discovery) |
|
||||
| spec/unified-workflow-spec.md | Unified PromptTemplate workflow specification |
|
||||
| ccw/data/flows/*.json | Unified workflows (DAG format, dynamic discovery) |
|
||||
| templates/*.json | Legacy workflow templates (command chain format) |
|
||||
|
||||
### Demo Workflows (Unified Format)
|
||||
|
||||
| File | Description | Nodes |
|
||||
|------|-------------|-------|
|
||||
| `demo-unified-workflow.json` | Auth implementation | 7 nodes: Analyze → Plan → Implement → Review → Tests → Report |
|
||||
| `parallel-ci-workflow.json` | CI/CD pipeline | 8 nodes: Parallel checks → Merge → Conditional notify |
|
||||
| `simple-analysis-workflow.json` | Analysis pipeline | 3 nodes: Explore → Analyze → Report |
|
||||
|
||||
324
.claude/skills/flow-coordinator/spec/unified-workflow-spec.md
Normal file
324
.claude/skills/flow-coordinator/spec/unified-workflow-spec.md
Normal file
@@ -0,0 +1,324 @@
|
||||
# Unified Workflow Specification v1.0
|
||||
|
||||
> Standard format for PromptTemplate-based workflow definitions
|
||||
|
||||
## Overview
|
||||
|
||||
This specification defines the JSON schema for unified workflows where **all nodes are prompt templates** with natural language instructions. This replaces the previous multi-type node system with a single, flexible model.
|
||||
|
||||
**Design Philosophy**: Every workflow step is a natural language instruction that can optionally specify execution tool and mode. Data flows through named outputs referenced by subsequent steps.
|
||||
|
||||
---
|
||||
|
||||
## Schema Definition
|
||||
|
||||
### Root Object: `Flow`
|
||||
|
||||
```typescript
|
||||
interface Flow {
|
||||
id: string; // Unique identifier (kebab-case)
|
||||
name: string; // Display name
|
||||
description?: string; // Human-readable description
|
||||
version: number; // Schema version (currently 1)
|
||||
created_at: string; // ISO 8601 timestamp
|
||||
updated_at: string; // ISO 8601 timestamp
|
||||
nodes: FlowNode[]; // Workflow steps
|
||||
edges: FlowEdge[]; // Step connections (DAG)
|
||||
variables: Record<string, unknown>; // Global workflow variables
|
||||
metadata: FlowMetadata; // Classification and source info
|
||||
}
|
||||
```
|
||||
|
||||
### FlowNode
|
||||
|
||||
```typescript
|
||||
interface FlowNode {
|
||||
id: string; // Unique node ID
|
||||
type: 'prompt-template'; // Always 'prompt-template'
|
||||
position: { x: number; y: number }; // Canvas position
|
||||
data: PromptTemplateNodeData; // Node configuration
|
||||
}
|
||||
```
|
||||
|
||||
### PromptTemplateNodeData
|
||||
|
||||
```typescript
|
||||
interface PromptTemplateNodeData {
|
||||
// === Required ===
|
||||
label: string; // Display label in editor
|
||||
instruction: string; // Natural language instruction
|
||||
|
||||
// === Data Flow ===
|
||||
outputName?: string; // Name for output reference
|
||||
contextRefs?: string[]; // References to previous outputs
|
||||
|
||||
// === Execution Config ===
|
||||
tool?: CliTool; // 'gemini' | 'qwen' | 'codex' | 'claude'
|
||||
mode?: ExecutionMode; // 'analysis' | 'write' | 'mainprocess' | 'async'
|
||||
|
||||
// === Runtime State (populated during execution) ===
|
||||
executionStatus?: ExecutionStatus;
|
||||
executionError?: string;
|
||||
executionResult?: unknown;
|
||||
}
|
||||
```
|
||||
|
||||
### FlowEdge
|
||||
|
||||
```typescript
|
||||
interface FlowEdge {
|
||||
id: string; // Unique edge ID
|
||||
source: string; // Source node ID
|
||||
target: string; // Target node ID
|
||||
type?: string; // Edge type (default: 'default')
|
||||
data?: {
|
||||
label?: string; // Edge label (e.g., 'parallel')
|
||||
condition?: string; // Conditional expression
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### FlowMetadata
|
||||
|
||||
```typescript
|
||||
interface FlowMetadata {
|
||||
source?: 'template' | 'custom' | 'imported';
|
||||
tags?: string[];
|
||||
category?: string;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Instruction Syntax
|
||||
|
||||
### Context References
|
||||
|
||||
Use `{{outputName}}` syntax to reference outputs from previous steps:
|
||||
|
||||
```
|
||||
Analyze {{requirements_analysis}} and create implementation plan.
|
||||
```
|
||||
|
||||
### Nested Property Access
|
||||
|
||||
```
|
||||
If {{ci_report.status}} === 'failed', stop execution.
|
||||
```
|
||||
|
||||
### Multiple References
|
||||
|
||||
```
|
||||
Combine {{lint_result}}, {{typecheck_result}}, and {{test_result}} into report.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Execution Modes
|
||||
|
||||
| Mode | Behavior | Use Case |
|
||||
|------|----------|----------|
|
||||
| `analysis` | Read-only, no file changes | Code review, exploration |
|
||||
| `write` | Can create/modify/delete files | Implementation, fixes |
|
||||
| `mainprocess` | Blocking, synchronous | Interactive steps |
|
||||
| `async` | Background, non-blocking | Long-running tasks |
|
||||
|
||||
---
|
||||
|
||||
## DAG Execution Semantics
|
||||
|
||||
### Sequential Execution
|
||||
|
||||
Nodes with single input edge execute after predecessor completes.
|
||||
|
||||
```
|
||||
[A] ──▶ [B] ──▶ [C]
|
||||
```
|
||||
|
||||
### Parallel Execution
|
||||
|
||||
Multiple edges from same source trigger parallel execution:
|
||||
|
||||
```
|
||||
┌──▶ [B]
|
||||
[A] ──┤
|
||||
└──▶ [C]
|
||||
```
|
||||
|
||||
### Merge Point
|
||||
|
||||
Node with multiple input edges waits for all predecessors:
|
||||
|
||||
```
|
||||
[B] ──┐
|
||||
├──▶ [D]
|
||||
[C] ──┘
|
||||
```
|
||||
|
||||
### Conditional Branching
|
||||
|
||||
Edge `data.condition` specifies branch condition:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "e-decision-success",
|
||||
"source": "decision",
|
||||
"target": "notify-success",
|
||||
"data": { "condition": "decision.result === 'pass'" }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example: Minimal Workflow
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "simple-analysis",
|
||||
"name": "Simple Analysis",
|
||||
"version": 1,
|
||||
"created_at": "2026-02-04T00:00:00.000Z",
|
||||
"updated_at": "2026-02-04T00:00:00.000Z",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "analyze",
|
||||
"type": "prompt-template",
|
||||
"position": { "x": 100, "y": 100 },
|
||||
"data": {
|
||||
"label": "Analyze Code",
|
||||
"instruction": "Analyze the authentication module for security issues.",
|
||||
"outputName": "analysis",
|
||||
"tool": "gemini",
|
||||
"mode": "analysis"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "report",
|
||||
"type": "prompt-template",
|
||||
"position": { "x": 100, "y": 250 },
|
||||
"data": {
|
||||
"label": "Generate Report",
|
||||
"instruction": "Based on {{analysis}}, generate a security report with recommendations.",
|
||||
"outputName": "report",
|
||||
"contextRefs": ["analysis"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{ "id": "e1", "source": "analyze", "target": "report" }
|
||||
],
|
||||
"variables": {},
|
||||
"metadata": { "source": "custom", "tags": ["security"] }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example: Parallel with Merge
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "start",
|
||||
"type": "prompt-template",
|
||||
"position": { "x": 200, "y": 50 },
|
||||
"data": {
|
||||
"label": "Prepare",
|
||||
"instruction": "Set up build environment",
|
||||
"outputName": "env"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "lint",
|
||||
"type": "prompt-template",
|
||||
"position": { "x": 100, "y": 200 },
|
||||
"data": {
|
||||
"label": "Lint",
|
||||
"instruction": "Run linter checks",
|
||||
"outputName": "lint_result",
|
||||
"tool": "codex",
|
||||
"mode": "analysis",
|
||||
"contextRefs": ["env"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "test",
|
||||
"type": "prompt-template",
|
||||
"position": { "x": 300, "y": 200 },
|
||||
"data": {
|
||||
"label": "Test",
|
||||
"instruction": "Run unit tests",
|
||||
"outputName": "test_result",
|
||||
"tool": "codex",
|
||||
"mode": "analysis",
|
||||
"contextRefs": ["env"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "merge",
|
||||
"type": "prompt-template",
|
||||
"position": { "x": 200, "y": 350 },
|
||||
"data": {
|
||||
"label": "Merge Results",
|
||||
"instruction": "Combine {{lint_result}} and {{test_result}} into CI report",
|
||||
"outputName": "ci_report",
|
||||
"contextRefs": ["lint_result", "test_result"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{ "id": "e1", "source": "start", "target": "lint", "data": { "label": "parallel" } },
|
||||
{ "id": "e2", "source": "start", "target": "test", "data": { "label": "parallel" } },
|
||||
{ "id": "e3", "source": "lint", "target": "merge" },
|
||||
{ "id": "e4", "source": "test", "target": "merge" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration from Old Format
|
||||
|
||||
### Old Template Step
|
||||
|
||||
```json
|
||||
{
|
||||
"cmd": "/workflow:lite-plan",
|
||||
"args": "\"{{goal}}\"",
|
||||
"execution": { "type": "slash-command", "mode": "mainprocess" }
|
||||
}
|
||||
```
|
||||
|
||||
### New PromptTemplate Node
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "plan",
|
||||
"type": "prompt-template",
|
||||
"data": {
|
||||
"label": "Create Plan",
|
||||
"instruction": "Execute /workflow:lite-plan for: {{goal}}",
|
||||
"outputName": "plan_result",
|
||||
"mode": "mainprocess"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Validation Rules
|
||||
|
||||
1. **Unique IDs**: All node and edge IDs must be unique within the flow
|
||||
2. **Valid References**: `contextRefs` must reference existing `outputName` values
|
||||
3. **DAG Structure**: No circular dependencies allowed
|
||||
4. **Required Fields**: `id`, `name`, `version`, `nodes`, `edges` are required
|
||||
5. **Node Type**: All nodes must have `type: 'prompt-template'`
|
||||
|
||||
---
|
||||
|
||||
## File Location
|
||||
|
||||
Workflow files stored in: `ccw/data/flows/*.json`
|
||||
|
||||
Template discovery: `Glob('*.json', { path: 'ccw/data/flows/' })`
|
||||
Reference in New Issue
Block a user