@leclabs/agent-flow-navigator-mcp 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -110,6 +110,12 @@
110
110
  {
111
111
  "from": "commit",
112
112
  "to": "end_success"
113
+ },
114
+ {
115
+ "from": "hitl_blocked",
116
+ "to": "execute",
117
+ "on": "passed",
118
+ "label": "Human resolved issue, resume"
113
119
  }
114
120
  ]
115
121
  }
@@ -0,0 +1,248 @@
1
+ {
2
+ "id": "refactor",
3
+ "name": "Refactor",
4
+ "description": "Transform outdated codebases into modern equivalents using Functional Core / Imperative Shell architecture. Separates pure business logic from side effects.",
5
+ "nodes": {
6
+ "start": {
7
+ "type": "start",
8
+ "name": "Start",
9
+ "description": "Refactoring workflow begins"
10
+ },
11
+ "analyze_structure": {
12
+ "type": "task",
13
+ "name": "Analyze Structure",
14
+ "description": "Map current architecture: modules, dependencies, entry points. Identify coupling and cohesion issues.",
15
+ "agent": "Planner",
16
+ "stage": "analysis"
17
+ },
18
+ "identify_debt": {
19
+ "type": "task",
20
+ "name": "Identify Technical Debt",
21
+ "description": "Find code smells, anti-patterns, outdated practices. Document violations of SOLID, DRY, and separation of concerns.",
22
+ "agent": "Planner",
23
+ "stage": "analysis"
24
+ },
25
+ "classify_components": {
26
+ "type": "task",
27
+ "name": "Classify Components",
28
+ "description": "Categorize code into Functional Core (pure logic, no side effects) vs Imperative Shell (I/O, state, external calls).",
29
+ "agent": "Planner",
30
+ "stage": "analysis"
31
+ },
32
+ "design_refactor": {
33
+ "type": "task",
34
+ "name": "Design Refactor Plan",
35
+ "description": "Create transformation plan: define functional core boundaries, shell interfaces, and migration sequence.",
36
+ "agent": "Planner",
37
+ "stage": "planning"
38
+ },
39
+ "plan_review": {
40
+ "type": "gate",
41
+ "name": "Review Plan",
42
+ "description": "Verify refactor plan maintains behavioral equivalence while achieving architectural goals.",
43
+ "agent": "Reviewer",
44
+ "stage": "planning",
45
+ "maxRetries": 2,
46
+ "config": {
47
+ "scrutinyLevel": 3
48
+ }
49
+ },
50
+ "extract_core": {
51
+ "type": "task",
52
+ "name": "Extract Functional Core",
53
+ "description": "Refactor pure business logic into functional core: no side effects, deterministic, testable in isolation.",
54
+ "agent": "Developer",
55
+ "stage": "development"
56
+ },
57
+ "isolate_shell": {
58
+ "type": "task",
59
+ "name": "Isolate Imperative Shell",
60
+ "description": "Wrap side effects (I/O, state, external services) in thin imperative shell that coordinates functional core.",
61
+ "agent": "Developer",
62
+ "stage": "development"
63
+ },
64
+ "write_tests": {
65
+ "type": "task",
66
+ "name": "Write Tests",
67
+ "description": "Add tests verifying behavioral equivalence. Unit tests for functional core, integration tests for shell.",
68
+ "agent": "Tester",
69
+ "stage": "development"
70
+ },
71
+ "run_tests": {
72
+ "type": "gate",
73
+ "name": "Run Tests",
74
+ "description": "Execute test suite. Verify refactored code produces identical behavior to original.",
75
+ "agent": "Tester",
76
+ "stage": "verification",
77
+ "maxRetries": 3
78
+ },
79
+ "code_review": {
80
+ "type": "gate",
81
+ "name": "Code Review",
82
+ "description": "Review architecture: clean functional/shell separation, no hidden side effects in core, shell is minimal.",
83
+ "agent": "Reviewer",
84
+ "stage": "verification",
85
+ "maxRetries": 2,
86
+ "config": {
87
+ "scrutinyLevel": 3
88
+ }
89
+ },
90
+ "lint_format": {
91
+ "type": "gate",
92
+ "name": "Lint & Format",
93
+ "description": "Run lint and format checks. Auto-fix issues where possible.",
94
+ "agent": "Developer",
95
+ "stage": "delivery",
96
+ "maxRetries": 3
97
+ },
98
+ "commit": {
99
+ "type": "task",
100
+ "name": "Commit Changes",
101
+ "description": "Commit all changes with a descriptive message summarizing the refactoring",
102
+ "agent": "Developer",
103
+ "stage": "delivery"
104
+ },
105
+ "end_success": {
106
+ "type": "end",
107
+ "result": "success",
108
+ "name": "Complete",
109
+ "description": "Refactoring completed successfully"
110
+ },
111
+ "hitl_analysis_failed": {
112
+ "type": "end",
113
+ "result": "blocked",
114
+ "escalation": "hitl",
115
+ "name": "Analysis Blocked",
116
+ "description": "Analysis or planning needs human guidance"
117
+ },
118
+ "hitl_dev_failed": {
119
+ "type": "end",
120
+ "result": "blocked",
121
+ "escalation": "hitl",
122
+ "name": "Development Blocked",
123
+ "description": "Development or verification needs human intervention"
124
+ }
125
+ },
126
+ "edges": [
127
+ {
128
+ "from": "start",
129
+ "to": "analyze_structure"
130
+ },
131
+ {
132
+ "from": "analyze_structure",
133
+ "to": "identify_debt"
134
+ },
135
+ {
136
+ "from": "identify_debt",
137
+ "to": "classify_components"
138
+ },
139
+ {
140
+ "from": "classify_components",
141
+ "to": "design_refactor"
142
+ },
143
+ {
144
+ "from": "design_refactor",
145
+ "to": "plan_review"
146
+ },
147
+ {
148
+ "from": "plan_review",
149
+ "to": "design_refactor",
150
+ "on": "failed",
151
+ "label": "Revise plan based on feedback"
152
+ },
153
+ {
154
+ "from": "plan_review",
155
+ "to": "hitl_analysis_failed",
156
+ "on": "failed",
157
+ "label": "Planning exhausted retries"
158
+ },
159
+ {
160
+ "from": "plan_review",
161
+ "to": "extract_core",
162
+ "on": "passed",
163
+ "label": "Plan approved, begin refactoring"
164
+ },
165
+ {
166
+ "from": "extract_core",
167
+ "to": "isolate_shell"
168
+ },
169
+ {
170
+ "from": "isolate_shell",
171
+ "to": "write_tests"
172
+ },
173
+ {
174
+ "from": "write_tests",
175
+ "to": "run_tests"
176
+ },
177
+ {
178
+ "from": "run_tests",
179
+ "to": "extract_core",
180
+ "on": "failed",
181
+ "label": "Fix failing tests"
182
+ },
183
+ {
184
+ "from": "run_tests",
185
+ "to": "hitl_dev_failed",
186
+ "on": "failed",
187
+ "label": "Tests keep failing"
188
+ },
189
+ {
190
+ "from": "run_tests",
191
+ "to": "code_review",
192
+ "on": "passed",
193
+ "label": "Tests pass, ready for review"
194
+ },
195
+ {
196
+ "from": "code_review",
197
+ "to": "extract_core",
198
+ "on": "failed",
199
+ "label": "Address review feedback"
200
+ },
201
+ {
202
+ "from": "code_review",
203
+ "to": "hitl_dev_failed",
204
+ "on": "failed",
205
+ "label": "Review issues persist"
206
+ },
207
+ {
208
+ "from": "code_review",
209
+ "to": "lint_format",
210
+ "on": "passed",
211
+ "label": "Code approved, run lint checks"
212
+ },
213
+ {
214
+ "from": "lint_format",
215
+ "to": "commit",
216
+ "on": "passed",
217
+ "label": "Lint passes, commit changes"
218
+ },
219
+ {
220
+ "from": "lint_format",
221
+ "to": "extract_core",
222
+ "on": "failed",
223
+ "label": "Fix lint/format issues"
224
+ },
225
+ {
226
+ "from": "lint_format",
227
+ "to": "hitl_dev_failed",
228
+ "on": "failed",
229
+ "label": "Lint issues persist"
230
+ },
231
+ {
232
+ "from": "commit",
233
+ "to": "end_success"
234
+ },
235
+ {
236
+ "from": "hitl_analysis_failed",
237
+ "to": "design_refactor",
238
+ "on": "passed",
239
+ "label": "Human resolved analysis issue, resume"
240
+ },
241
+ {
242
+ "from": "hitl_dev_failed",
243
+ "to": "extract_core",
244
+ "on": "passed",
245
+ "label": "Human resolved development issue, resume"
246
+ }
247
+ ]
248
+ }
@@ -148,6 +148,12 @@
148
148
  {
149
149
  "from": "commit",
150
150
  "to": "end_success"
151
+ },
152
+ {
153
+ "from": "hitl_failed",
154
+ "to": "write_tests",
155
+ "on": "passed",
156
+ "label": "Human resolved issue, resume"
151
157
  }
