create-merlin-brain 2.3.3 → 2.5.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/CLAUDE.md +38 -0
- package/files/agents/merlin.md +49 -53
- package/files/commands/merlin/route.md +189 -0
- 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,453 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Merlin Loop - Boot Sequence
|
|
4
|
+
# The 5-phase Merlin Pro boot sequence that runs before every task
|
|
5
|
+
# Part of Merlin Pro v1.0
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
9
|
+
# Boot Sequence State
|
|
10
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
11
|
+
|
|
12
|
+
BOOT_CONTEXT_TOKENS=0
|
|
13
|
+
BOOT_SELECTED_AGENT=""
|
|
14
|
+
BOOT_INSTALLED_SKILLS=""
|
|
15
|
+
BOOT_TRAJECTORY=""
|
|
16
|
+
BOOT_RECENT_CHANGES=""
|
|
17
|
+
BOOT_CHECKPOINT_SUMMARY=""
|
|
18
|
+
BOOT_HOT_FILES=""
|
|
19
|
+
BOOT_DECISION_COUNT=0
|
|
20
|
+
|
|
21
|
+
# Colors (ensure they're defined)
|
|
22
|
+
: "${RESET:=\033[0m}"
|
|
23
|
+
: "${BOLD:=\033[1m}"
|
|
24
|
+
: "${DIM:=\033[2m}"
|
|
25
|
+
: "${RED:=\033[31m}"
|
|
26
|
+
: "${GREEN:=\033[32m}"
|
|
27
|
+
: "${YELLOW:=\033[33m}"
|
|
28
|
+
: "${BLUE:=\033[34m}"
|
|
29
|
+
: "${MAGENTA:=\033[35m}"
|
|
30
|
+
: "${CYAN:=\033[36m}"
|
|
31
|
+
|
|
32
|
+
# Spinner frames for animated loading
|
|
33
|
+
BOOT_SPINNER=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
|
|
34
|
+
|
|
35
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
36
|
+
# Boot Animation Helpers
|
|
37
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
38
|
+
|
|
39
|
+
# Animated phase indicator
|
|
40
|
+
boot_phase_start() {
|
|
41
|
+
local phase_num="$1"
|
|
42
|
+
local phase_name="$2"
|
|
43
|
+
printf "${CYAN}[%s/5]${RESET} ${BOLD}%s${RESET}" "$phase_num" "$phase_name"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Phase success with checkmark
|
|
47
|
+
boot_phase_success() {
|
|
48
|
+
local message="$1"
|
|
49
|
+
echo -e "\r${GREEN} ✓${RESET} $message"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Phase warning
|
|
53
|
+
boot_phase_warn() {
|
|
54
|
+
local message="$1"
|
|
55
|
+
echo -e "\r${YELLOW} ⚠${RESET} $message"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Phase skip
|
|
59
|
+
boot_phase_skip() {
|
|
60
|
+
local message="$1"
|
|
61
|
+
echo -e "\r${DIM} ⏭ $message${RESET}"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Quick spinner (single line update)
|
|
65
|
+
boot_spinner() {
|
|
66
|
+
local message="$1"
|
|
67
|
+
local i=0
|
|
68
|
+
printf "\r ${CYAN}%s${RESET} %s" "${BOOT_SPINNER[$i]}" "$message"
|
|
69
|
+
sleep 0.1
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
73
|
+
# Phase 1: Reinforce Sights
|
|
74
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
75
|
+
|
|
76
|
+
boot_phase_1_sights() {
|
|
77
|
+
local task_description="$1"
|
|
78
|
+
|
|
79
|
+
echo ""
|
|
80
|
+
boot_phase_start "1" "Reinforcing Sights..."
|
|
81
|
+
echo ""
|
|
82
|
+
|
|
83
|
+
# Check Sights connection
|
|
84
|
+
if type check_sights_connection &> /dev/null; then
|
|
85
|
+
local status
|
|
86
|
+
status=$(check_sights_connection 2>/dev/null || echo "not_configured")
|
|
87
|
+
|
|
88
|
+
if [ "$status" != "connected" ]; then
|
|
89
|
+
boot_phase_warn "Sights not connected - using local context"
|
|
90
|
+
return 1
|
|
91
|
+
fi
|
|
92
|
+
boot_phase_success "Sights: ${GREEN}Connected${RESET}"
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# Load checkpoint
|
|
96
|
+
if type fetch_checkpoint &> /dev/null; then
|
|
97
|
+
boot_spinner "Loading checkpoint..."
|
|
98
|
+
local checkpoint
|
|
99
|
+
checkpoint=$(fetch_checkpoint 2>/dev/null || echo "")
|
|
100
|
+
if [ -n "$checkpoint" ] && [ "$checkpoint" != "null" ]; then
|
|
101
|
+
BOOT_CHECKPOINT_SUMMARY=$(echo "$checkpoint" | jq -r '.checkpoint.summary // empty' 2>/dev/null || echo "")
|
|
102
|
+
local created_at
|
|
103
|
+
created_at=$(echo "$checkpoint" | jq -r '.checkpoint.createdAt // empty' 2>/dev/null | cut -c1-16 || echo "")
|
|
104
|
+
if [ -n "$BOOT_CHECKPOINT_SUMMARY" ]; then
|
|
105
|
+
boot_phase_success "Checkpoint: ${BOOT_CHECKPOINT_SUMMARY:0:50}"
|
|
106
|
+
BOOT_CONTEXT_TOKENS=$((BOOT_CONTEXT_TOKENS + 200))
|
|
107
|
+
else
|
|
108
|
+
boot_phase_skip "No checkpoint found"
|
|
109
|
+
fi
|
|
110
|
+
else
|
|
111
|
+
boot_phase_skip "No checkpoint found"
|
|
112
|
+
fi
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# Load trajectory
|
|
116
|
+
if type fetch_trajectory &> /dev/null; then
|
|
117
|
+
boot_spinner "Detecting trajectory..."
|
|
118
|
+
BOOT_TRAJECTORY=$(fetch_trajectory 2>/dev/null || echo "")
|
|
119
|
+
if [ -n "$BOOT_TRAJECTORY" ] && [ "$BOOT_TRAJECTORY" != "Not detected" ]; then
|
|
120
|
+
boot_phase_success "Trajectory: ${CYAN}\"$BOOT_TRAJECTORY\"${RESET}"
|
|
121
|
+
BOOT_CONTEXT_TOKENS=$((BOOT_CONTEXT_TOKENS + 50))
|
|
122
|
+
else
|
|
123
|
+
boot_phase_skip "Trajectory not detected yet"
|
|
124
|
+
fi
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# Load recent changes
|
|
128
|
+
if type fetch_recent_changes &> /dev/null; then
|
|
129
|
+
boot_spinner "Loading recent changes..."
|
|
130
|
+
BOOT_RECENT_CHANGES=$(fetch_recent_changes 2>/dev/null || echo "")
|
|
131
|
+
if [ -n "$BOOT_RECENT_CHANGES" ]; then
|
|
132
|
+
# Extract hot files
|
|
133
|
+
local hot_files
|
|
134
|
+
hot_files=$(echo "$BOOT_RECENT_CHANGES" | grep -o '\*\*[^*]*\*\*:' | head -3 | sed 's/\*\*//g' | sed 's/:$//' | tr '\n' ', ' | sed 's/,$//')
|
|
135
|
+
if [ -n "$hot_files" ]; then
|
|
136
|
+
BOOT_HOT_FILES="$hot_files"
|
|
137
|
+
boot_phase_success "Hot files: ${YELLOW}$hot_files${RESET}"
|
|
138
|
+
BOOT_CONTEXT_TOKENS=$((BOOT_CONTEXT_TOKENS + 300))
|
|
139
|
+
else
|
|
140
|
+
boot_phase_skip "No recent commits tracked"
|
|
141
|
+
fi
|
|
142
|
+
else
|
|
143
|
+
boot_phase_skip "Recent changes not available"
|
|
144
|
+
fi
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
# Load recent decisions
|
|
148
|
+
if type get_decisions &> /dev/null; then
|
|
149
|
+
boot_spinner "Loading decisions..."
|
|
150
|
+
local decisions
|
|
151
|
+
decisions=$(get_decisions 5 2>/dev/null || echo "")
|
|
152
|
+
if [ -n "$decisions" ]; then
|
|
153
|
+
BOOT_DECISION_COUNT=$(echo "$decisions" | jq -r 'if type == "object" then (.count // (.decisions | length) // 0) else 0 end' 2>/dev/null || echo "0")
|
|
154
|
+
if [ "$BOOT_DECISION_COUNT" -gt 0 ]; then
|
|
155
|
+
boot_phase_success "Decisions: ${BOOT_DECISION_COUNT} recent decisions loaded"
|
|
156
|
+
BOOT_CONTEXT_TOKENS=$((BOOT_CONTEXT_TOKENS + 100))
|
|
157
|
+
else
|
|
158
|
+
boot_phase_skip "No decisions recorded yet"
|
|
159
|
+
fi
|
|
160
|
+
else
|
|
161
|
+
boot_phase_skip "Decisions not available"
|
|
162
|
+
fi
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
return 0
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
169
|
+
# Phase 2: Analyze Task & Select Agent
|
|
170
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
171
|
+
|
|
172
|
+
boot_phase_2_agent() {
|
|
173
|
+
local task_description="$1"
|
|
174
|
+
|
|
175
|
+
echo ""
|
|
176
|
+
boot_phase_start "2" "Analyzing task..."
|
|
177
|
+
echo ""
|
|
178
|
+
|
|
179
|
+
# Select agent based on task keywords
|
|
180
|
+
if type select_agent_for_task &> /dev/null; then
|
|
181
|
+
BOOT_SELECTED_AGENT=$(select_agent_for_task "$task_description" 2>/dev/null || echo "implementation_dev")
|
|
182
|
+
else
|
|
183
|
+
BOOT_SELECTED_AGENT="implementation_dev"
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
local agent_name
|
|
187
|
+
if type get_agent_display_name &> /dev/null; then
|
|
188
|
+
agent_name=$(get_agent_display_name "$BOOT_SELECTED_AGENT" 2>/dev/null || echo "$BOOT_SELECTED_AGENT")
|
|
189
|
+
else
|
|
190
|
+
agent_name="$BOOT_SELECTED_AGENT"
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
boot_phase_success "Agent: ${BOLD}${MAGENTA}$agent_name${RESET}"
|
|
194
|
+
|
|
195
|
+
# Load agent-specific context
|
|
196
|
+
if type get_agent_context_injection &> /dev/null; then
|
|
197
|
+
local agent_context
|
|
198
|
+
agent_context=$(get_agent_context_injection "$BOOT_SELECTED_AGENT" 2>/dev/null || echo "")
|
|
199
|
+
if [ -n "$agent_context" ]; then
|
|
200
|
+
local context_lines
|
|
201
|
+
context_lines=$(echo "$agent_context" | wc -l | tr -d ' ')
|
|
202
|
+
boot_phase_success "Domain knowledge: ${context_lines} guidelines loaded"
|
|
203
|
+
BOOT_CONTEXT_TOKENS=$((BOOT_CONTEXT_TOKENS + 150))
|
|
204
|
+
fi
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
return 0
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
211
|
+
# Phase 3: Scan Skills Index
|
|
212
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
213
|
+
|
|
214
|
+
boot_phase_3_scan_skills() {
|
|
215
|
+
local task_description="$1"
|
|
216
|
+
|
|
217
|
+
echo ""
|
|
218
|
+
boot_phase_start "3" "Scanning skills index..."
|
|
219
|
+
echo ""
|
|
220
|
+
|
|
221
|
+
# Get required skills for selected agent
|
|
222
|
+
local required_skills=""
|
|
223
|
+
if type get_agent_required_skills &> /dev/null; then
|
|
224
|
+
required_skills=$(get_agent_required_skills "$BOOT_SELECTED_AGENT" 2>/dev/null || echo "")
|
|
225
|
+
fi
|
|
226
|
+
|
|
227
|
+
# Detect additional skills from task keywords
|
|
228
|
+
local detected_skills=""
|
|
229
|
+
if type detect_skills_from_task &> /dev/null; then
|
|
230
|
+
detected_skills=$(detect_skills_from_task "$task_description" 2>/dev/null || echo "")
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
# Combine and deduplicate
|
|
234
|
+
local all_skills
|
|
235
|
+
all_skills=$(echo -e "$required_skills\n$detected_skills" | sort -u | grep -v "^$" || echo "")
|
|
236
|
+
|
|
237
|
+
if [ -n "$all_skills" ]; then
|
|
238
|
+
local skill_count
|
|
239
|
+
skill_count=$(echo "$all_skills" | wc -l | tr -d ' ')
|
|
240
|
+
boot_phase_success "Found ${skill_count} relevant skill(s)"
|
|
241
|
+
|
|
242
|
+
# Check which are installed
|
|
243
|
+
local installed=""
|
|
244
|
+
if type get_installed_skills &> /dev/null; then
|
|
245
|
+
installed=$(get_installed_skills 2>/dev/null || echo "")
|
|
246
|
+
fi
|
|
247
|
+
|
|
248
|
+
# Store for phase 4
|
|
249
|
+
BOOT_NEEDED_SKILLS="$all_skills"
|
|
250
|
+
BOOT_INSTALLED_SKILLS="$installed"
|
|
251
|
+
else
|
|
252
|
+
boot_phase_success "No additional skills needed"
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
return 0
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
259
|
+
# Phase 4: Install Missing Skills
|
|
260
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
261
|
+
|
|
262
|
+
boot_phase_4_install_skills() {
|
|
263
|
+
echo ""
|
|
264
|
+
boot_phase_start "4" "Checking skills..."
|
|
265
|
+
echo ""
|
|
266
|
+
|
|
267
|
+
if [ -z "$BOOT_NEEDED_SKILLS" ]; then
|
|
268
|
+
boot_phase_success "All skills ready"
|
|
269
|
+
return 0
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
local installed_count=0
|
|
273
|
+
local skipped_count=0
|
|
274
|
+
|
|
275
|
+
while IFS= read -r skill; do
|
|
276
|
+
[ -z "$skill" ] && continue
|
|
277
|
+
|
|
278
|
+
if echo "$BOOT_INSTALLED_SKILLS" | grep -q "^${skill}$"; then
|
|
279
|
+
((skipped_count++))
|
|
280
|
+
else
|
|
281
|
+
# Install the skill
|
|
282
|
+
if type install_skill &> /dev/null && install_skill "$skill" 2>/dev/null; then
|
|
283
|
+
boot_phase_success "Installed: ${GREEN}$skill${RESET}"
|
|
284
|
+
((installed_count++))
|
|
285
|
+
BOOT_CONTEXT_TOKENS=$((BOOT_CONTEXT_TOKENS + 500))
|
|
286
|
+
else
|
|
287
|
+
boot_phase_skip "$skill (not available)"
|
|
288
|
+
((skipped_count++))
|
|
289
|
+
fi
|
|
290
|
+
fi
|
|
291
|
+
done <<< "$BOOT_NEEDED_SKILLS"
|
|
292
|
+
|
|
293
|
+
if [ $installed_count -eq 0 ]; then
|
|
294
|
+
boot_phase_success "All ${skipped_count} skill(s) ready"
|
|
295
|
+
fi
|
|
296
|
+
|
|
297
|
+
return 0
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
301
|
+
# Phase 5: Start Work
|
|
302
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
303
|
+
|
|
304
|
+
boot_phase_5_ready() {
|
|
305
|
+
local task_description="$1"
|
|
306
|
+
|
|
307
|
+
echo ""
|
|
308
|
+
boot_phase_start "5" "Boot complete!"
|
|
309
|
+
echo ""
|
|
310
|
+
|
|
311
|
+
# Show summary box
|
|
312
|
+
echo -e " ${CYAN}┌──────────────────────────────────────────────────────┐${RESET}"
|
|
313
|
+
printf " ${CYAN}│${RESET} %-52s ${CYAN}│${RESET}\n" "Context: ~${BOOT_CONTEXT_TOKENS} tokens loaded"
|
|
314
|
+
|
|
315
|
+
local agent_display
|
|
316
|
+
if type get_agent_display_name &> /dev/null; then
|
|
317
|
+
agent_display=$(get_agent_display_name "$BOOT_SELECTED_AGENT" 2>/dev/null || echo "$BOOT_SELECTED_AGENT")
|
|
318
|
+
else
|
|
319
|
+
agent_display="$BOOT_SELECTED_AGENT"
|
|
320
|
+
fi
|
|
321
|
+
printf " ${CYAN}│${RESET} %-52s ${CYAN}│${RESET}\n" "Agent: $agent_display"
|
|
322
|
+
|
|
323
|
+
if [ -n "$BOOT_TRAJECTORY" ] && [ "$BOOT_TRAJECTORY" != "Not detected" ]; then
|
|
324
|
+
printf " ${CYAN}│${RESET} %-52s ${CYAN}│${RESET}\n" "Trajectory: ${BOOT_TRAJECTORY:0:40}"
|
|
325
|
+
fi
|
|
326
|
+
|
|
327
|
+
if [ -n "$BOOT_HOT_FILES" ]; then
|
|
328
|
+
printf " ${CYAN}│${RESET} %-52s ${CYAN}│${RESET}\n" "Hot files: ${BOOT_HOT_FILES:0:40}"
|
|
329
|
+
fi
|
|
330
|
+
|
|
331
|
+
if [ "$BOOT_DECISION_COUNT" -gt 0 ]; then
|
|
332
|
+
printf " ${CYAN}│${RESET} %-52s ${CYAN}│${RESET}\n" "Decisions: $BOOT_DECISION_COUNT loaded"
|
|
333
|
+
fi
|
|
334
|
+
|
|
335
|
+
echo -e " ${CYAN}└──────────────────────────────────────────────────────┘${RESET}"
|
|
336
|
+
|
|
337
|
+
return 0
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
341
|
+
# Main Boot Sequence
|
|
342
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
343
|
+
|
|
344
|
+
run_boot_sequence() {
|
|
345
|
+
local task_description="${1:-Unknown task}"
|
|
346
|
+
local quiet="${2:-false}"
|
|
347
|
+
|
|
348
|
+
# Reset state
|
|
349
|
+
BOOT_CONTEXT_TOKENS=0
|
|
350
|
+
BOOT_SELECTED_AGENT=""
|
|
351
|
+
BOOT_INSTALLED_SKILLS=""
|
|
352
|
+
BOOT_TRAJECTORY=""
|
|
353
|
+
BOOT_RECENT_CHANGES=""
|
|
354
|
+
BOOT_NEEDED_SKILLS=""
|
|
355
|
+
BOOT_CHECKPOINT_SUMMARY=""
|
|
356
|
+
BOOT_HOT_FILES=""
|
|
357
|
+
BOOT_DECISION_COUNT=0
|
|
358
|
+
|
|
359
|
+
if [ "$quiet" != "true" ]; then
|
|
360
|
+
echo ""
|
|
361
|
+
echo -e "${MAGENTA}╔═══════════════════════════════════════════════════════════════════╗${RESET}"
|
|
362
|
+
echo -e "${MAGENTA}║${RESET} ${BOLD}🚀 MERLIN PRO BOOT SEQUENCE${RESET} ${MAGENTA}║${RESET}"
|
|
363
|
+
echo -e "${MAGENTA}╚═══════════════════════════════════════════════════════════════════╝${RESET}"
|
|
364
|
+
fi
|
|
365
|
+
|
|
366
|
+
# Run all 5 phases
|
|
367
|
+
boot_phase_1_sights "$task_description"
|
|
368
|
+
boot_phase_2_agent "$task_description"
|
|
369
|
+
boot_phase_3_scan_skills "$task_description"
|
|
370
|
+
boot_phase_4_install_skills
|
|
371
|
+
boot_phase_5_ready "$task_description"
|
|
372
|
+
|
|
373
|
+
if [ "$quiet" != "true" ]; then
|
|
374
|
+
echo ""
|
|
375
|
+
echo -e "${GREEN}╔═══════════════════════════════════════════════════════════════════╗${RESET}"
|
|
376
|
+
printf "${GREEN}║${RESET} %-65s ${GREEN}║${RESET}\n" "▶️ Starting: ${task_description:0:50}"
|
|
377
|
+
echo -e "${GREEN}╚═══════════════════════════════════════════════════════════════════╝${RESET}"
|
|
378
|
+
echo ""
|
|
379
|
+
fi
|
|
380
|
+
|
|
381
|
+
return 0
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
385
|
+
# Quick Boot (minimal output)
|
|
386
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
387
|
+
|
|
388
|
+
run_boot_quick() {
|
|
389
|
+
local task_description="${1:-Unknown task}"
|
|
390
|
+
|
|
391
|
+
# Reset state
|
|
392
|
+
BOOT_CONTEXT_TOKENS=0
|
|
393
|
+
BOOT_SELECTED_AGENT=""
|
|
394
|
+
BOOT_TRAJECTORY=""
|
|
395
|
+
|
|
396
|
+
# Quick loads (no output)
|
|
397
|
+
if type fetch_trajectory &> /dev/null; then
|
|
398
|
+
BOOT_TRAJECTORY=$(fetch_trajectory 2>/dev/null || echo "")
|
|
399
|
+
fi
|
|
400
|
+
|
|
401
|
+
if type select_agent_for_task &> /dev/null; then
|
|
402
|
+
BOOT_SELECTED_AGENT=$(select_agent_for_task "$task_description" 2>/dev/null || echo "implementation_dev")
|
|
403
|
+
fi
|
|
404
|
+
|
|
405
|
+
BOOT_CONTEXT_TOKENS=500 # Estimate
|
|
406
|
+
|
|
407
|
+
echo -e "${GREEN}🚀${RESET} Boot complete (quick mode) - Agent: $BOOT_SELECTED_AGENT"
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
411
|
+
# Helper: Get boot context for prompt injection
|
|
412
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
413
|
+
|
|
414
|
+
get_boot_context() {
|
|
415
|
+
local context=""
|
|
416
|
+
|
|
417
|
+
# Add trajectory
|
|
418
|
+
if [ -n "$BOOT_TRAJECTORY" ] && [ "$BOOT_TRAJECTORY" != "Not detected" ]; then
|
|
419
|
+
context="**Current Trajectory:** $BOOT_TRAJECTORY
|
|
420
|
+
|
|
421
|
+
"
|
|
422
|
+
fi
|
|
423
|
+
|
|
424
|
+
# Add recent changes
|
|
425
|
+
if [ -n "$BOOT_RECENT_CHANGES" ]; then
|
|
426
|
+
context="${context}${BOOT_RECENT_CHANGES}
|
|
427
|
+
|
|
428
|
+
"
|
|
429
|
+
fi
|
|
430
|
+
|
|
431
|
+
# Add agent context
|
|
432
|
+
if [ -n "$BOOT_SELECTED_AGENT" ] && type get_agent_context_injection &> /dev/null; then
|
|
433
|
+
local agent_context
|
|
434
|
+
agent_context=$(get_agent_context_injection "$BOOT_SELECTED_AGENT" 2>/dev/null || echo "")
|
|
435
|
+
if [ -n "$agent_context" ]; then
|
|
436
|
+
context="${context}## Agent Guidance ($BOOT_SELECTED_AGENT)
|
|
437
|
+
|
|
438
|
+
$agent_context
|
|
439
|
+
|
|
440
|
+
"
|
|
441
|
+
fi
|
|
442
|
+
fi
|
|
443
|
+
|
|
444
|
+
echo "$context"
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
448
|
+
# Exports
|
|
449
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
450
|
+
|
|
451
|
+
export -f run_boot_sequence 2>/dev/null || true
|
|
452
|
+
export -f run_boot_quick 2>/dev/null || true
|
|
453
|
+
export -f get_boot_context 2>/dev/null || true
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Merlin Loop - Discussion Mode
|
|
4
|
+
# Spawns a fresh Claude session for conversation without losing loop state
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
8
|
+
# Discussion State
|
|
9
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
10
|
+
|
|
11
|
+
DISCUSSION_DECISIONS=()
|
|
12
|
+
DISCUSSION_CONTEXT=""
|
|
13
|
+
|
|
14
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
15
|
+
# Context Building
|
|
16
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
17
|
+
|
|
18
|
+
# Build a minimal context summary for discussion mode
|
|
19
|
+
# Keeps under 2000 tokens for lightweight conversation
|
|
20
|
+
build_discussion_context() {
|
|
21
|
+
local output="$1"
|
|
22
|
+
local iteration="$2"
|
|
23
|
+
|
|
24
|
+
local context=""
|
|
25
|
+
|
|
26
|
+
# Current task/situation
|
|
27
|
+
context+="## Current Situation\n\n"
|
|
28
|
+
context+="Loop iteration: $iteration\n"
|
|
29
|
+
|
|
30
|
+
# Extract what we're working on from the output
|
|
31
|
+
if echo "$output" | grep -q "DECISION_NEEDED"; then
|
|
32
|
+
local decision_line
|
|
33
|
+
decision_line=$(echo "$output" | grep -o "DECISION_NEEDED|[^|]*|[^|]*" | head -1)
|
|
34
|
+
local reason
|
|
35
|
+
reason=$(echo "$decision_line" | cut -d'|' -f2)
|
|
36
|
+
local details
|
|
37
|
+
details=$(echo "$decision_line" | cut -d'|' -f3)
|
|
38
|
+
context+="Decision needed: $reason\n"
|
|
39
|
+
context+="Details: $details\n"
|
|
40
|
+
elif echo "$output" | grep -q "LOW_CONFIDENCE"; then
|
|
41
|
+
local confidence_line
|
|
42
|
+
confidence_line=$(echo "$output" | grep -o "LOW_CONFIDENCE|[^|]*|[^|]*" | head -1)
|
|
43
|
+
local reason
|
|
44
|
+
reason=$(echo "$confidence_line" | cut -d'|' -f2)
|
|
45
|
+
local details
|
|
46
|
+
details=$(echo "$confidence_line" | cut -d'|' -f3)
|
|
47
|
+
context+="Uncertainty: $reason\n"
|
|
48
|
+
context+="Details: $details\n"
|
|
49
|
+
fi
|
|
50
|
+
context+="\n"
|
|
51
|
+
|
|
52
|
+
# Recent history (last 3 tasks)
|
|
53
|
+
context+="## Recent Progress\n\n"
|
|
54
|
+
if [ -f "$HISTORY_FILE" ]; then
|
|
55
|
+
local recent_tasks
|
|
56
|
+
recent_tasks=$(grep '"type":"task_complete"' "$HISTORY_FILE" | tail -3)
|
|
57
|
+
if [ -n "$recent_tasks" ]; then
|
|
58
|
+
echo "$recent_tasks" | while read -r line; do
|
|
59
|
+
if command -v jq &> /dev/null; then
|
|
60
|
+
local task_name
|
|
61
|
+
task_name=$(echo "$line" | jq -r '.task_name // "Unknown task"')
|
|
62
|
+
context+="- ✓ $task_name\n"
|
|
63
|
+
fi
|
|
64
|
+
done
|
|
65
|
+
else
|
|
66
|
+
context+="- No tasks completed yet\n"
|
|
67
|
+
fi
|
|
68
|
+
fi
|
|
69
|
+
context+="\n"
|
|
70
|
+
|
|
71
|
+
# Any pending decisions from previous discussions
|
|
72
|
+
if [ ${#DISCUSSION_DECISIONS[@]} -gt 0 ]; then
|
|
73
|
+
context+="## Previous Decisions (this session)\n\n"
|
|
74
|
+
for decision in "${DISCUSSION_DECISIONS[@]}"; do
|
|
75
|
+
context+="- $decision\n"
|
|
76
|
+
done
|
|
77
|
+
context+="\n"
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# Project summary if available
|
|
81
|
+
if [ -f ".planning/PROJECT.md" ]; then
|
|
82
|
+
context+="## Project\n\n"
|
|
83
|
+
context+=$(head -20 ".planning/PROJECT.md" | grep -v "^#" | head -5)
|
|
84
|
+
context+="\n"
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
DISCUSSION_CONTEXT="$context"
|
|
88
|
+
echo -e "$context"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
92
|
+
# Discussion Spawning
|
|
93
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
94
|
+
|
|
95
|
+
# Spawn a fresh Claude session for discussion
|
|
96
|
+
# The user can chat, think through problems, and make decisions
|
|
97
|
+
spawn_discussion() {
|
|
98
|
+
local output="$1"
|
|
99
|
+
local iteration="$2"
|
|
100
|
+
|
|
101
|
+
echo ""
|
|
102
|
+
echo -e "${CYAN}╔═══════════════════════════════════════════════════════════════╗${RESET}"
|
|
103
|
+
echo -e "${CYAN}║ ${BOLD}DISCUSSION MODE${RESET}${CYAN} ║${RESET}"
|
|
104
|
+
echo -e "${CYAN}║ Fresh Claude for conversation (no code changes) ║${RESET}"
|
|
105
|
+
echo -e "${CYAN}╚═══════════════════════════════════════════════════════════════╝${RESET}"
|
|
106
|
+
echo ""
|
|
107
|
+
|
|
108
|
+
# Build context
|
|
109
|
+
local context
|
|
110
|
+
context=$(build_discussion_context "$output" "$iteration")
|
|
111
|
+
|
|
112
|
+
# Load discussion prompt
|
|
113
|
+
local prompt_file="$SCRIPT_DIR/prompts/PROMPT_DISCUSS.md"
|
|
114
|
+
if [ ! -f "$prompt_file" ]; then
|
|
115
|
+
echo -e "${YELLOW}Discussion prompt not found, using default${RESET}"
|
|
116
|
+
prompt_file=""
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# Build the full prompt
|
|
120
|
+
local full_prompt=""
|
|
121
|
+
if [ -n "$prompt_file" ]; then
|
|
122
|
+
full_prompt=$(cat "$prompt_file")
|
|
123
|
+
else
|
|
124
|
+
full_prompt="You are in DISCUSSION MODE - an advisor helping think through problems.
|
|
125
|
+
Do NOT make code changes. Help the user think, ask questions, suggest approaches.
|
|
126
|
+
When the user makes a decision, mark it with DECISION: prefix.
|
|
127
|
+
When done, the user will say 'done' or 'continue'."
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
# Inject context
|
|
131
|
+
full_prompt=$(echo "$full_prompt" | sed "s/{{CURRENT_TASK}}/$(echo "$context" | head -5 | tr '\n' ' ')/g")
|
|
132
|
+
full_prompt=$(echo "$full_prompt" | sed "s/{{RECENT_HISTORY}}/See context below/g")
|
|
133
|
+
full_prompt=$(echo "$full_prompt" | sed "s/{{PENDING_DECISIONS}}/None/g")
|
|
134
|
+
full_prompt=$(echo "$full_prompt" | sed "s/{{PROJECT_CONTEXT}}/See context below/g")
|
|
135
|
+
|
|
136
|
+
full_prompt+="
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
## Context
|
|
140
|
+
|
|
141
|
+
$context
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
I'm ready to discuss. What's on your mind?"
|
|
145
|
+
|
|
146
|
+
echo -e "${BLUE}Starting discussion session...${RESET}"
|
|
147
|
+
echo -e "${BLUE}Type 'done' or 'continue' when ready to return to the loop.${RESET}"
|
|
148
|
+
echo ""
|
|
149
|
+
|
|
150
|
+
# Spawn Claude in interactive mode (not stream-json)
|
|
151
|
+
local discussion_output
|
|
152
|
+
local ai_cli
|
|
153
|
+
ai_cli=$(detect_ai_cli 2>/dev/null || echo "claude")
|
|
154
|
+
|
|
155
|
+
# Run interactively - this blocks until user ends discussion
|
|
156
|
+
case "$ai_cli" in
|
|
157
|
+
claude)
|
|
158
|
+
# Use interactive mode without --output-format
|
|
159
|
+
discussion_output=$(echo "$full_prompt" | claude 2>&1)
|
|
160
|
+
;;
|
|
161
|
+
*)
|
|
162
|
+
discussion_output=$(echo "$full_prompt" | "$ai_cli" 2>&1)
|
|
163
|
+
;;
|
|
164
|
+
esac
|
|
165
|
+
|
|
166
|
+
# Process decisions from the discussion
|
|
167
|
+
save_discussion_decisions "$discussion_output"
|
|
168
|
+
|
|
169
|
+
echo ""
|
|
170
|
+
echo -e "${GREEN}Discussion complete.${RESET}"
|
|
171
|
+
|
|
172
|
+
local decision_count=${#DISCUSSION_DECISIONS[@]}
|
|
173
|
+
if [ "$decision_count" -gt 0 ]; then
|
|
174
|
+
echo -e "${GREEN}Captured $decision_count decision(s) for future context.${RESET}"
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
echo ""
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
181
|
+
# Decision Capture
|
|
182
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
183
|
+
|
|
184
|
+
# Parse Claude's output for DECISION: markers and save them
|
|
185
|
+
save_discussion_decisions() {
|
|
186
|
+
local output="$1"
|
|
187
|
+
|
|
188
|
+
# Extract all DECISION: lines
|
|
189
|
+
while IFS= read -r line; do
|
|
190
|
+
local decision
|
|
191
|
+
decision=$(echo "$line" | sed 's/DECISION://')
|
|
192
|
+
decision=$(echo "$decision" | xargs) # Trim whitespace
|
|
193
|
+
|
|
194
|
+
if [ -n "$decision" ]; then
|
|
195
|
+
DISCUSSION_DECISIONS+=("$decision")
|
|
196
|
+
|
|
197
|
+
# Also save to state file for persistence
|
|
198
|
+
if [ -f "$STATE_FILE" ] && command -v jq &> /dev/null; then
|
|
199
|
+
update_state_json ".memory.decisions += [\"[Discussion] $decision\"]"
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
# Log to history
|
|
203
|
+
local now
|
|
204
|
+
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
205
|
+
echo "{\"type\":\"discussion_decision\",\"decision\":\"$decision\",\"timestamp\":\"$now\"}" >> "$HISTORY_FILE"
|
|
206
|
+
fi
|
|
207
|
+
done < <(echo "$output" | grep "^DECISION:" || echo "$output" | grep "DECISION:")
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
# Get decisions made in discussions (for injection into next task)
|
|
211
|
+
get_discussion_decisions() {
|
|
212
|
+
if [ ${#DISCUSSION_DECISIONS[@]} -gt 0 ]; then
|
|
213
|
+
echo "## Decisions from Discussion"
|
|
214
|
+
echo ""
|
|
215
|
+
for decision in "${DISCUSSION_DECISIONS[@]}"; do
|
|
216
|
+
echo "- $decision"
|
|
217
|
+
done
|
|
218
|
+
fi
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
# Clear discussion decisions (after they've been applied)
|
|
222
|
+
clear_discussion_decisions() {
|
|
223
|
+
DISCUSSION_DECISIONS=()
|
|
224
|
+
}
|