create-merlin-brain 2.3.3 → 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/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
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Merlin Loop - Mode Management
|
|
4
|
+
# Handles automated, interactive, and hybrid loop modes
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
# Load discussion mode if available
|
|
8
|
+
if [ -f "$SCRIPT_DIR/lib/discuss.sh" ]; then
|
|
9
|
+
source "$SCRIPT_DIR/lib/discuss.sh"
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
# Load timeout if available
|
|
13
|
+
if [ -f "$SCRIPT_DIR/lib/timeout.sh" ]; then
|
|
14
|
+
source "$SCRIPT_DIR/lib/timeout.sh"
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
18
|
+
# Mode Constants
|
|
19
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
20
|
+
|
|
21
|
+
MODE_AUTO="auto"
|
|
22
|
+
MODE_INTERACTIVE="interactive"
|
|
23
|
+
MODE_HYBRID="hybrid"
|
|
24
|
+
|
|
25
|
+
# Default mode
|
|
26
|
+
LOOP_MODE="${LOOP_MODE:-$MODE_HYBRID}"
|
|
27
|
+
|
|
28
|
+
# Hybrid mode settings
|
|
29
|
+
PAUSE_INTERVAL="${MERLIN_PAUSE_INTERVAL:-5}" # Pause every N tasks in hybrid mode
|
|
30
|
+
|
|
31
|
+
# Pause reason tracking
|
|
32
|
+
LAST_PAUSE_REASON=""
|
|
33
|
+
|
|
34
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
35
|
+
# Pause Detection
|
|
36
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
37
|
+
|
|
38
|
+
# Check if we should pause based on mode and signals
|
|
39
|
+
# Args: $1 = output from Claude, $2 = current task count
|
|
40
|
+
# Returns: 0 if should pause, 1 if should continue
|
|
41
|
+
should_pause() {
|
|
42
|
+
local output="$1"
|
|
43
|
+
local task_count="${2:-0}"
|
|
44
|
+
|
|
45
|
+
case "$LOOP_MODE" in
|
|
46
|
+
"$MODE_AUTO")
|
|
47
|
+
# Auto mode: only pause on explicit CHECKPOINT_SIGNAL
|
|
48
|
+
if echo "$output" | grep -q "CHECKPOINT_SIGNAL"; then
|
|
49
|
+
LAST_PAUSE_REASON="checkpoint"
|
|
50
|
+
return 0
|
|
51
|
+
fi
|
|
52
|
+
return 1
|
|
53
|
+
;;
|
|
54
|
+
|
|
55
|
+
"$MODE_INTERACTIVE")
|
|
56
|
+
# Interactive mode: always pause after every task
|
|
57
|
+
LAST_PAUSE_REASON="interactive"
|
|
58
|
+
return 0
|
|
59
|
+
;;
|
|
60
|
+
|
|
61
|
+
"$MODE_HYBRID")
|
|
62
|
+
# Hybrid mode: pause on decisions, low confidence, checkpoints, or every N tasks
|
|
63
|
+
|
|
64
|
+
# Check for decision needed
|
|
65
|
+
if echo "$output" | grep -q "DECISION_NEEDED"; then
|
|
66
|
+
LAST_PAUSE_REASON="decision"
|
|
67
|
+
return 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# Check for low confidence
|
|
71
|
+
if echo "$output" | grep -q "LOW_CONFIDENCE"; then
|
|
72
|
+
LAST_PAUSE_REASON="low_confidence"
|
|
73
|
+
return 0
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# Check for explicit checkpoint
|
|
77
|
+
if echo "$output" | grep -q "CHECKPOINT_SIGNAL"; then
|
|
78
|
+
LAST_PAUSE_REASON="checkpoint"
|
|
79
|
+
return 0
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# Check for periodic pause (every N tasks)
|
|
83
|
+
if [ "$PAUSE_INTERVAL" -gt 0 ] && [ "$task_count" -gt 0 ]; then
|
|
84
|
+
if [ $((task_count % PAUSE_INTERVAL)) -eq 0 ]; then
|
|
85
|
+
LAST_PAUSE_REASON="periodic"
|
|
86
|
+
return 0
|
|
87
|
+
fi
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
return 1
|
|
91
|
+
;;
|
|
92
|
+
|
|
93
|
+
*)
|
|
94
|
+
# Unknown mode, default to continue
|
|
95
|
+
return 1
|
|
96
|
+
;;
|
|
97
|
+
esac
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# Get human-readable pause reason
|
|
101
|
+
get_pause_reason() {
|
|
102
|
+
case "$LAST_PAUSE_REASON" in
|
|
103
|
+
"checkpoint")
|
|
104
|
+
echo "Explicit checkpoint requested"
|
|
105
|
+
;;
|
|
106
|
+
"decision")
|
|
107
|
+
echo "Decision needed - Claude wants your input"
|
|
108
|
+
;;
|
|
109
|
+
"low_confidence")
|
|
110
|
+
echo "Low confidence - Claude is uncertain about approach"
|
|
111
|
+
;;
|
|
112
|
+
"interactive")
|
|
113
|
+
echo "Interactive mode - pausing after every task"
|
|
114
|
+
;;
|
|
115
|
+
"periodic")
|
|
116
|
+
echo "Periodic checkpoint (every $PAUSE_INTERVAL tasks)"
|
|
117
|
+
;;
|
|
118
|
+
*)
|
|
119
|
+
echo "Pause requested"
|
|
120
|
+
;;
|
|
121
|
+
esac
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# Extract signal details from output
|
|
125
|
+
# Format: SIGNAL_NAME|reason|details
|
|
126
|
+
extract_signal_details() {
|
|
127
|
+
local output="$1"
|
|
128
|
+
local signal_type="$2"
|
|
129
|
+
|
|
130
|
+
local signal_line
|
|
131
|
+
signal_line=$(echo "$output" | grep -o "${signal_type}|[^|]*|[^|]*" | head -1)
|
|
132
|
+
|
|
133
|
+
if [ -n "$signal_line" ]; then
|
|
134
|
+
local reason
|
|
135
|
+
reason=$(echo "$signal_line" | cut -d'|' -f2)
|
|
136
|
+
local details
|
|
137
|
+
details=$(echo "$signal_line" | cut -d'|' -f3)
|
|
138
|
+
echo "Reason: $reason"
|
|
139
|
+
[ -n "$details" ] && echo "Details: $details"
|
|
140
|
+
fi
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
144
|
+
# Pause Handler
|
|
145
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
146
|
+
|
|
147
|
+
# Handle a pause point with user interaction
|
|
148
|
+
# Returns: 0 = continue, 1 = quit, 2 = skip task
|
|
149
|
+
handle_pause() {
|
|
150
|
+
local output="$1"
|
|
151
|
+
local iteration="$2"
|
|
152
|
+
local task_count="$3"
|
|
153
|
+
|
|
154
|
+
echo ""
|
|
155
|
+
echo -e "${MAGENTA}╔═══════════════════════════════════════════════════════════════╗${RESET}"
|
|
156
|
+
echo -e "${MAGENTA}║ ${BOLD}PAUSE POINT${RESET}${MAGENTA} ║${RESET}"
|
|
157
|
+
echo -e "${MAGENTA}╚═══════════════════════════════════════════════════════════════╝${RESET}"
|
|
158
|
+
echo ""
|
|
159
|
+
|
|
160
|
+
# Show pause reason
|
|
161
|
+
local reason
|
|
162
|
+
reason=$(get_pause_reason)
|
|
163
|
+
echo -e "${YELLOW}Why paused:${RESET} $reason"
|
|
164
|
+
echo ""
|
|
165
|
+
|
|
166
|
+
# Show signal details if present
|
|
167
|
+
if [ "$LAST_PAUSE_REASON" = "decision" ]; then
|
|
168
|
+
echo -e "${CYAN}Decision details:${RESET}"
|
|
169
|
+
extract_signal_details "$output" "DECISION_NEEDED"
|
|
170
|
+
echo ""
|
|
171
|
+
elif [ "$LAST_PAUSE_REASON" = "low_confidence" ]; then
|
|
172
|
+
echo -e "${CYAN}Uncertainty details:${RESET}"
|
|
173
|
+
extract_signal_details "$output" "LOW_CONFIDENCE"
|
|
174
|
+
echo ""
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
# Show progress
|
|
178
|
+
echo -e "${BLUE}Progress:${RESET} Iteration $iteration, Tasks completed: $task_count"
|
|
179
|
+
echo ""
|
|
180
|
+
|
|
181
|
+
# Show menu
|
|
182
|
+
echo -e "${BOLD}Options:${RESET}"
|
|
183
|
+
echo -e " ${GREEN}[c]${RESET} Continue to next task"
|
|
184
|
+
echo -e " ${CYAN}[d]${RESET} Discuss (spawn fresh Claude for conversation)"
|
|
185
|
+
echo -e " ${YELLOW}[s]${RESET} Skip current task"
|
|
186
|
+
echo -e " ${RED}[q]${RESET} Quit and save state"
|
|
187
|
+
echo ""
|
|
188
|
+
|
|
189
|
+
# Use timeout if available, otherwise standard read
|
|
190
|
+
local response
|
|
191
|
+
if declare -f read_with_timeout &> /dev/null; then
|
|
192
|
+
response=$(read_with_timeout "Your choice [c/d/s/q]: " "c")
|
|
193
|
+
else
|
|
194
|
+
echo -n "Your choice [c/d/s/q]: "
|
|
195
|
+
read -r response
|
|
196
|
+
response="${response:-c}"
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
case "${response,,}" in
|
|
200
|
+
c|continue)
|
|
201
|
+
echo -e "${GREEN}Continuing...${RESET}"
|
|
202
|
+
return 0
|
|
203
|
+
;;
|
|
204
|
+
d|discuss)
|
|
205
|
+
echo -e "${CYAN}Launching discussion mode...${RESET}"
|
|
206
|
+
# Check if discuss.sh is available
|
|
207
|
+
if declare -f spawn_discussion &> /dev/null; then
|
|
208
|
+
spawn_discussion "$output" "$iteration"
|
|
209
|
+
# After discussion, show menu again
|
|
210
|
+
handle_pause "$output" "$iteration" "$task_count"
|
|
211
|
+
return $?
|
|
212
|
+
else
|
|
213
|
+
echo -e "${YELLOW}Discussion mode not available. Continuing...${RESET}"
|
|
214
|
+
return 0
|
|
215
|
+
fi
|
|
216
|
+
;;
|
|
217
|
+
s|skip)
|
|
218
|
+
echo -e "${YELLOW}Skipping current task...${RESET}"
|
|
219
|
+
record_task_skipped "$iteration"
|
|
220
|
+
return 2
|
|
221
|
+
;;
|
|
222
|
+
q|quit)
|
|
223
|
+
echo -e "${RED}Saving state and exiting...${RESET}"
|
|
224
|
+
update_state "status" "paused"
|
|
225
|
+
return 1
|
|
226
|
+
;;
|
|
227
|
+
*)
|
|
228
|
+
echo -e "${YELLOW}Unknown option. Continuing...${RESET}"
|
|
229
|
+
return 0
|
|
230
|
+
;;
|
|
231
|
+
esac
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
# Record that a task was skipped
|
|
235
|
+
record_task_skipped() {
|
|
236
|
+
local iteration="$1"
|
|
237
|
+
local now
|
|
238
|
+
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
239
|
+
|
|
240
|
+
if [ -n "$HISTORY_FILE" ]; then
|
|
241
|
+
echo "{\"type\":\"task_skipped\",\"iteration\":$iteration,\"timestamp\":\"$now\"}" >> "$HISTORY_FILE"
|
|
242
|
+
fi
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
246
|
+
# Mode Display
|
|
247
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
248
|
+
|
|
249
|
+
# Get mode display name with description
|
|
250
|
+
get_mode_display() {
|
|
251
|
+
case "$LOOP_MODE" in
|
|
252
|
+
"$MODE_AUTO")
|
|
253
|
+
echo "Automated (pauses only on explicit checkpoints)"
|
|
254
|
+
;;
|
|
255
|
+
"$MODE_INTERACTIVE")
|
|
256
|
+
echo "Interactive (pauses after every task)"
|
|
257
|
+
;;
|
|
258
|
+
"$MODE_HYBRID")
|
|
259
|
+
echo "Hybrid (pauses on decisions, low confidence, every ${PAUSE_INTERVAL} tasks)"
|
|
260
|
+
;;
|
|
261
|
+
*)
|
|
262
|
+
echo "$LOOP_MODE"
|
|
263
|
+
;;
|
|
264
|
+
esac
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
# Validate mode value
|
|
268
|
+
validate_mode() {
|
|
269
|
+
local mode="$1"
|
|
270
|
+
|
|
271
|
+
case "$mode" in
|
|
272
|
+
"$MODE_AUTO"|"$MODE_INTERACTIVE"|"$MODE_HYBRID")
|
|
273
|
+
return 0
|
|
274
|
+
;;
|
|
275
|
+
*)
|
|
276
|
+
echo -e "${RED}Invalid mode: $mode${RESET}"
|
|
277
|
+
echo "Valid modes: auto, interactive, hybrid"
|
|
278
|
+
return 1
|
|
279
|
+
;;
|
|
280
|
+
esac
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
# Set loop mode with validation
|
|
284
|
+
set_loop_mode() {
|
|
285
|
+
local mode="$1"
|
|
286
|
+
|
|
287
|
+
if validate_mode "$mode"; then
|
|
288
|
+
LOOP_MODE="$mode"
|
|
289
|
+
echo -e "${GREEN}Loop mode set to: $(get_mode_display)${RESET}"
|
|
290
|
+
return 0
|
|
291
|
+
fi
|
|
292
|
+
|
|
293
|
+
return 1
|
|
294
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Merlin Loop - Session End Protocol
|
|
4
|
+
# Enforces commit + sync + checkpoint at every task end
|
|
5
|
+
# Part of Merlin Pro v1.0
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
9
|
+
# Configuration
|
|
10
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
11
|
+
|
|
12
|
+
SESSION_END_AUTO_COMMIT="${MERLIN_AUTO_COMMIT:-true}"
|
|
13
|
+
SESSION_END_AUTO_PUSH="${MERLIN_AUTO_PUSH:-true}"
|
|
14
|
+
SESSION_END_AUTO_CHECKPOINT="${MERLIN_AUTO_CHECKPOINT:-true}"
|
|
15
|
+
SESSION_END_REQUIRE_CLEAN="${MERLIN_REQUIRE_CLEAN:-false}"
|
|
16
|
+
|
|
17
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
18
|
+
# Session End Protocol
|
|
19
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
20
|
+
|
|
21
|
+
# Main session end protocol - call this after every task
|
|
22
|
+
session_end_protocol() {
|
|
23
|
+
local task_description="${1:-Task completed}"
|
|
24
|
+
local task_id="${2:-}"
|
|
25
|
+
local files_changed="${3:-}" # Comma-separated list or empty for auto-detect
|
|
26
|
+
|
|
27
|
+
echo ""
|
|
28
|
+
echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
29
|
+
echo -e "${BOLD}📦 SESSION END PROTOCOL${RESET}"
|
|
30
|
+
echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
31
|
+
echo ""
|
|
32
|
+
|
|
33
|
+
local success=true
|
|
34
|
+
local commit_hash=""
|
|
35
|
+
|
|
36
|
+
# Step 1: Check for uncommitted changes
|
|
37
|
+
echo -e "${CYAN}[1/5]${RESET} Checking for changes..."
|
|
38
|
+
local has_changes
|
|
39
|
+
has_changes=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
40
|
+
|
|
41
|
+
if [ "$has_changes" -eq 0 ]; then
|
|
42
|
+
echo -e " ${GREEN}✓${RESET} No uncommitted changes"
|
|
43
|
+
else
|
|
44
|
+
echo -e " ${YELLOW}!${RESET} Found ${has_changes} changed files"
|
|
45
|
+
|
|
46
|
+
# Step 2: Auto-commit if enabled
|
|
47
|
+
if [ "$SESSION_END_AUTO_COMMIT" == "true" ]; then
|
|
48
|
+
echo -e "${CYAN}[2/5]${RESET} Committing changes..."
|
|
49
|
+
|
|
50
|
+
# Stage all changes (or specific files if provided)
|
|
51
|
+
if [ -n "$files_changed" ]; then
|
|
52
|
+
# Stage specific files
|
|
53
|
+
IFS=',' read -ra FILES <<< "$files_changed"
|
|
54
|
+
for file in "${FILES[@]}"; do
|
|
55
|
+
git add "$file" 2>/dev/null || true
|
|
56
|
+
done
|
|
57
|
+
else
|
|
58
|
+
# Stage all changes
|
|
59
|
+
git add -A
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# Create commit message
|
|
63
|
+
local commit_msg="feat: ${task_description}"
|
|
64
|
+
if [ -n "$task_id" ]; then
|
|
65
|
+
commit_msg="${commit_msg}
|
|
66
|
+
|
|
67
|
+
Task: ${task_id}
|
|
68
|
+
Co-Authored-By: Merlin Loop <noreply@merlin.build>"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# Commit
|
|
72
|
+
if git commit -m "$commit_msg" 2>/dev/null; then
|
|
73
|
+
commit_hash=$(git rev-parse HEAD 2>/dev/null)
|
|
74
|
+
echo -e " ${GREEN}✓${RESET} Committed: ${commit_hash:0:7}"
|
|
75
|
+
else
|
|
76
|
+
echo -e " ${YELLOW}⚠${RESET} Commit failed (pre-commit hook or no changes)"
|
|
77
|
+
success=false
|
|
78
|
+
fi
|
|
79
|
+
else
|
|
80
|
+
echo -e "${CYAN}[2/5]${RESET} Auto-commit disabled, skipping"
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# Step 3: Push to remote
|
|
85
|
+
if [ "$SESSION_END_AUTO_PUSH" == "true" ] && [ -n "$commit_hash" ]; then
|
|
86
|
+
echo -e "${CYAN}[3/5]${RESET} Pushing to remote..."
|
|
87
|
+
|
|
88
|
+
if git push 2>/dev/null; then
|
|
89
|
+
echo -e " ${GREEN}✓${RESET} Pushed to remote"
|
|
90
|
+
else
|
|
91
|
+
echo -e " ${YELLOW}⚠${RESET} Push failed (network or permissions)"
|
|
92
|
+
fi
|
|
93
|
+
else
|
|
94
|
+
echo -e "${CYAN}[3/5]${RESET} Push skipped"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# Step 4: Record commit to Sights activity tracking
|
|
98
|
+
echo -e "${CYAN}[4/5]${RESET} Recording to Sights..."
|
|
99
|
+
if [ -n "$commit_hash" ] && type record_last_commit &> /dev/null; then
|
|
100
|
+
if record_last_commit "$task_id" "merlin-loop" "loop-agent"; then
|
|
101
|
+
echo -e " ${GREEN}✓${RESET} Recorded in Sights activity"
|
|
102
|
+
else
|
|
103
|
+
echo -e " ${YELLOW}⚠${RESET} Sights recording failed (API key or connection)"
|
|
104
|
+
fi
|
|
105
|
+
else
|
|
106
|
+
echo -e " ${DIM}Skipped (no commit or Sights not available)${RESET}"
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# Step 5: Save checkpoint
|
|
110
|
+
if [ "$SESSION_END_AUTO_CHECKPOINT" == "true" ]; then
|
|
111
|
+
echo -e "${CYAN}[5/5]${RESET} Saving checkpoint..."
|
|
112
|
+
|
|
113
|
+
if type save_checkpoint &> /dev/null; then
|
|
114
|
+
# Get current task info
|
|
115
|
+
local current_phase=""
|
|
116
|
+
local current_task=""
|
|
117
|
+
|
|
118
|
+
if [ -f "$STATE_FILE" ] && command -v jq &> /dev/null; then
|
|
119
|
+
current_phase=$(jq -r '.current_phase // empty' "$STATE_FILE" 2>/dev/null || echo "")
|
|
120
|
+
current_task=$(jq -r '.current_task // empty' "$STATE_FILE" 2>/dev/null || echo "")
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
# Get files as JSON array
|
|
124
|
+
local files_json="[]"
|
|
125
|
+
if [ -n "$files_changed" ]; then
|
|
126
|
+
files_json=$(echo "$files_changed" | jq -R 'split(",")' 2>/dev/null || echo "[]")
|
|
127
|
+
elif [ -n "$commit_hash" ]; then
|
|
128
|
+
files_json=$(git diff-tree --no-commit-id --name-only -r "$commit_hash" 2>/dev/null | jq -R -s 'split("\n") | map(select(. != ""))' || echo "[]")
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
if save_checkpoint "$task_description" "$current_phase" "$current_task" "$files_json" "merlin-loop"; then
|
|
132
|
+
echo -e " ${GREEN}✓${RESET} Checkpoint saved to cloud"
|
|
133
|
+
else
|
|
134
|
+
echo -e " ${YELLOW}⚠${RESET} Checkpoint save failed"
|
|
135
|
+
fi
|
|
136
|
+
else
|
|
137
|
+
echo -e " ${DIM}Skipped (checkpoint function not available)${RESET}"
|
|
138
|
+
fi
|
|
139
|
+
else
|
|
140
|
+
echo -e "${CYAN}[5/5]${RESET} Auto-checkpoint disabled, skipping"
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Step 6: Release task claim (if held)
|
|
144
|
+
if [ -n "$task_id" ] && type release_task &> /dev/null; then
|
|
145
|
+
release_task "$task_id" 2>/dev/null || true
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
echo ""
|
|
149
|
+
echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
150
|
+
|
|
151
|
+
if [ "$success" == "true" ]; then
|
|
152
|
+
echo -e "${GREEN}✅ Session end protocol complete${RESET}"
|
|
153
|
+
else
|
|
154
|
+
echo -e "${YELLOW}⚠️ Session end protocol completed with warnings${RESET}"
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
158
|
+
echo ""
|
|
159
|
+
|
|
160
|
+
return 0
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
164
|
+
# Quick Protocol (minimal version)
|
|
165
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
166
|
+
|
|
167
|
+
# Quick session end - just commit and checkpoint, no visual output
|
|
168
|
+
session_end_quick() {
|
|
169
|
+
local task_description="${1:-Task completed}"
|
|
170
|
+
local task_id="${2:-}"
|
|
171
|
+
|
|
172
|
+
# Check for changes
|
|
173
|
+
local has_changes
|
|
174
|
+
has_changes=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
175
|
+
|
|
176
|
+
if [ "$has_changes" -gt 0 ] && [ "$SESSION_END_AUTO_COMMIT" == "true" ]; then
|
|
177
|
+
git add -A
|
|
178
|
+
git commit -m "feat: ${task_description}" --quiet 2>/dev/null || true
|
|
179
|
+
git push --quiet 2>/dev/null || true
|
|
180
|
+
record_last_commit "$task_id" "merlin-loop" "loop-agent" 2>/dev/null || true
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
if [ "$SESSION_END_AUTO_CHECKPOINT" == "true" ] && type save_checkpoint &> /dev/null; then
|
|
184
|
+
save_checkpoint "$task_description" "" "" "[]" "merlin-loop" 2>/dev/null || true
|
|
185
|
+
fi
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
189
|
+
# Validation
|
|
190
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
191
|
+
|
|
192
|
+
# Check if work tree is clean (for strict mode)
|
|
193
|
+
check_clean_worktree() {
|
|
194
|
+
local has_changes
|
|
195
|
+
has_changes=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
196
|
+
|
|
197
|
+
if [ "$has_changes" -eq 0 ]; then
|
|
198
|
+
return 0
|
|
199
|
+
else
|
|
200
|
+
if [ "$SESSION_END_REQUIRE_CLEAN" == "true" ]; then
|
|
201
|
+
echo -e "${RED}Error: Work tree not clean. Commit or stash changes before continuing.${RESET}"
|
|
202
|
+
return 1
|
|
203
|
+
fi
|
|
204
|
+
return 0
|
|
205
|
+
fi
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
# Verify session end was successful
|
|
209
|
+
verify_session_end() {
|
|
210
|
+
local errors=0
|
|
211
|
+
|
|
212
|
+
# Check if work tree is clean (if required)
|
|
213
|
+
if [ "$SESSION_END_REQUIRE_CLEAN" == "true" ]; then
|
|
214
|
+
local has_changes
|
|
215
|
+
has_changes=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
216
|
+
if [ "$has_changes" -gt 0 ]; then
|
|
217
|
+
echo -e "${YELLOW}Warning: Uncommitted changes remain${RESET}"
|
|
218
|
+
((errors++))
|
|
219
|
+
fi
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
# Check if checkpoint was saved (if enabled)
|
|
223
|
+
if [ "$SESSION_END_AUTO_CHECKPOINT" == "true" ] && type fetch_checkpoint &> /dev/null; then
|
|
224
|
+
local checkpoint
|
|
225
|
+
checkpoint=$(fetch_checkpoint 2>/dev/null || echo "")
|
|
226
|
+
if [ -z "$checkpoint" ]; then
|
|
227
|
+
echo -e "${YELLOW}Warning: No checkpoint found${RESET}"
|
|
228
|
+
((errors++))
|
|
229
|
+
fi
|
|
230
|
+
fi
|
|
231
|
+
|
|
232
|
+
return $errors
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
236
|
+
# Integration Hook
|
|
237
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
238
|
+
|
|
239
|
+
# Hook to call after Claude completes a task
|
|
240
|
+
# Usage: after_task_hook "task description" "task-id" "file1.ts,file2.ts"
|
|
241
|
+
after_task_hook() {
|
|
242
|
+
session_end_protocol "$@"
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# Export for use by other scripts
|
|
246
|
+
export -f session_end_protocol 2>/dev/null || true
|
|
247
|
+
export -f session_end_quick 2>/dev/null || true
|
|
248
|
+
export -f after_task_hook 2>/dev/null || true
|