152
158
  ]
153
159
  }
@@ -236,6 +236,24 @@
236
236
  {
237
237
  "from": "commit",
238
238
  "to": "end_success"
239
+ },
240
+ {
241
+ "from": "hitl_ir_failed",
242
+ "to": "ir_component_tree",
243
+ "on": "passed",
244
+ "label": "Human resolved IR issue, resume"
245
+ },
246
+ {
247
+ "from": "hitl_build_failed",
248
+ "to": "uiRebuild_build",
249
+ "on": "passed",
250
+ "label": "Human resolved build issue, resume"
251
+ },
252
+ {
253
+ "from": "hitl_final_failed",
254
+ "to": "uiRebuild_build",
255
+ "on": "passed",
256
+ "label": "Human resolved final review issue, resume"
239
257
  }
240
258
  ]
241
259
  }
package/copier.js CHANGED
@@ -12,78 +12,41 @@
12
12
  export function generateFlowReadme() {
13
13
  return `# Flow Plugin
14
14
 
15
- DAG-based workflow orchestration for Claude Code.
16
-
17
- ## Overview
18
-
19
- Flow provides structured workflows that guide tasks through defined stages (planning → development → verification → delivery). Each step can be delegated to specialized subagents.
15
+ DAG-based workflow orchestration for AI agents.
20
16
 
21
17
  ## Quick Start
22
18
 
23
- Workflows work immediately from the built-in catalog - no setup required:
24
-
25
19
  \`\`\`bash
26
- # Create a task with workflow tracking
27
- /flow:task-create "Add user authentication" [workflow] feature-development
20
+ # Load the orchestrator at session start
21
+ /flow:prime
28
22
 
29
- # Or use prefix shortcuts
30
- feat: Add user authentication # → feature-development workflow
31
- bug: Fix login error # → bug-fix workflow
32
- task: Update config file # → quick-task workflow
23
+ # Create a task using a command
24
+ /flow:feat "add user authentication"
33
25
 
34
- # Run the task autonomously
35
- /flow:run
26
+ # Execute all pending tasks
27
+ /flow:go
36
28
  \`\`\`
37
29
 
38
30
  ## Commands
39
31
 
40
- | Command | Description |
41
- |---------|-------------|
42
- | \`/flow:prime\` | Load Orchestrator context (invoke at session start) |
43
- | \`/flow:task-create\` | Create a new task with workflow tracking |
44
- | \`/flow:task-list\` | List all flow tasks with current status |
45
- | \`/flow:task-get\` | Get detailed task info including workflow diagram |
46
- | \`/flow:task-advance\` | Advance task: \`<taskId> <passed|failed> [summary]\` |
47
- | \`/flow:run\` | Execute flow tasks autonomously |
48
- | \`/flow:list\` | List available workflows |
49
- | \`/flow:diagram\` | Generate mermaid diagram for a workflow |
50
- | \`/flow:init\` | Copy workflows to .flow/workflows/ for customization |
51
- | \`/flow:load\` | Reload workflows after editing .flow/workflows/ |
52
-
53
- ## Available Workflows
54
-
55
- - **quick-task** - Minimal: understand → execute → verify (best for simple tasks)
56
- - **agile-task** - Simple: analyze → implement → test → review
57
- - **feature-development** - Full lifecycle: requirements → planning → implementation → testing → PR
58
- - **bug-fix** - Bug workflow: reproduce → investigate → fix → verify → PR
59
- - **test-coverage** - Analyze coverage gaps and write tests
60
- - **context-optimization** - Optimize agent context and instructions
61
- - **ui-reconstruction** - Reconstruct UI components from screenshots or designs
32
+ | Command | Workflow | Description |
33
+ | --- | --- | --- |
34
+ | \`/flow:feat\` | feature-development | New feature with planning + review |
35
+ | \`/flow:bug\` | bug-fix | Bug investigation and fix |
36
+ | \`/flow:task\` | agile-task | General development task |
37
+ | \`/flow:fix\` | quick-task | Quick fix, minimal ceremony |
38
+ | \`/flow:spec\` | test-coverage | Analyze and improve test coverage |
39
+ | \`/flow:ctx\` | context-optimization | Optimize agent context and prompts |
40
+ | \`/flow:ui\` | ui-reconstruction | Reconstruct UI from reference |
41
+ | \`/flow:go\` | _(runs queue)_ | Execute all pending tasks |
62
42
 
63
- ## Customization (Optional)
43
+ Use \`/flow:task-create "description" <workflow-id>\` for workflows without command shortcuts.
64
44
 
65
- Flow's workflows work directly from the catalog in the flow->navigator mcp. If you want to create custom workflows you can run \`/flow:init\` to select a workflow from the catalog to customize for your project, your agents, and your tools.
66
-
67
- \`\`\`bash
68
- # Copy catalog workflows to .flow/workflows/ for editing
69
- /flow:init
70
-
71
- # Edit .flow/workflows/{workflow}/workflow.json
72
- # Then reload
73
- /flow:load
74
- \`\`\`
75
-
76
- **Customization options:**
77
- - Modify step definitions in workflow.json
78
- - Add custom \`instructions\` to steps for project-specific guidance
79
- - Create new workflows by adding new directories
45
+ ## Available Workflows
80
46
 
81
- ## How It Works
47
+ Workflows are defined in \`.flow/workflows/\`. Edit \`workflow.json\` to customize, then run \`/flow:load\` to reload.
82
48
 
83
- 1. **Navigate API** - Stateless MCP server computes next step based on workflow DAG
84
- 2. **Task Metadata** - Workflow state stored in Claude Code task metadata
85
- 3. **Subagent Delegation** - Steps delegated to specialized agents (planner, developer, tester, reviewer)
86
- 4. **Retry Logic** - Failed steps retry with configurable limits, escalate to HITL if exceeded
49
+ See [Flow Plugin docs](https://github.com/leclabs/agent-toolkit/tree/main/plugins/flow) for the full workflow catalog.
87
50
  `;
88
51
  }
