create-merlin-brain 2.3.2 → 2.4.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.
- package/bin/install.cjs +76 -3
- package/dist/server/api/client.d.ts +4 -0
- package/dist/server/api/client.d.ts.map +1 -1
- package/dist/server/api/client.js +4 -0
- package/dist/server/api/client.js.map +1 -1
- package/dist/server/tools/project.d.ts.map +1 -1
- package/dist/server/tools/project.js +22 -1
- package/dist/server/tools/project.js.map +1 -1
- package/files/loop/lib/agents.sh +603 -0
- package/files/loop/lib/boot.sh +453 -0
- package/files/loop/lib/discuss.sh +224 -0
- package/files/loop/lib/modes.sh +294 -0
- package/files/loop/lib/session-end.sh +248 -0
- package/files/loop/lib/sights.sh +725 -0
- package/files/loop/lib/timeout.sh +207 -0
- package/files/loop/lib/tui.sh +388 -0
- package/files/loop/merlin-loop.sh +311 -16
- package/files/loop/prompts/PROMPT_DISCUSS.md +102 -0
- package/files/loop/prompts/PROMPT_build.md +152 -2
- package/package.json +1 -1
|
@@ -37,6 +37,12 @@ source "$SCRIPT_DIR/lib/state.sh"
|
|
|
37
37
|
source "$SCRIPT_DIR/lib/safety.sh"
|
|
38
38
|
source "$SCRIPT_DIR/lib/progress.sh"
|
|
39
39
|
source "$SCRIPT_DIR/lib/context.sh"
|
|
40
|
+
source "$SCRIPT_DIR/lib/modes.sh"
|
|
41
|
+
source "$SCRIPT_DIR/lib/sights.sh" 2>/dev/null || true # Sights integration
|
|
42
|
+
source "$SCRIPT_DIR/lib/agents.sh" 2>/dev/null || true # Agent profiles and routing
|
|
43
|
+
source "$SCRIPT_DIR/lib/boot.sh" 2>/dev/null || true # Boot sequence
|
|
44
|
+
source "$SCRIPT_DIR/lib/session-end.sh" 2>/dev/null || true # Session end protocol
|
|
45
|
+
source "$SCRIPT_DIR/lib/tui.sh" 2>/dev/null || true # Interactive TUI
|
|
40
46
|
|
|
41
47
|
# Defaults (can be overridden via config.json or env vars)
|
|
42
48
|
MAX_ITERATIONS="${MERLIN_MAX_ITERATIONS:-50}"
|
|
@@ -46,6 +52,14 @@ RATE_LIMIT_PER_HOUR="${MERLIN_RATE_LIMIT:-100}"
|
|
|
46
52
|
CLOUD_SYNC="${MERLIN_CLOUD_SYNC:-true}"
|
|
47
53
|
NOTIFY_ON_COMPLETE="${MERLIN_NOTIFY:-true}"
|
|
48
54
|
|
|
55
|
+
# Loop mode configuration (auto, interactive, hybrid)
|
|
56
|
+
LOOP_MODE="${MERLIN_LOOP_MODE:-hybrid}"
|
|
57
|
+
PAUSE_INTERVAL="${MERLIN_PAUSE_INTERVAL:-5}" # Pause every N tasks in hybrid mode
|
|
58
|
+
|
|
59
|
+
# Timeout configuration
|
|
60
|
+
PAUSE_TIMEOUT="${MERLIN_PAUSE_TIMEOUT:-300}" # 5 minutes default
|
|
61
|
+
TIMEOUT_ENABLED="${MERLIN_TIMEOUT_ENABLED:-true}"
|
|
62
|
+
|
|
49
63
|
# Native Claude Tasks integration
|
|
50
64
|
# This enables cross-session task coordination - all spawned instances share task state
|
|
51
65
|
# If not provided, we'll generate one based on project directory
|
|
@@ -159,11 +173,13 @@ show_launch_screen() {
|
|
|
159
173
|
echo ""
|
|
160
174
|
echo -e " ${YELLOW}${BOLD}▸ STARTING AUTONOMOUS BUILD${RESET}"
|
|
161
175
|
echo ""
|
|
162
|
-
echo -e " ${CYAN}
|
|
176
|
+
echo -e " ${CYAN}Command:${RESET} ${BOLD}$mode${RESET}"
|
|
177
|
+
echo -e " ${CYAN}Loop Mode:${RESET} ${BOLD}$(get_mode_display)${RESET}"
|
|
163
178
|
echo -e " ${CYAN}AI:${RESET} ${BOLD}$ai_cli${RESET}"
|
|
164
179
|
echo -e " ${CYAN}Max Iterations:${RESET} ${BOLD}$MAX_ITERATIONS${RESET}"
|
|
165
180
|
echo -e " ${CYAN}Cooldown:${RESET} ${BOLD}${COOLDOWN_SECONDS}s${RESET} between iterations"
|
|
166
181
|
echo -e " ${CYAN}Circuit Breaker:${RESET}${BOLD}$CIRCUIT_BREAKER_THRESHOLD${RESET} errors to stop"
|
|
182
|
+
echo -e " ${CYAN}Pause Timeout:${RESET} ${BOLD}$(get_timeout_status 2>/dev/null || echo "${PAUSE_TIMEOUT}s")${RESET}"
|
|
167
183
|
echo -e " ${CYAN}Cloud Sync:${RESET} ${BOLD}$CLOUD_SYNC${RESET}"
|
|
168
184
|
echo -e " ${CYAN}Task List:${RESET} ${BOLD}${TASK_LIST_ID:-auto}${RESET}"
|
|
169
185
|
echo ""
|
|
@@ -198,6 +214,7 @@ show_launch_screen() {
|
|
|
198
214
|
|
|
199
215
|
init_loop() {
|
|
200
216
|
mkdir -p "$LOOP_DIR"
|
|
217
|
+
LOOP_START_TIME=$(date +%s)
|
|
201
218
|
|
|
202
219
|
# Check for existing lock (another loop running)
|
|
203
220
|
if [ -f "$LOCK_FILE" ]; then
|
|
@@ -236,9 +253,95 @@ init_loop() {
|
|
|
236
253
|
if [ "$CLOUD_SYNC" = "true" ]; then
|
|
237
254
|
sync_from_cloud
|
|
238
255
|
fi
|
|
256
|
+
|
|
257
|
+
# Initialize Sights (includes worker registration)
|
|
258
|
+
if type init_sights &> /dev/null; then
|
|
259
|
+
init_sights
|
|
260
|
+
echo -e "${GREEN}✓ Sights: Connected${RESET}"
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
# Check for cloud checkpoint and offer restore
|
|
264
|
+
if type fetch_checkpoint &> /dev/null; then
|
|
265
|
+
local checkpoint_json
|
|
266
|
+
checkpoint_json=$(fetch_checkpoint 2>/dev/null || echo "")
|
|
267
|
+
|
|
268
|
+
if [ -n "$checkpoint_json" ]; then
|
|
269
|
+
local has_checkpoint
|
|
270
|
+
has_checkpoint=$(echo "$checkpoint_json" | jq -r '.checkpoint != null' 2>/dev/null || echo "false")
|
|
271
|
+
|
|
272
|
+
if [ "$has_checkpoint" = "true" ]; then
|
|
273
|
+
echo ""
|
|
274
|
+
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
275
|
+
echo -e "${BOLD}📍 Cloud Checkpoint Found${RESET}"
|
|
276
|
+
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
277
|
+
format_checkpoint "$checkpoint_json"
|
|
278
|
+
echo ""
|
|
279
|
+
|
|
280
|
+
if [ "${QUIET:-false}" != "true" ] && [ -t 0 ]; then
|
|
281
|
+
echo -e "${YELLOW}Resume from this checkpoint?${RESET}"
|
|
282
|
+
echo -e " [${BOLD}y${RESET}] Yes, continue from checkpoint"
|
|
283
|
+
echo -e " [${BOLD}n${RESET}] No, start fresh"
|
|
284
|
+
echo -ne " Choice: "
|
|
285
|
+
read -r choice
|
|
286
|
+
|
|
287
|
+
if [ "$choice" = "y" ] || [ "$choice" = "Y" ]; then
|
|
288
|
+
# Restore checkpoint state to local state
|
|
289
|
+
local phase task summary
|
|
290
|
+
phase=$(echo "$checkpoint_json" | jq -r '.checkpoint.currentPhase // empty')
|
|
291
|
+
task=$(echo "$checkpoint_json" | jq -r '.checkpoint.currentTask // empty')
|
|
292
|
+
summary=$(echo "$checkpoint_json" | jq -r '.checkpoint.summary // empty')
|
|
293
|
+
|
|
294
|
+
if [ -n "$phase" ]; then
|
|
295
|
+
set_state_value "plan.current_phase" "$phase"
|
|
296
|
+
fi
|
|
297
|
+
if [ -n "$task" ] && [ "$task" != "null" ]; then
|
|
298
|
+
set_state_value "plan.current_task" "$task"
|
|
299
|
+
fi
|
|
300
|
+
|
|
301
|
+
echo -e "${GREEN}✓ Checkpoint restored${RESET}"
|
|
302
|
+
echo ""
|
|
303
|
+
fi
|
|
304
|
+
fi
|
|
305
|
+
fi
|
|
306
|
+
fi
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
# Start worker heartbeat in background
|
|
310
|
+
if type start_heartbeat &> /dev/null; then
|
|
311
|
+
start_heartbeat 30 # Send heartbeat every 30 seconds
|
|
312
|
+
echo -e "${BLUE}Worker heartbeat started${RESET}"
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
# Check for other active workers (multi-user awareness)
|
|
316
|
+
if type get_active_workers &> /dev/null; then
|
|
317
|
+
local workers_json
|
|
318
|
+
workers_json=$(get_active_workers 2>/dev/null || echo "")
|
|
319
|
+
|
|
320
|
+
if [ -n "$workers_json" ]; then
|
|
321
|
+
local worker_count
|
|
322
|
+
worker_count=$(echo "$workers_json" | jq -r '.count // 0' 2>/dev/null || echo "0")
|
|
323
|
+
|
|
324
|
+
if [ "$worker_count" -gt 1 ]; then
|
|
325
|
+
echo ""
|
|
326
|
+
echo -e "${YELLOW}⚠️ ${worker_count} workers active on this repository${RESET}"
|
|
327
|
+
echo -e "${YELLOW} Be aware of potential task conflicts.${RESET}"
|
|
328
|
+
echo ""
|
|
329
|
+
fi
|
|
330
|
+
fi
|
|
331
|
+
fi
|
|
239
332
|
}
|
|
240
333
|
|
|
241
334
|
cleanup() {
|
|
335
|
+
# Stop worker heartbeat
|
|
336
|
+
if type stop_heartbeat &> /dev/null; then
|
|
337
|
+
stop_heartbeat
|
|
338
|
+
fi
|
|
339
|
+
|
|
340
|
+
# Run quick session end on cleanup (commit + checkpoint without verbose output)
|
|
341
|
+
if type session_end_quick &> /dev/null; then
|
|
342
|
+
session_end_quick "Loop stopped (cleanup)" "" 2>/dev/null || true
|
|
343
|
+
fi
|
|
344
|
+
|
|
242
345
|
rm -f "$LOCK_FILE"
|
|
243
346
|
|
|
244
347
|
# Final cloud sync
|
|
@@ -252,16 +355,36 @@ run_iteration() {
|
|
|
252
355
|
local iteration="$2"
|
|
253
356
|
local prompt_file="$SCRIPT_DIR/prompts/PROMPT_${mode}.md"
|
|
254
357
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
echo
|
|
358
|
+
# Get current task info
|
|
359
|
+
local current_task_desc task_completed task_total
|
|
360
|
+
current_task_desc=$(get_state_value "plan.current_task_desc" 2>/dev/null || echo "Continue with next task")
|
|
361
|
+
task_completed=$(get_state_value "plan.completed_tasks" 2>/dev/null || echo "0")
|
|
362
|
+
task_total=$(get_state_value "plan.total_tasks" 2>/dev/null || echo "0")
|
|
363
|
+
|
|
364
|
+
# TUI: Show task header with progress bar (replaces basic echo)
|
|
365
|
+
if type show_task_header &> /dev/null; then
|
|
366
|
+
show_task_header "$iteration" "$MAX_ITERATIONS" "$current_task_desc" "${BOOT_SELECTED_AGENT:-claude}"
|
|
367
|
+
if [ "$task_total" -gt 0 ] && type draw_progress_bar &> /dev/null; then
|
|
368
|
+
draw_progress_bar "$task_completed" "$task_total" 50 " Tasks"
|
|
369
|
+
echo ""
|
|
370
|
+
fi
|
|
371
|
+
else
|
|
372
|
+
echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
373
|
+
echo -e "${BOLD}Iteration $iteration / $MAX_ITERATIONS${RESET} (mode: $mode)"
|
|
374
|
+
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
375
|
+
fi
|
|
376
|
+
|
|
377
|
+
# Run Merlin Pro Boot Sequence (5 phases)
|
|
378
|
+
if type run_boot_sequence &> /dev/null; then
|
|
379
|
+
run_boot_sequence "$current_task_desc"
|
|
380
|
+
fi
|
|
258
381
|
|
|
259
382
|
# Record iteration start
|
|
260
383
|
local start_time
|
|
261
384
|
start_time=$(date +%s)
|
|
262
385
|
record_iteration_start "$iteration" "$mode"
|
|
263
386
|
|
|
264
|
-
# Build the prompt with current state
|
|
387
|
+
# Build the prompt with current state and boot context
|
|
265
388
|
local full_prompt
|
|
266
389
|
full_prompt=$(build_prompt "$mode")
|
|
267
390
|
|
|
@@ -269,13 +392,25 @@ run_iteration() {
|
|
|
269
392
|
local output
|
|
270
393
|
local exit_code=0
|
|
271
394
|
|
|
272
|
-
|
|
273
|
-
|
|
395
|
+
# Route to the specialist agent selected by boot sequence
|
|
396
|
+
local agent_cli_name="merlin"
|
|
397
|
+
if [ -n "${BOOT_SELECTED_AGENT:-}" ] && type get_agent_cli_name &> /dev/null; then
|
|
398
|
+
agent_cli_name=$(get_agent_cli_name "$BOOT_SELECTED_AGENT")
|
|
399
|
+
fi
|
|
400
|
+
|
|
401
|
+
local agent_display_name="$agent_cli_name"
|
|
402
|
+
if type get_agent_display_name &> /dev/null && [ -n "${BOOT_SELECTED_AGENT:-}" ]; then
|
|
403
|
+
agent_display_name=$(get_agent_display_name "$BOOT_SELECTED_AGENT")
|
|
404
|
+
fi
|
|
405
|
+
|
|
406
|
+
echo -e "${BLUE}Spawning Claude as ${BOLD}${agent_display_name}${RESET}${BLUE}...${RESET}"
|
|
407
|
+
echo -e "${BLUE}Agent: --agent ${agent_cli_name} | Task list: ${TASK_LIST_ID}${RESET}"
|
|
274
408
|
|
|
275
409
|
# Capture output and exit code
|
|
276
410
|
# CLAUDE_CODE_TASK_LIST_ID enables cross-session task coordination
|
|
411
|
+
# --agent routes to the specialist selected by boot sequence
|
|
277
412
|
set +e
|
|
278
|
-
output=$(CLAUDE_CODE_TASK_LIST_ID="$TASK_LIST_ID" claude --agent
|
|
413
|
+
output=$(CLAUDE_CODE_TASK_LIST_ID="$TASK_LIST_ID" claude --agent "$agent_cli_name" --output-format stream-json 2>&1 <<< "$full_prompt")
|
|
279
414
|
exit_code=$?
|
|
280
415
|
set -e
|
|
281
416
|
|
|
@@ -288,30 +423,110 @@ run_iteration() {
|
|
|
288
423
|
|
|
289
424
|
# Parse output for signals
|
|
290
425
|
if echo "$output" | grep -q "EXIT_SIGNAL"; then
|
|
426
|
+
# Run session end protocol before completing
|
|
427
|
+
if type session_end_protocol &> /dev/null; then
|
|
428
|
+
session_end_protocol "$current_task_desc" "" ""
|
|
429
|
+
fi
|
|
291
430
|
echo -e "\n${GREEN}${BOLD}EXIT_SIGNAL detected - Loop complete!${RESET}"
|
|
292
431
|
return 0
|
|
293
432
|
fi
|
|
294
433
|
|
|
295
|
-
|
|
434
|
+
# Count tasks completed (for pause interval)
|
|
435
|
+
local task_count
|
|
436
|
+
task_count=$(get_state_value "plan.completed_tasks" 2>/dev/null || echo "0")
|
|
437
|
+
|
|
438
|
+
# Check if we should pause based on mode
|
|
439
|
+
if should_pause "$output" "$task_count"; then
|
|
440
|
+
local pause_result
|
|
441
|
+
handle_pause "$output" "$iteration" "$task_count"
|
|
442
|
+
pause_result=$?
|
|
443
|
+
|
|
444
|
+
case $pause_result in
|
|
445
|
+
0) # Continue
|
|
446
|
+
;;
|
|
447
|
+
1) # Quit
|
|
448
|
+
return 0
|
|
449
|
+
;;
|
|
450
|
+
2) # Skip task
|
|
451
|
+
return 2
|
|
452
|
+
;;
|
|
453
|
+
esac
|
|
454
|
+
fi
|
|
455
|
+
|
|
456
|
+
# Legacy checkpoint handling (for backward compatibility)
|
|
457
|
+
if echo "$output" | grep -q "CHECKPOINT_SIGNAL" && [ "$LAST_PAUSE_REASON" != "checkpoint" ]; then
|
|
296
458
|
echo -e "\n${YELLOW}${BOLD}CHECKPOINT_SIGNAL detected - User input needed${RESET}"
|
|
297
459
|
handle_checkpoint "$output"
|
|
298
460
|
return 2 # Continue after checkpoint
|
|
299
461
|
fi
|
|
300
462
|
|
|
301
463
|
if [ $exit_code -ne 0 ]; then
|
|
464
|
+
# TUI: Show task failure
|
|
465
|
+
if type show_task_complete &> /dev/null; then
|
|
466
|
+
show_task_complete "$current_task_desc" "$duration" "error"
|
|
467
|
+
fi
|
|
302
468
|
echo -e "\n${RED}Claude exited with error code: $exit_code${RESET}"
|
|
303
469
|
record_error "$iteration" "$exit_code" "$output"
|
|
304
470
|
return 1
|
|
305
471
|
fi
|
|
306
472
|
|
|
473
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
474
|
+
# MERLIN GUARANTEE: Verify Claude used Sights during iteration
|
|
475
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
476
|
+
local sights_used=true
|
|
477
|
+
if ! echo "$output" | grep -qiE "merlin_get_context|merlin_search|merlin_find_files|SIGHTS|merlin_get_conventions|merlin_check_freshness|merlin_get_brief"; then
|
|
478
|
+
sights_used=false
|
|
479
|
+
echo ""
|
|
480
|
+
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
481
|
+
echo -e "${YELLOW}⚠️ MERLIN GUARANTEE WARNING${RESET}"
|
|
482
|
+
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
483
|
+
echo -e "${YELLOW}Claude did not use Merlin Sights during this iteration.${RESET}"
|
|
484
|
+
echo -e "${YELLOW}Output may lack codebase awareness. Proceeding with caution.${RESET}"
|
|
485
|
+
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
486
|
+
echo ""
|
|
487
|
+
fi
|
|
488
|
+
|
|
307
489
|
# Update state from Claude's output
|
|
308
490
|
parse_and_update_state "$output"
|
|
309
491
|
|
|
492
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
493
|
+
# SESSION END PROTOCOL: Enforced after every iteration
|
|
494
|
+
# Replaces ad-hoc inline calls with the formal 5-step protocol
|
|
495
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
496
|
+
local current_task
|
|
497
|
+
current_task=$(get_state_value "plan.current_task" 2>/dev/null || echo "")
|
|
498
|
+
|
|
499
|
+
if type session_end_protocol &> /dev/null; then
|
|
500
|
+
# Full protocol: check changes → commit → push → record to Sights → checkpoint
|
|
501
|
+
session_end_protocol "$current_task_desc" "$current_task" ""
|
|
502
|
+
else
|
|
503
|
+
# Fallback: ad-hoc calls if session-end.sh didn't load
|
|
504
|
+
if type record_last_commit &> /dev/null; then
|
|
505
|
+
record_last_commit "$current_task" "claude" "loop-agent" &
|
|
506
|
+
fi
|
|
507
|
+
if type save_checkpoint &> /dev/null; then
|
|
508
|
+
local current_phase working_files
|
|
509
|
+
current_phase=$(get_state_value "plan.current_phase" 2>/dev/null || echo "")
|
|
510
|
+
working_files=$(get_state_value "plan.working_files" 2>/dev/null || echo "[]")
|
|
511
|
+
save_checkpoint "Completed iteration $iteration" "$current_phase" "$current_task" "$working_files" "merlin-loop" &
|
|
512
|
+
fi
|
|
513
|
+
fi
|
|
514
|
+
|
|
515
|
+
# Update worker heartbeat with current task
|
|
516
|
+
if type worker_heartbeat &> /dev/null; then
|
|
517
|
+
worker_heartbeat "$current_task" &
|
|
518
|
+
fi
|
|
519
|
+
|
|
310
520
|
# Cloud sync after each iteration
|
|
311
521
|
if [ "$CLOUD_SYNC" = "true" ]; then
|
|
312
522
|
sync_to_cloud_async
|
|
313
523
|
fi
|
|
314
524
|
|
|
525
|
+
# TUI: Show task completion
|
|
526
|
+
if type show_task_complete &> /dev/null; then
|
|
527
|
+
show_task_complete "$current_task_desc" "$duration" "success"
|
|
528
|
+
fi
|
|
529
|
+
|
|
315
530
|
return 2 # Continue loop
|
|
316
531
|
}
|
|
317
532
|
|
|
@@ -336,6 +551,33 @@ $state
|
|
|
336
551
|
\`\`\`
|
|
337
552
|
"
|
|
338
553
|
|
|
554
|
+
# Inject recent changes from Sights (if available)
|
|
555
|
+
if type fetch_recent_changes &> /dev/null; then
|
|
556
|
+
local recent_changes
|
|
557
|
+
recent_changes=$(fetch_recent_changes 2>/dev/null || echo "")
|
|
558
|
+
if [ -n "$recent_changes" ] && [ "$recent_changes" != "# Recent Changes: Not available"* ]; then
|
|
559
|
+
prompt="$prompt
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
$recent_changes
|
|
563
|
+
"
|
|
564
|
+
fi
|
|
565
|
+
fi
|
|
566
|
+
|
|
567
|
+
# Inject boot context (agent guidance, skills, trajectory)
|
|
568
|
+
if type get_boot_context &> /dev/null; then
|
|
569
|
+
local boot_context
|
|
570
|
+
boot_context=$(get_boot_context)
|
|
571
|
+
if [ -n "$boot_context" ]; then
|
|
572
|
+
prompt="$prompt
|
|
573
|
+
|
|
574
|
+
---
|
|
575
|
+
## Boot Context (from Merlin Pro)
|
|
576
|
+
|
|
577
|
+
$boot_context"
|
|
578
|
+
fi
|
|
579
|
+
fi
|
|
580
|
+
|
|
339
581
|
# Inject relevant context from memory
|
|
340
582
|
local context
|
|
341
583
|
context=$(get_relevant_context "$mode")
|
|
@@ -419,7 +661,16 @@ main_loop() {
|
|
|
419
661
|
case $result in
|
|
420
662
|
0)
|
|
421
663
|
# EXIT_SIGNAL - complete
|
|
422
|
-
|
|
664
|
+
# TUI: Show beautiful completion banner
|
|
665
|
+
if type show_loop_complete &> /dev/null; then
|
|
666
|
+
local stats_completed stats_errors
|
|
667
|
+
stats_completed=$(get_state_value "plan.completed_tasks" 2>/dev/null || echo "$iteration")
|
|
668
|
+
stats_errors=$(get_state_value "plan.total_errors" 2>/dev/null || echo "0")
|
|
669
|
+
local total_elapsed=$(($(date +%s) - ${LOOP_START_TIME:-$(date +%s)}))
|
|
670
|
+
show_loop_complete "$stats_completed" "$total_elapsed" "$stats_errors"
|
|
671
|
+
else
|
|
672
|
+
echo -e "\n${GREEN}${BOLD}Loop completed successfully after $iteration iterations${RESET}"
|
|
673
|
+
fi
|
|
423
674
|
if [ "$NOTIFY_ON_COMPLETE" = "true" ]; then
|
|
424
675
|
send_notification "Merlin Loop Complete" "Finished after $iteration iterations"
|
|
425
676
|
fi
|
|
@@ -444,6 +695,14 @@ main_loop() {
|
|
|
444
695
|
fi
|
|
445
696
|
done
|
|
446
697
|
|
|
698
|
+
# TUI: Show completion even on max iterations
|
|
699
|
+
if type show_loop_complete &> /dev/null; then
|
|
700
|
+
local stats_completed stats_errors
|
|
701
|
+
stats_completed=$(get_state_value "plan.completed_tasks" 2>/dev/null || echo "0")
|
|
702
|
+
stats_errors=$(get_state_value "plan.total_errors" 2>/dev/null || echo "0")
|
|
703
|
+
local total_elapsed=$(($(date +%s) - ${LOOP_START_TIME:-$(date +%s)}))
|
|
704
|
+
show_loop_complete "$stats_completed" "$total_elapsed" "$stats_errors"
|
|
705
|
+
fi
|
|
447
706
|
echo -e "\n${YELLOW}${BOLD}Max iterations ($MAX_ITERATIONS) reached${RESET}"
|
|
448
707
|
echo "Resume with: merlin-loop resume --max $((MAX_ITERATIONS + 50))"
|
|
449
708
|
show_summary
|
|
@@ -478,22 +737,56 @@ usage() {
|
|
|
478
737
|
echo " reset Reset loop state (keeps history)"
|
|
479
738
|
echo ""
|
|
480
739
|
echo "Options:"
|
|
740
|
+
echo " --mode MODE Loop mode: auto, interactive, hybrid (default: hybrid)"
|
|
741
|
+
echo " auto - Only pause on explicit checkpoints"
|
|
742
|
+
echo " interactive - Pause after every task"
|
|
743
|
+
echo " hybrid - Pause on decisions, low confidence, every N tasks"
|
|
744
|
+
echo " --pause-every N Pause every N tasks in hybrid mode (default: 5)"
|
|
745
|
+
echo " --timeout N Pause timeout in seconds (default: 300 = 5 min)"
|
|
746
|
+
echo " --no-timeout Disable timeout (pauses wait forever)"
|
|
481
747
|
echo " --max N Maximum iterations (default: 50)"
|
|
482
748
|
echo " --cooldown N Seconds between iterations (default: 2)"
|
|
483
749
|
echo " --no-sync Disable cloud sync"
|
|
484
|
-
echo " --afk AFK mode (stricter safety,
|
|
750
|
+
echo " --afk AFK mode (stricter safety, shorter timeout, auto mode)"
|
|
485
751
|
echo " --quiet Minimal output"
|
|
486
752
|
echo ""
|
|
487
753
|
echo "Examples:"
|
|
488
|
-
echo " merlin-loop plan
|
|
489
|
-
echo " merlin-loop build
|
|
490
|
-
echo " merlin-loop --
|
|
491
|
-
echo " merlin-loop --
|
|
754
|
+
echo " merlin-loop plan # Create implementation plan"
|
|
755
|
+
echo " merlin-loop build # Execute plan tasks (hybrid mode)"
|
|
756
|
+
echo " merlin-loop --mode auto build # Fully automated, no pauses"
|
|
757
|
+
echo " merlin-loop --mode interactive build # Pause after every task"
|
|
758
|
+
echo " merlin-loop --max 100 build # Allow up to 100 iterations"
|
|
759
|
+
echo " merlin-loop --afk auto # Run unattended (auto mode)"
|
|
492
760
|
}
|
|
493
761
|
|
|
494
762
|
parse_args() {
|
|
495
763
|
while [[ $# -gt 0 ]]; do
|
|
496
764
|
case $1 in
|
|
765
|
+
--mode)
|
|
766
|
+
if validate_mode "$2"; then
|
|
767
|
+
LOOP_MODE="$2"
|
|
768
|
+
# Interactive mode disables timeout by default
|
|
769
|
+
if [ "$2" = "interactive" ]; then
|
|
770
|
+
TIMEOUT_ENABLED="false"
|
|
771
|
+
fi
|
|
772
|
+
else
|
|
773
|
+
exit 1
|
|
774
|
+
fi
|
|
775
|
+
shift 2
|
|
776
|
+
;;
|
|
777
|
+
--pause-every)
|
|
778
|
+
PAUSE_INTERVAL="$2"
|
|
779
|
+
shift 2
|
|
780
|
+
;;
|
|
781
|
+
--timeout)
|
|
782
|
+
PAUSE_TIMEOUT="$2"
|
|
783
|
+
TIMEOUT_ENABLED="true"
|
|
784
|
+
shift 2
|
|
785
|
+
;;
|
|
786
|
+
--no-timeout)
|
|
787
|
+
TIMEOUT_ENABLED="false"
|
|
788
|
+
shift
|
|
789
|
+
;;
|
|
497
790
|
--max)
|
|
498
791
|
MAX_ITERATIONS="$2"
|
|
499
792
|
shift 2
|
|
@@ -507,9 +800,11 @@ parse_args() {
|
|
|
507
800
|
shift
|
|
508
801
|
;;
|
|
509
802
|
--afk)
|
|
510
|
-
# AFK mode: stricter limits, more safety
|
|
803
|
+
# AFK mode: stricter limits, auto mode, shorter timeout, more safety
|
|
511
804
|
CIRCUIT_BREAKER_THRESHOLD=3
|
|
512
805
|
NOTIFY_ON_COMPLETE="true"
|
|
806
|
+
LOOP_MODE="auto" # AFK means fully automated
|
|
807
|
+
PAUSE_TIMEOUT=120 # Shorter timeout for AFK (2 minutes)
|
|
513
808
|
shift
|
|
514
809
|
;;
|
|
515
810
|
--quiet)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Merlin Loop - Discussion Mode
|
|
2
|
+
|
|
3
|
+
You are in **DISCUSSION MODE** - an advisor helping the user think through problems, not an implementer.
|
|
4
|
+
|
|
5
|
+
## Your Role
|
|
6
|
+
|
|
7
|
+
You are here to:
|
|
8
|
+
- **Listen** and understand what the user is trying to accomplish
|
|
9
|
+
- **Ask clarifying questions** to fully understand the situation
|
|
10
|
+
- **Suggest approaches** when asked (but don't decide for them)
|
|
11
|
+
- **Think through trade-offs** together
|
|
12
|
+
- **Help refine ideas** before implementation resumes
|
|
13
|
+
|
|
14
|
+
## What You Should NOT Do
|
|
15
|
+
|
|
16
|
+
- ❌ Do NOT write or modify code
|
|
17
|
+
- ❌ Do NOT run commands
|
|
18
|
+
- ❌ Do NOT try to implement anything
|
|
19
|
+
- ❌ Do NOT make decisions for the user
|
|
20
|
+
|
|
21
|
+
This is purely a **thinking and talking session**.
|
|
22
|
+
|
|
23
|
+
## Context
|
|
24
|
+
|
|
25
|
+
**Current Task:**
|
|
26
|
+
{{CURRENT_TASK}}
|
|
27
|
+
|
|
28
|
+
**Recent History:**
|
|
29
|
+
{{RECENT_HISTORY}}
|
|
30
|
+
|
|
31
|
+
**Pending Decisions:**
|
|
32
|
+
{{PENDING_DECISIONS}}
|
|
33
|
+
|
|
34
|
+
**Project:**
|
|
35
|
+
{{PROJECT_CONTEXT}}
|
|
36
|
+
|
|
37
|
+
## How to Have a Good Discussion
|
|
38
|
+
|
|
39
|
+
1. **Understand first** - Ask what specifically is confusing or needs thought
|
|
40
|
+
2. **Explore options** - Help think through different approaches
|
|
41
|
+
3. **Surface trade-offs** - What's good/bad about each option
|
|
42
|
+
4. **Let them decide** - Your job is to clarify, not choose
|
|
43
|
+
|
|
44
|
+
## Capturing Decisions
|
|
45
|
+
|
|
46
|
+
When the user makes a decision during our discussion, mark it clearly with the `DECISION:` prefix so it can be captured and used later:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
DECISION: We'll use Option B because it's simpler and we don't need the extra flexibility.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
You can capture multiple decisions:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
DECISION: Database will use PostgreSQL for ACID compliance.
|
|
56
|
+
DECISION: API will be REST-style, not GraphQL - simpler for this use case.
|
|
57
|
+
DECISION: Error messages will be user-friendly, with technical details in logs only.
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Ending the Discussion
|
|
61
|
+
|
|
62
|
+
The user will say one of these to end the discussion:
|
|
63
|
+
- **"done"** or **"continue"** - Return to the loop
|
|
64
|
+
- **"quit"** - Exit the loop entirely
|
|
65
|
+
|
|
66
|
+
When they're ready to end, summarize any decisions made:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
Great discussion! Here's what we decided:
|
|
70
|
+
|
|
71
|
+
DECISION: [summary of decision 1]
|
|
72
|
+
DECISION: [summary of decision 2]
|
|
73
|
+
|
|
74
|
+
Ready to continue with the loop. Say "done" or "continue" to proceed.
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Example Discussion Flow
|
|
78
|
+
|
|
79
|
+
**User:** I'm not sure how to handle the error cases here.
|
|
80
|
+
|
|
81
|
+
**You:** What kind of errors are you seeing? Are these validation errors from user input, or unexpected system errors?
|
|
82
|
+
|
|
83
|
+
**User:** Mostly validation - users entering bad data.
|
|
84
|
+
|
|
85
|
+
**You:** Got it. There are a few approaches:
|
|
86
|
+
1. **Fail fast** - Return error immediately on first invalid field
|
|
87
|
+
2. **Collect all** - Validate everything, return all errors at once
|
|
88
|
+
3. **Progressive** - Validate as they type, show inline errors
|
|
89
|
+
|
|
90
|
+
What matters more to you - developer simplicity or user experience?
|
|
91
|
+
|
|
92
|
+
**User:** User experience. Let's collect all errors.
|
|
93
|
+
|
|
94
|
+
**You:** Makes sense. So we'll validate all fields and return a list of errors.
|
|
95
|
+
|
|
96
|
+
DECISION: Use collect-all validation to show users all errors at once for better UX.
|
|
97
|
+
|
|
98
|
+
Anything else you want to think through?
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
Now, what would you like to discuss?
|