create-merlin-brain 3.7.3 → 3.8.0-beta.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.
@@ -0,0 +1,285 @@
1
+ ---
2
+ name: merlin:workflow
3
+ description: Run predefined multi-agent workflows (feature-dev, bug-fix, security-audit, refactor)
4
+ argument-hint: "[list | run <id> \"<task>\" | status | resume | skip]"
5
+ allowed-tools:
6
+ - Read
7
+ - Glob
8
+ - Grep
9
+ - Bash
10
+ - Write
11
+ - AskUserQuestion
12
+ - mcp__merlin__merlin_get_context
13
+ - mcp__merlin__merlin_search
14
+ - mcp__merlin__merlin_find_files
15
+ - mcp__merlin__merlin_sync_task
16
+ - mcp__merlin__merlin_save_checkpoint
17
+ ---
18
+
19
+ <objective>
20
+ Orchestrate predefined multi-agent workflows from within Claude Code sessions.
21
+
22
+ Workflows are named pipelines (e.g., "feature-dev", "bug-fix") that chain specialist agents
23
+ through a fixed sequence automatically. Each step spawns a FRESH Claude process with 200K context.
24
+
25
+ This command wraps the Merlin Loop workflow engine for interactive use:
26
+ - `list` — show available workflows
27
+ - `run <id> "<task>"` — execute a workflow
28
+ - `status` — check current run progress
29
+ - `resume` — resume an interrupted workflow
30
+ - `skip` — skip current step and advance
31
+
32
+ Workflows use the Blend Engine for optimal agent selection per step, structured handoffs
33
+ between steps, and independent verification for high-stakes steps.
34
+ </objective>
35
+
36
+ <process>
37
+
38
+ ## Step 1: Parse Arguments
39
+
40
+ Extract from $ARGUMENTS:
41
+ - **subcommand**: First word (list, run, status, resume, skip)
42
+ - **workflow-id**: Second word (only for `run`)
43
+ - **task**: Everything after workflow-id (only for `run`)
44
+
45
+ If no arguments:
46
+ ```
47
+ Available workflow commands:
48
+
49
+ /merlin:workflow list — See available workflows
50
+ /merlin:workflow run feature-dev "Add X" — Run a workflow
51
+ /merlin:workflow status — Check progress
52
+ /merlin:workflow resume — Resume interrupted workflow
53
+ /merlin:workflow skip — Skip current step
54
+
55
+ Bundled workflows: feature-dev, bug-fix, security-audit, refactor
56
+ ```
57
+
58
+ ## Step 2: Route by Subcommand
59
+
60
+ ### `list` — Show Available Workflows
61
+
62
+ Read workflow JSON files from two locations:
63
+ ```bash
64
+ # Bundled workflows (shipped with Merlin)
65
+ ls ~/.claude/loop/workflows/*.json 2>/dev/null
66
+
67
+ # User-installed workflows
68
+ ls ~/.claude/loop/workflows/*.json 2>/dev/null
69
+ ```
70
+
71
+ For each workflow file, extract and display:
72
+ - id, name, description, step count
73
+ - Step pipeline (e.g., "plan -> setup -> implement -> verify -> test -> pr -> review")
74
+
75
+ Format:
76
+ ```
77
+ Available Workflows
78
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
79
+
80
+ feature-dev [7 steps]
81
+ Feature Development — Drop in a feature request. Get back a tested PR.
82
+ Pipeline: Planning -> Setup -> Implementation -> Verification -> Testing -> PR -> Review
83
+
84
+ bug-fix [6 steps]
85
+ Bug Fix — Report a bug. Get back a fix with regression test and PR.
86
+ Pipeline: Triage -> Investigation -> Fix -> Regression Test -> Verification -> PR
87
+
88
+ security-audit [6 steps]
89
+ Security Audit — Run a full security sweep. Get fixes and a clean report.
90
+ Pipeline: Scan -> Prioritize -> Fix -> Verification -> Tests -> PR
91
+
92
+ refactor [6 steps]
93
+ Code Refactor — Analyze, plan, refactor, verify, test, and PR.
94
+ Pipeline: Analysis -> Plan -> Refactor -> Verification -> Tests -> PR
95
+ ```
96
+
97
+ ### `run` — Execute a Workflow
98
+
99
+ **Required args:** workflow-id and task description.
100
+
101
+ If missing:
102
+ ```
103
+ Usage: /merlin:workflow run <workflow-id> "<task description>"
104
+
105
+ Example: /merlin:workflow run feature-dev "Add OAuth login with Google and GitHub providers"
106
+ ```
107
+
108
+ #### Step 2a: Load Workflow
109
+
110
+ ```bash
111
+ WORKFLOW_FILE=""
112
+ for dir in ~/.claude/loop/workflows; do
113
+ if [ -f "$dir/${WORKFLOW_ID}.json" ]; then
114
+ WORKFLOW_FILE="$dir/${WORKFLOW_ID}.json"
115
+ break
116
+ fi
117
+ done
118
+ ```
119
+
120
+ If not found, list available workflows and error.
121
+
122
+ #### Step 2b: Get Sights Context
123
+
124
+ ```
125
+ Call: merlin_get_context
126
+ Task: "{task description}"
127
+ ```
128
+
129
+ #### Step 2c: Pre-Flight Summary
130
+
131
+ Parse the workflow JSON and show:
132
+ ```
133
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
134
+ WORKFLOW: Feature Development
135
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
136
+
137
+ Task: Add OAuth login with Google and GitHub providers
138
+ Steps: 7
139
+ Pipeline: Planning -> Setup -> Implementation -> Verification -> Testing -> PR -> Review
140
+
141
+ Each step spawns a fresh Claude process with 200K context.
142
+ Estimated time: 15-30 minutes depending on complexity.
143
+
144
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
145
+ ```
146
+
147
+ Ask user to confirm or provide additional context:
148
+ - "Any constraints or notes before we start?"
149
+ - If none, proceed.
150
+
151
+ #### Step 2d: Execute via merlin-loop
152
+
153
+ Spawn the workflow engine as a shell process:
154
+
155
+ ```bash
156
+ cd "$(pwd)" && merlin-loop workflow run "${WORKFLOW_ID}" "${TASK}" 2>&1
157
+ ```
158
+
159
+ Use Bash tool with `timeout: 600000` (10 minutes per step, up to 60 minutes total).
160
+
161
+ **IMPORTANT:** If `merlin-loop` is not in PATH, find it:
162
+ ```bash
163
+ LOOP_SCRIPT="${HOME}/.claude/loop/merlin-loop.sh"
164
+ bash "$LOOP_SCRIPT" workflow run "${WORKFLOW_ID}" "${TASK}" 2>&1
165
+ ```
166
+
167
+ #### Step 2e: Monitor and Report
168
+
169
+ After each step completes, the workflow engine outputs progress. Capture and relay to user:
170
+ - Step name and status (done/failed/skipped)
171
+ - Overall progress (e.g., "3/7 steps complete")
172
+
173
+ If the workflow pauses on failure:
174
+ ```
175
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
176
+ WORKFLOW PAUSED at step: implement
177
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
178
+
179
+ Step "Implementation" failed after 3 attempts.
180
+
181
+ [1] Resume workflow (/merlin:workflow resume)
182
+ [2] Skip this step (/merlin:workflow skip)
183
+ [3] Investigate the failure
184
+ [4] Abort
185
+ ```
186
+
187
+ #### Step 2f: Completion
188
+
189
+ ```
190
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
191
+ WORKFLOW COMPLETE: Feature Development
192
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
193
+
194
+ Task: Add OAuth login
195
+ Steps: 7/7 complete
196
+ Duration: ~22 minutes
197
+
198
+ Steps completed:
199
+ ✓ Planning
200
+ ✓ Environment Setup
201
+ ✓ Implementation
202
+ ✓ Independent Verification
203
+ ✓ Testing
204
+ ✓ Pull Request
205
+ ✓ Final Review
206
+
207
+ Next steps:
208
+ [1] Review the PR
209
+ [2] Run another workflow
210
+ [3] Continue in current context
211
+ ```
212
+
213
+ ### `status` — Show Current Run
214
+
215
+ Read the workflow run state:
216
+ ```bash
217
+ cat .merlin-loop/workflow-run.json 2>/dev/null
218
+ ```
219
+
220
+ Parse and display:
221
+ ```
222
+ Workflow: Feature Development
223
+ Task: Add OAuth login
224
+ Status: running
225
+ Step: implement (3/7)
226
+
227
+ ✓ plan (done)
228
+ ✓ setup (done)
229
+ ▶ implement (running, attempt 1)
230
+ · verify (pending)
231
+ · test (pending)
232
+ · pr (pending)
233
+ · review (pending)
234
+ ```
235
+
236
+ If no active run: "No active workflow. Run one with: /merlin:workflow run <id> \"task\""
237
+
238
+ ### `resume` — Resume Interrupted Workflow
239
+
240
+ ```bash
241
+ LOOP_SCRIPT="${HOME}/.claude/loop/merlin-loop.sh"
242
+ bash "$LOOP_SCRIPT" workflow resume 2>&1
243
+ ```
244
+
245
+ Show progress as steps complete.
246
+
247
+ ### `skip` — Skip Current Step
248
+
249
+ ```bash
250
+ LOOP_SCRIPT="${HOME}/.claude/loop/merlin-loop.sh"
251
+ bash "$LOOP_SCRIPT" workflow skip 2>&1
252
+ ```
253
+
254
+ Confirm the skip and show updated status.
255
+
256
+ </process>
257
+
258
+ <critical_rules>
259
+
260
+ **ALWAYS use merlin-loop workflow engine** — do NOT try to orchestrate steps manually.
261
+ The workflow engine handles: blend selection, handoffs, retries, state tracking, resumability.
262
+
263
+ **FRESH PROCESS PER STEP** — each workflow step spawns a new `claude --agent` process.
264
+ This is handled by the workflow engine, not by this command.
265
+
266
+ **NEVER run specialist work in this orchestrator context** — only manage the workflow lifecycle.
267
+
268
+ **STATE IS PERSISTENT** — workflow-run.json survives crashes. Resume picks up exactly where it left off.
269
+
270
+ **TIMEOUT** — use 600000ms (10 min) for status/resume/skip, up to 3600000ms (60 min) for full workflow runs.
271
+
272
+ </critical_rules>
273
+
274
+ <error_handling>
275
+
276
+ | Condition | Action |
277
+ |-----------|--------|
278
+ | Workflow ID not found | List available workflows, suggest closest match |
279
+ | merlin-loop not found | Check ~/.claude/loop/merlin-loop.sh, suggest reinstall |
280
+ | Workflow step fails | Show failure, offer resume/skip/investigate/abort |
281
+ | Timeout on full run | Suggest using `merlin-loop workflow run` directly in terminal |
282
+ | No workflow-run.json | Report "no active run" for status/resume/skip |
283
+ | Invalid JSON in workflow | Report parse error, suggest checking file |
284
+
285
+ </error_handling>
@@ -0,0 +1,359 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
4
+ # ║ MERLIN WORKFLOW ENGINE — Run, Resume, Status, CLI ║
5
+ # ║ Orchestration layer on top of workflow.sh core engine ║
6
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
7
+ #
8
+ # Requires: workflow.sh loaded first (provides core functions)
9
+
10
+ # Colors (inherit or set defaults)
11
+ : "${RESET:=\033[0m}"
12
+ : "${BOLD:=\033[1m}"
13
+ : "${DIM:=\033[2m}"
14
+ : "${RED:=\033[31m}"
15
+ : "${GREEN:=\033[32m}"
16
+ : "${YELLOW:=\033[33m}"
17
+ : "${BLUE:=\033[34m}"
18
+ : "${MAGENTA:=\033[35m}"
19
+ : "${CYAN:=\033[36m}"
20
+
21
+ # ═══════════════════════════════════════════════════════════════════════════════
22
+ # Main Run Orchestrator
23
+ # ═══════════════════════════════════════════════════════════════════════════════
24
+
25
+ # Run a complete workflow
26
+ workflow_run() {
27
+ local wf_id="$1"
28
+ local task="$2"
29
+
30
+ # Load workflow
31
+ local wf_json
32
+ wf_json=$(workflow_load "$wf_id") || return 1
33
+
34
+ local wf_name step_count on_failure
35
+ wf_name=$(echo "$wf_json" | python3 -c "import json,sys; print(json.load(sys.stdin).get('name', '$wf_id'))")
36
+ step_count=$(echo "$wf_json" | python3 -c "import json,sys; print(len(json.load(sys.stdin)['steps']))")
37
+ on_failure=$(echo "$wf_json" | python3 -c "import json,sys; print(json.load(sys.stdin).get('on_failure','pause'))")
38
+
39
+ # Banner
40
+ echo -e "${MAGENTA}${BOLD}"
41
+ echo " ╔═══════════════════════════════════════════════════════════╗"
42
+ echo " ║ MERLIN WORKFLOW: ${wf_name}"
43
+ echo " ╚═══════════════════════════════════════════════════════════╝"
44
+ echo -e "${RESET}"
45
+ echo -e " ${CYAN}Task:${RESET} $task"
46
+ echo -e " ${CYAN}Steps:${RESET} $step_count"
47
+ echo ""
48
+
49
+ # Show step pipeline
50
+ local step_labels
51
+ step_labels=$(echo "$wf_json" | python3 -c "
52
+ import json,sys
53
+ wf = json.load(sys.stdin)
54
+ labels = [s.get('label', s['id']) for s in wf['steps']]
55
+ print(' -> '.join(labels))
56
+ ")
57
+ echo -e " ${DIM}Pipeline: ${step_labels}${RESET}"
58
+ echo ""
59
+
60
+ # Initialize run
61
+ local session_dir="${MERLIN_LOOP_DIR:-.merlin-loop}/workflow-session"
62
+ mkdir -p "$session_dir"
63
+
64
+ local run_id
65
+ run_id=$(workflow_run_init "$wf_json" "$task")
66
+ echo -e " ${BLUE}Run ID: ${BOLD}${run_id}${RESET}"
67
+ echo ""
68
+
69
+ # Create handoff chain
70
+ if type handoff_create &>/dev/null; then
71
+ handoff_create "$session_dir" >/dev/null
72
+ fi
73
+
74
+ # Execute steps sequentially
75
+ local step_num=0
76
+ local total_steps="$step_count"
77
+
78
+ echo "$wf_json" | python3 -c "
79
+ import json,sys
80
+ wf = json.load(sys.stdin)
81
+ for s in wf['steps']:
82
+ print(json.dumps(s))
83
+ " | while IFS= read -r step_json; do
84
+ step_num=$((step_num + 1))
85
+ local step_id step_label
86
+ step_id=$(echo "$step_json" | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")
87
+ step_label=$(echo "$step_json" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('label', d['id']))")
88
+
89
+ echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
90
+ echo -e "${BOLD} Step ${step_num}/${total_steps}: ${step_label}${RESET}"
91
+ echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
92
+
93
+ # Get handoff context for this step
94
+ local handoff_context=""
95
+ if type handoff_format_for_agent &>/dev/null && [ -f "$session_dir/handoff.json" ]; then
96
+ handoff_context=$(handoff_format_for_agent "$session_dir/handoff.json")
97
+ fi
98
+
99
+ # Execute step
100
+ local step_output
101
+ step_output=$(workflow_step_run "$step_json" "$task" "$handoff_context" "$session_dir")
102
+ local step_exit=$?
103
+
104
+ if [ $step_exit -eq 0 ]; then
105
+ echo -e " ${GREEN}${BOLD}✓ ${step_label} complete${RESET}"
106
+ else
107
+ echo -e " ${RED}${BOLD}✗ ${step_label} failed${RESET}"
108
+ case "$on_failure" in
109
+ pause)
110
+ echo -e " ${YELLOW}Workflow paused. Resume with: merlin-loop workflow resume${RESET}"
111
+ return 1 ;;
112
+ skip)
113
+ echo -e " ${YELLOW}Skipping failed step and continuing...${RESET}"
114
+ _workflow_update_step "$step_id" "skipped" ;;
115
+ abort)
116
+ echo -e " ${RED}Workflow aborted.${RESET}"
117
+ return 1 ;;
118
+ esac
119
+ fi
120
+ echo ""
121
+ done
122
+
123
+ # Completion banner
124
+ echo -e "${GREEN}${BOLD}"
125
+ echo " ╔═══════════════════════════════════════════════════════════╗"
126
+ echo " ║ WORKFLOW COMPLETE ║"
127
+ echo " ╚═══════════════════════════════════════════════════════════╝"
128
+ echo -e "${RESET}"
129
+
130
+ local on_complete
131
+ on_complete=$(echo "$wf_json" | python3 -c "import json,sys; print(json.load(sys.stdin).get('on_complete','notify'))")
132
+ if [ "$on_complete" = "notify" ] && type send_notification &>/dev/null; then
133
+ send_notification "Workflow Complete" "${wf_name}: ${task}"
134
+ fi
135
+ return 0
136
+ }
137
+
138
+ # ═══════════════════════════════════════════════════════════════════════════════
139
+ # Resume from interrupted run
140
+ # ═══════════════════════════════════════════════════════════════════════════════
141
+
142
+ workflow_resume() {
143
+ local run_id="${1:-}"
144
+
145
+ if [ ! -f "$WORKFLOW_RUN_FILE" ]; then
146
+ echo -e "${RED}No workflow run to resume.${RESET}" >&2
147
+ return 1
148
+ fi
149
+
150
+ local wf_id task current_step
151
+ wf_id=$(python3 -c "import json; r=json.load(open('$WORKFLOW_RUN_FILE')); print(r['workflow'])")
152
+ task=$(python3 -c "import json; r=json.load(open('$WORKFLOW_RUN_FILE')); print(r['task'])")
153
+ current_step=$(python3 -c "import json; r=json.load(open('$WORKFLOW_RUN_FILE')); print(r.get('current_step',''))")
154
+
155
+ if [ -z "$current_step" ] || [ "$current_step" = "None" ]; then
156
+ echo -e "${GREEN}Workflow already completed.${RESET}"
157
+ return 0
158
+ fi
159
+
160
+ echo -e "${BLUE}Resuming workflow '${wf_id}' from step '${current_step}'${RESET}"
161
+ echo -e "${BLUE}Task: ${task}${RESET}"
162
+ echo ""
163
+
164
+ local wf_json
165
+ wf_json=$(workflow_load "$wf_id") || return 1
166
+
167
+ local session_dir="${MERLIN_LOOP_DIR:-.merlin-loop}/workflow-session"
168
+ mkdir -p "$session_dir"
169
+
170
+ if [ ! -f "$session_dir/handoff.json" ] && type handoff_create &>/dev/null; then
171
+ handoff_create "$session_dir" >/dev/null
172
+ fi
173
+
174
+ local step_num=0
175
+ echo "$wf_json" | python3 -c "
176
+ import json,sys
177
+ wf = json.load(sys.stdin)
178
+ for s in wf['steps']:
179
+ print(json.dumps(s))
180
+ " | while IFS= read -r step_json; do
181
+ step_num=$((step_num + 1))
182
+ local step_id
183
+ step_id=$(echo "$step_json" | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")
184
+
185
+ local step_status
186
+ step_status=$(python3 -c "import json; r=json.load(open('$WORKFLOW_RUN_FILE')); print(r['steps'].get('$step_id',{}).get('status','pending'))")
187
+
188
+ if [ "$step_status" = "done" ] || [ "$step_status" = "skipped" ]; then
189
+ echo -e " ${DIM}✓ ${step_id} (${step_status})${RESET}"
190
+ continue
191
+ fi
192
+
193
+ local step_label
194
+ step_label=$(echo "$step_json" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('label', d['id']))")
195
+
196
+ echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
197
+ echo -e "${BOLD} Resuming: ${step_label}${RESET}"
198
+ echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
199
+
200
+ local handoff_context=""
201
+ if type handoff_format_for_agent &>/dev/null && [ -f "$session_dir/handoff.json" ]; then
202
+ handoff_context=$(handoff_format_for_agent "$session_dir/handoff.json")
203
+ fi
204
+
205
+ local step_output
206
+ step_output=$(workflow_step_run "$step_json" "$task" "$handoff_context" "$session_dir")
207
+ local step_exit=$?
208
+
209
+ if [ $step_exit -eq 0 ]; then
210
+ echo -e " ${GREEN}${BOLD}✓ ${step_label} complete${RESET}"
211
+ else
212
+ echo -e " ${RED}${BOLD}✗ ${step_label} failed${RESET}"
213
+ echo -e " ${YELLOW}Paused. Resume with: merlin-loop workflow resume${RESET}"
214
+ return 1
215
+ fi
216
+ done
217
+
218
+ echo -e "${GREEN}${BOLD} Workflow resumed and completed.${RESET}"
219
+ return 0
220
+ }
221
+
222
+ # ═══════════════════════════════════════════════════════════════════════════════
223
+ # Status Display
224
+ # ═══════════════════════════════════════════════════════════════════════════════
225
+
226
+ workflow_status() {
227
+ if [ ! -f "$WORKFLOW_RUN_FILE" ]; then
228
+ echo -e "${DIM}No active workflow run.${RESET}"
229
+ return 0
230
+ fi
231
+
232
+ python3 << PYEOF
233
+ import json
234
+
235
+ with open("$WORKFLOW_RUN_FILE", 'r') as f:
236
+ run = json.load(f)
237
+
238
+ print(f"\n \033[1mWorkflow:\033[0m {run.get('workflow_name', run['workflow'])}")
239
+ print(f" \033[1mTask:\033[0m {run['task']}")
240
+ print(f" \033[1mStatus:\033[0m {run['status']}")
241
+ print(f" \033[1mRun ID:\033[0m {run['run_id']}")
242
+ print(f" \033[1mStarted:\033[0m {run['started']}")
243
+ print()
244
+
245
+ for step_id, step in run['steps'].items():
246
+ status = step.get('status', 'pending')
247
+ icons = {'done': '\033[32m\u2713\033[0m', 'running': '\033[33m\u25b6\033[0m',
248
+ 'failed': '\033[31m\u2717\033[0m', 'skipped': '\033[2m\u25cb\033[0m',
249
+ 'pending': '\033[2m\u00b7\033[0m'}
250
+ icon = icons.get(status, '?')
251
+ current = " \u2190 current" if step_id == run.get('current_step') else ""
252
+ attempts = f" (attempts: {step.get('attempts', 0)})" if step.get('attempts', 0) > 1 else ""
253
+ print(f" {icon} {step_id}{attempts}{current}")
254
+ print()
255
+ PYEOF
256
+ }
257
+
258
+ # ═══════════════════════════════════════════════════════════════════════════════
259
+ # Skip & Install
260
+ # ═══════════════════════════════════════════════════════════════════════════════
261
+
262
+ workflow_skip() {
263
+ if [ ! -f "$WORKFLOW_RUN_FILE" ]; then
264
+ echo -e "${RED}No active workflow run.${RESET}" >&2
265
+ return 1
266
+ fi
267
+
268
+ local current_step
269
+ current_step=$(python3 -c "import json; r=json.load(open('$WORKFLOW_RUN_FILE')); print(r.get('current_step',''))")
270
+
271
+ if [ -z "$current_step" ] || [ "$current_step" = "None" ]; then
272
+ echo -e "${GREEN}Workflow already completed.${RESET}"
273
+ return 0
274
+ fi
275
+
276
+ _workflow_update_step "$current_step" "skipped"
277
+ echo -e "${YELLOW}Skipped step: ${current_step}${RESET}"
278
+ workflow_status
279
+ }
280
+
281
+ workflow_install() {
282
+ local source="$1"
283
+ mkdir -p "$WORKFLOW_DIR"
284
+
285
+ if [[ "$source" == http* ]] || [[ "$source" == github.com/* ]]; then
286
+ local url="$source"
287
+ [[ "$url" != http* ]] && url="https://${url}"
288
+
289
+ if echo "$url" | grep -q "github.com"; then
290
+ url=$(echo "$url" | sed 's|github.com|raw.githubusercontent.com|' | sed 's|/blob/||')
291
+ fi
292
+
293
+ local filename
294
+ filename=$(basename "$url")
295
+ [[ "$filename" != *.json ]] && filename="${filename}.json"
296
+
297
+ echo -e "${BLUE}Downloading: ${url}${RESET}"
298
+ if curl -fsSL "$url" -o "$WORKFLOW_DIR/$filename" 2>/dev/null; then
299
+ if python3 -c "import json; json.load(open('$WORKFLOW_DIR/$filename'))" 2>/dev/null; then
300
+ echo -e "${GREEN}Installed: ${WORKFLOW_DIR}/${filename}${RESET}"
301
+ else
302
+ rm -f "$WORKFLOW_DIR/$filename"
303
+ echo -e "${RED}Invalid JSON. Installation failed.${RESET}" >&2
304
+ return 1
305
+ fi
306
+ else
307
+ echo -e "${RED}Download failed.${RESET}" >&2
308
+ return 1
309
+ fi
310
+ elif [ -f "$source" ]; then
311
+ local filename
312
+ filename=$(basename "$source")
313
+ cp "$source" "$WORKFLOW_DIR/$filename"
314
+ echo -e "${GREEN}Installed: ${WORKFLOW_DIR}/${filename}${RESET}"
315
+ else
316
+ echo -e "${RED}Source not found: ${source}${RESET}" >&2
317
+ return 1
318
+ fi
319
+ }
320
+
321
+ # ═══════════════════════════════════════════════════════════════════════════════
322
+ # CLI Dispatch
323
+ # ═══════════════════════════════════════════════════════════════════════════════
324
+
325
+ workflow_dispatch() {
326
+ local subcmd="${1:-list}"
327
+ shift 2>/dev/null || true
328
+
329
+ case "$subcmd" in
330
+ list) workflow_list ;;
331
+ run)
332
+ local wf_id="${1:-}"
333
+ shift 2>/dev/null || true
334
+ local task="$*"
335
+ if [ -z "$wf_id" ] || [ -z "$task" ]; then
336
+ echo -e "${RED}Usage: merlin-loop workflow run <id> \"<task>\"${RESET}" >&2
337
+ echo -e "${DIM}Example: merlin-loop workflow run feature-dev \"Add OAuth login\"${RESET}" >&2
338
+ return 1
339
+ fi
340
+ workflow_run "$wf_id" "$task"
341
+ ;;
342
+ status) workflow_status ;;
343
+ resume) workflow_resume "$@" ;;
344
+ skip) workflow_skip ;;
345
+ install)
346
+ local source="${1:-}"
347
+ if [ -z "$source" ]; then
348
+ echo -e "${RED}Usage: merlin-loop workflow install <url|file>${RESET}" >&2
349
+ return 1
350
+ fi
351
+ workflow_install "$source"
352
+ ;;
353
+ *)
354
+ echo -e "${RED}Unknown workflow command: ${subcmd}${RESET}" >&2
355
+ echo -e "Commands: list, run, status, resume, skip, install" >&2
356
+ return 1
357
+ ;;
358
+ esac
359
+ }