89
52
 
package/engine.js CHANGED
@@ -13,7 +13,8 @@
13
13
  * - Edge to end node = escalation (taken if retries exhausted)
14
14
  */
15
15
 
16
- import { existsSync, readFileSync } from "fs";
16
+ import { existsSync, readFileSync, writeFileSync } from "fs";
17
+ import { join } from "path";
17
18
 
18
19
  /**
19
20
  * Read and parse a task file
@@ -52,13 +53,56 @@ export function getTerminalType(node) {
52
53
  }
53
54
 
54
55
  /**
55
- * Convert agent ID to subagent reference
56
- * e.g., "developer" -> "@flow:developer"
56
+ * Return agent ID as-is from workflow definition.
57
+ * Prefixing (e.g., @flow:) is the caller's responsibility.
57
58
  */
58
59
  export function toSubagentRef(agentId) {
59
60
  if (!agentId) return null;
60
- if (agentId.startsWith("@")) return agentId;
61
- return `@flow:${agentId}`;
61
+ return agentId;
62
+ }
63
+
64
+ /**
65
+ * Workflow emoji mapping for task subjects
66
+ */
67
+ const WORKFLOW_EMOJIS = {
68
+ "feature-development": "✨",
69
+ "bug-fix": "🐛",
70
+ "agile-task": "📋",
71
+ "context-optimization": "🔧",
72
+ "quick-task": "⚡",
73
+ "ui-reconstruction": "🎨",
74
+ "test-coverage": "🧪",
75
+ };
76
+
77
+ /**
78
+ * Build formatted task subject for write-through
79
+ */
80
+ export function buildTaskSubject(taskId, userDescription, workflowType, stepId, subagent, terminal, maxRetries, retryCount) {
81
+ const emoji = WORKFLOW_EMOJIS[workflowType] || "";
82
+ const line1 = `#${taskId} ${userDescription}${emoji ? ` ${emoji}` : ""}`;
83
+
84
+ let line2;
85
+ if (terminal === "success") {
86
+ line2 = `→ ${workflowType} · completed ✓`;
87
+ } else if (terminal === "hitl" || terminal === "failure") {
88
+ line2 = `→ ${workflowType} · ${stepId} · HITL`;
89
+ } else {
90
+ const agent = subagent ? `(${subagent})` : "(direct)";
91
+ const retries = maxRetries > 0 ? ` · retries: ${retryCount}/${maxRetries}` : "";
92
+ line2 = `→ ${workflowType} · ${stepId} ${agent}${retries}`;
93
+ }
94
+
95
+ return `${line1}\n${line2}`;
96
+ }
97
+
98
+ /**
99
+ * Build activeForm for task spinner display
100
+ */
101
+ export function buildTaskActiveForm(stepName, subagent, terminal) {
102
+ if (terminal === "success") return "Completed";
103
+ if (terminal === "hitl" || terminal === "failure") return "HITL - Needs human help";
104
+ const agent = subagent ? ` (${subagent})` : "";
105
+ return `${stepName}${agent}`;
62
106
  }
63
107
 
64
108
  /**
@@ -68,8 +112,13 @@ export function getBaselineInstructions(stepId, stepName) {
68
112
  const id = stepId.toLowerCase();
69
113
  const name = (stepName || "").toLowerCase();
70
114
 
71
- // Analysis/Planning steps
72
- if (id.includes("analyze") || id.includes("analysis") || name.includes("analyze")) {
115
+ // Review steps (checked early — "plan_review" is a review, not a plan)
116
+ if (id.includes("review")) {
117
+ return "Check for correctness, code quality, and adherence to project standards. Verify the implementation meets requirements.";
118
+ }
119
+
120
+ // Analysis/Requirements steps
121
+ if (id.includes("analyze") || id.includes("analysis") || id.includes("parse") || id.includes("requirements") || name.includes("analyze")) {
73
122
  return "Review the task requirements carefully. Identify key constraints, dependencies, and acceptance criteria. Create a clear plan before proceeding.";
74
123
  }
75
124
  if (id.includes("plan") || id.includes("design") || name.includes("plan")) {
@@ -87,16 +136,16 @@ export function getBaselineInstructions(stepId, stepName) {
87
136
  return "Improve code structure without changing behavior. Ensure all tests pass before and after changes.";
88
137
  }
89
138
 
139
+ // Lint/format steps
140
+ if (id.includes("lint") || id.includes("format")) {
141
+ return "Run linting and formatting checks. Auto-fix issues where possible. Flag any issues that require manual attention.";
142
+ }
143
+
90
144
  // Testing steps
91
145
  if (id.includes("test") || id.includes("verify") || id.includes("validate")) {
92
146
  return "Verify the implementation works correctly. Test happy paths, edge cases, and error conditions. Document any issues found.";
93
147
  }
94
148
 
95
- // Review steps
96
- if (id.includes("review")) {
97
- return "Check for correctness, code quality, and adherence to project standards. Verify the implementation meets requirements.";
98
- }
99
-
100
149
  // Documentation steps
101
150
  if (id.includes("document") || id.includes("readme")) {
102
151
  return "Write clear, concise documentation. Focus on what users need to know, not implementation details.";
@@ -124,18 +173,30 @@ export function getBaselineInstructions(stepId, stepName) {
124
173
  return "Complete this step thoroughly. Document your findings and any decisions made.";
125
174
  }
126
175
 
176
+ /**
177
+ * Build context loading instructions from step-level context_files.
178
+ * Returns a markdown section or null if no context declared.
179
+ */
180
+ export function buildContextInstructions({ contextFiles, projectRoot }) {
181
+ if (!contextFiles?.length || !projectRoot) return null;
182
+ const lines = contextFiles.map((file) => `- Read file: ${join(projectRoot, file)}`);
183
+ return `## Context\n\nBefore beginning, load the following:\n${lines.join("\n")}`;
184
+ }
185
+
127
186
  /**
128
187
  * Build orchestrator instructions for task creation/update
129
188
  * Returns null for terminal nodes (no further work)
130
189
  */
131
- function buildOrchestratorInstructions(workflowType, stepId, stage, subagent, stepInstructions, description) {
190
+ function buildOrchestratorInstructions(workflowType, stepId, stage, subagent, stepInstructions, description, contextBlock) {
132
191
  if (!stepInstructions) return null; // Terminal nodes have no instructions
133
192
 
134
193
  const delegationPrefix = subagent ? `Invoke ${subagent} to complete the following task: ` : "";
135
194
 
136
- return `${delegationPrefix}${stepInstructions.guidance}
195
+ let result = `${delegationPrefix}${stepInstructions.guidance}
137
196
 
138
197
  ${description || "{task description}"}`;
198
+ if (contextBlock) result += `\n\n${contextBlock}`;
199
+ return result;
139
200
  }
140
201
 
141
202
  /**
@@ -149,7 +210,9 @@ function buildNavigateResponse(
149
210
  action,
150
211
  retriesIncremented = false,
151
212
  retryCount = 0,
152
- description = null
213
+ description = null,
214
+ resetRetryCount = false,
215
+ projectRoot = null
153
216
  ) {
154
217
  const stage = stepDef.stage || null;
155
218
  const subagent = stepDef.agent ? toSubagentRef(stepDef.agent) : null;
@@ -164,16 +227,27 @@ function buildNavigateResponse(
164
227
  guidance: stepDef.instructions || getBaselineInstructions(stepId, stepDef.name),
165
228
  };
166
229
 
230
+ // Build context block from step-level context_files
231
+ const contextBlock = isTerminal
232
+ ? null
233
+ : buildContextInstructions({ contextFiles: stepDef.context_files, projectRoot });
234
+
167
235
  // Build orchestrator instructions for all non-terminal actions
168
236
  const orchestratorInstructions = isTerminal
169
237
  ? null
170
- : buildOrchestratorInstructions(workflowType, stepId, stage, subagent, stepInstructions, description);
238
+ : buildOrchestratorInstructions(workflowType, stepId, stage, subagent, stepInstructions, description, contextBlock);
171
239
 
172
240
  // Build metadata for task storage
241
+ // Increment on retry, reset on start or explicit forward progress (conditional advance),
242
+ // preserve on unconditional advances within retry loops and escalations
173
243
  const metadata = {
174
244
  workflowType,
175
245
  currentStep: stepId,
176
- retryCount: retriesIncremented ? retryCount + 1 : retryCount,
246
+ retryCount: retriesIncremented
247
+ ? retryCount + 1
248
+ : action === "start" || resetRetryCount
249
+ ? 0
250
+ : retryCount,
177
251
  };
178
252
 
179
253
  return {
@@ -184,6 +258,7 @@ function buildNavigateResponse(
184
258
  terminal: getTerminalType(stepDef),
185
259
  action,
186
260
  retriesIncremented,
261
+ maxRetries: stepDef.maxRetries || 0,
187
262
  orchestratorInstructions,
188
263
  metadata,
189
264
  };
@@ -354,7 +429,7 @@ export class WorkflowEngine {
354
429
  * @param {string} [options.description] - User's task description
355
430
  * @returns {Object} Navigation response with currentStep, stepInstructions, terminal, action, metadata, etc.
356
431
  */
357
- navigate({ taskFilePath, workflowType, result, description } = {}) {
432
+ navigate({ taskFilePath, workflowType, result, description, projectRoot } = {}) {
358
433
  let currentStep = null;
359
434
  let retryCount = 0;
360
435
 
@@ -414,7 +489,7 @@ export class WorkflowEngine {
414
489
  throw new Error(`First step '${firstEdge.to}' not found in workflow`);
415
490
  }
416
491
 
417
- return buildNavigateResponse(workflowType, firstEdge.to, firstStepDef, "start", false, 0, description);
492
+ return buildNavigateResponse(workflowType, firstEdge.to, firstStepDef, "start", false, 0, description, false, projectRoot);
418
493
  }
419
494
 
420
495
  // Case 2: currentStep but no result - return current state
@@ -424,7 +499,7 @@ export class WorkflowEngine {
424
499
  throw new Error(`Step '${currentStep}' not found in workflow '${workflowType}'`);
425
500
  }
426
501
 
427
- return buildNavigateResponse(workflowType, currentStep, stepDef, "current", false, retryCount, description);
502
+ return buildNavigateResponse(workflowType, currentStep, stepDef, "current", false, retryCount, description, false, projectRoot);
428
503
  }
429
504
 
430
505
  // Case 3: currentStep and result - advance to next step
@@ -444,9 +519,13 @@ export class WorkflowEngine {
444
519
  }
445
520
 
446
521
  // Determine action and whether retries incremented
522
+ const currentStepDef = nodes[currentStep];
523
+ const isHitlResume = getTerminalType(currentStepDef) === "hitl";
447
524
  const isRetry = evaluation.action === "retry";
448
525
  let action;
449
- if (isRetry) {
526
+ if (isHitlResume) {
527
+ action = "advance"; // Human fixed it → fresh advance, retryCount resets
528
+ } else if (isRetry) {
450
529
  action = "retry";
451
530
  } else if (getTerminalType(nextStepDef) === "hitl") {
452
531
  action = "escalate";
@@ -454,14 +533,44 @@ export class WorkflowEngine {
454
533
  action = "advance";
455
534
  }
456
535
 
457
- return buildNavigateResponse(
536
+ // Only reset retryCount on genuine forward progress (conditional edge like on:"passed")
537
+ // Unconditional advances within retry loops (e.g., work → gate) preserve the count
538
+ const resetRetryCount = action === "advance" && evaluation.action === "conditional";
539
+
540
+ const response = buildNavigateResponse(
458
541
  workflowType,
459
542
  evaluation.nextStep,
460
543
  nextStepDef,
461
544
  action,
462
545
  isRetry,
463
546
  retryCount,
464
- description
547
+ description,
548
+ resetRetryCount,
549
+ projectRoot
465
550
  );
551
+
552
+ // Write-through: persist state transition and presentation to task file
553
+ if (taskFilePath) {
554
+ const task = readTaskFile(taskFilePath);
555
+ if (task) {
556
+ const userDesc = task.metadata?.userDescription || "";
557
+ task.metadata = { ...task.metadata, ...response.metadata };
558
+ task.subject = buildTaskSubject(
559
+ task.id, userDesc, response.metadata.workflowType,
560
+ response.currentStep, response.subagent, response.terminal,
561
+ response.maxRetries, response.metadata.retryCount
562
+ );
563
+ task.activeForm = buildTaskActiveForm(
564
+ response.stepInstructions?.name || response.currentStep,
565
+ response.subagent, response.terminal
566
+ );
567
+ if (response.orchestratorInstructions) {
568
+ task.description = response.orchestratorInstructions;
569
+ }
570
+ writeFileSync(taskFilePath, JSON.stringify(task, null, 2));
571
+ }
572
+ }
573
+
574
+ return response;
466
575
  }
467
576
  }