create-merlin-brain 3.6.0 → 3.6.2
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/blend.sh +731 -0
- package/files/loop/merlin-loop.sh +27 -9
- package/files/loop/merlin-session.sh +177 -34
- package/files/merlin/VERSION +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
4
|
+
# ║ MERLIN BLEND ENGINE - Dynamic Agent Fusion ║
|
|
5
|
+
# ║ Creates a custom-optimized specialist for EVERY task ║
|
|
6
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
7
|
+
#
|
|
8
|
+
# No static routing. No shortcuts. For every single task:
|
|
9
|
+
# 1. Analyze the task — extract ALL relevant domains
|
|
10
|
+
# 2. Score every available agent against the task
|
|
11
|
+
# 3. Pull the top agents' system prompts
|
|
12
|
+
# 4. Blend into a single, coherent custom specialist
|
|
13
|
+
# 5. Inject Sights codebase context
|
|
14
|
+
# 6. Write a temporary agent .md file
|
|
15
|
+
# 7. Return the path — caller spawns with `claude --agent <path>`
|
|
16
|
+
#
|
|
17
|
+
# The blended agent is NOT a concatenation. It's a coherent new identity:
|
|
18
|
+
# - Primary agent: full system prompt (the core expertise)
|
|
19
|
+
# - Secondary agents: extracted guidelines and patterns (supporting expertise)
|
|
20
|
+
# - Sights context: real codebase knowledge (files, patterns, conventions)
|
|
21
|
+
#
|
|
22
|
+
# Used by both merlin-session.sh and merlin-loop.sh.
|
|
23
|
+
#
|
|
24
|
+
|
|
25
|
+
# Colors
|
|
26
|
+
: "${RESET:=\033[0m}"
|
|
27
|
+
: "${BOLD:=\033[1m}"
|
|
28
|
+
: "${DIM:=\033[2m}"
|
|
29
|
+
: "${CYAN:=\033[36m}"
|
|
30
|
+
: "${MAGENTA:=\033[35m}"
|
|
31
|
+
: "${GREEN:=\033[32m}"
|
|
32
|
+
: "${YELLOW:=\033[33m}"
|
|
33
|
+
|
|
34
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
35
|
+
# Agent Registry
|
|
36
|
+
# keyword → agent_name mapping with primary (3pt) and secondary (1pt) keywords
|
|
37
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
38
|
+
|
|
39
|
+
# Agent directories to search (in order of priority)
|
|
40
|
+
AGENTS_DIR="${HOME}/.claude/agents"
|
|
41
|
+
AGENTS_FALLBACK_DIR=""
|
|
42
|
+
|
|
43
|
+
# All agents and their scoring keywords
|
|
44
|
+
# Format: BLEND_<safename>_PRIMARY="word1 word2 ..."
|
|
45
|
+
# Format: BLEND_<safename>_SECONDARY="word3 word4 ..."
|
|
46
|
+
# Format: BLEND_<safename>_FILE="agent-name" (the .md filename without extension)
|
|
47
|
+
# Format: BLEND_<safename>_LABEL="Display Name"
|
|
48
|
+
|
|
49
|
+
BLEND_impl_PRIMARY="implement build create add feature code write develop make"
|
|
50
|
+
BLEND_impl_SECONDARY="function class module service endpoint handler"
|
|
51
|
+
BLEND_impl_FILE="implementation-dev"
|
|
52
|
+
BLEND_impl_LABEL="Implementation Developer"
|
|
53
|
+
|
|
54
|
+
BLEND_security_PRIMARY="security auth login password encrypt token 2fa oauth jwt"
|
|
55
|
+
BLEND_security_SECONDARY="validate sanitize session csrf xss injection rate-limit"
|
|
56
|
+
BLEND_security_FILE="hardening-guard"
|
|
57
|
+
BLEND_security_LABEL="Security Specialist"
|
|
58
|
+
|
|
59
|
+
BLEND_architect_PRIMARY="architect architecture schema model database design service structure"
|
|
60
|
+
BLEND_architect_SECONDARY="migration pattern microservice monolith layer module boundary"
|
|
61
|
+
BLEND_architect_FILE="system-architect"
|
|
62
|
+
BLEND_architect_LABEL="System Architect"
|
|
63
|
+
|
|
64
|
+
BLEND_spec_PRIMARY="plan spec feature idea milestone roadmap requirement user-story"
|
|
65
|
+
BLEND_spec_SECONDARY="scope priority acceptance-criteria mvp"
|
|
66
|
+
BLEND_spec_FILE="product-spec"
|
|
67
|
+
BLEND_spec_LABEL="Product Spec Writer"
|
|
68
|
+
|
|
69
|
+
BLEND_test_PRIMARY="test spec coverage unit integration e2e jest pytest mocha"
|
|
70
|
+
BLEND_test_SECONDARY="mock stub fixture snapshot regression assertion"
|
|
71
|
+
BLEND_test_FILE="tests-qa"
|
|
72
|
+
BLEND_test_LABEL="Testing Specialist"
|
|
73
|
+
|
|
74
|
+
BLEND_debug_PRIMARY="debug fix error bug crash issue trace exception"
|
|
75
|
+
BLEND_debug_SECONDARY="stacktrace log breakpoint reproduce regression"
|
|
76
|
+
BLEND_debug_FILE="merlin-debugger"
|
|
77
|
+
BLEND_debug_LABEL="Debugging Specialist"
|
|
78
|
+
|
|
79
|
+
BLEND_refactor_PRIMARY="refactor cleanup dry organize split extract consolidate"
|
|
80
|
+
BLEND_refactor_SECONDARY="rename simplify reduce dedup modularize"
|
|
81
|
+
BLEND_refactor_FILE="dry-refactor"
|
|
82
|
+
BLEND_refactor_LABEL="Refactoring Specialist"
|
|
83
|
+
|
|
84
|
+
BLEND_frontend_PRIMARY="ui component react vue frontend svelte next nuxt"
|
|
85
|
+
BLEND_frontend_SECONDARY="css layout responsive tailwind shadcn radix form modal"
|
|
86
|
+
BLEND_frontend_FILE="merlin-frontend"
|
|
87
|
+
BLEND_frontend_LABEL="Frontend Specialist"
|
|
88
|
+
|
|
89
|
+
BLEND_api_PRIMARY="api endpoint route rest graphql webhook"
|
|
90
|
+
BLEND_api_SECONDARY="handler controller middleware request response status"
|
|
91
|
+
BLEND_api_FILE="merlin-api-designer"
|
|
92
|
+
BLEND_api_LABEL="API Designer"
|
|
93
|
+
|
|
94
|
+
BLEND_deploy_PRIMARY="deploy infra railway docker env pipeline ci cd"
|
|
95
|
+
BLEND_deploy_SECONDARY="container kubernetes health monitoring"
|
|
96
|
+
BLEND_deploy_FILE="ops-railway"
|
|
97
|
+
BLEND_deploy_LABEL="DevOps Specialist"
|
|
98
|
+
|
|
99
|
+
BLEND_docs_PRIMARY="docs readme documentation comment"
|
|
100
|
+
BLEND_docs_SECONDARY="jsdoc typedoc markdown guide tutorial"
|
|
101
|
+
BLEND_docs_FILE="docs-keeper"
|
|
102
|
+
BLEND_docs_LABEL="Documentation Specialist"
|
|
103
|
+
|
|
104
|
+
BLEND_perf_PRIMARY="performance optimize speed slow memory latency"
|
|
105
|
+
BLEND_perf_SECONDARY="cache profiling bottleneck benchmark bundle"
|
|
106
|
+
BLEND_perf_FILE="merlin-performance"
|
|
107
|
+
BLEND_perf_LABEL="Performance Specialist"
|
|
108
|
+
|
|
109
|
+
BLEND_migrate_PRIMARY="migrate migration schema database alter table column"
|
|
110
|
+
BLEND_migrate_SECONDARY="seed rollback version upgrade"
|
|
111
|
+
BLEND_migrate_FILE="merlin-migrator"
|
|
112
|
+
BLEND_migrate_LABEL="Migration Specialist"
|
|
113
|
+
|
|
114
|
+
BLEND_video_PRIMARY="video animation remotion render composition"
|
|
115
|
+
BLEND_video_SECONDARY="frame sequence transition spring interpolate"
|
|
116
|
+
BLEND_video_FILE="remotion"
|
|
117
|
+
BLEND_video_LABEL="Video/Remotion Specialist"
|
|
118
|
+
|
|
119
|
+
BLEND_secaudit_PRIMARY="audit penetration vulnerability scan"
|
|
120
|
+
BLEND_secaudit_SECONDARY="cve exploit disclosure compliance"
|
|
121
|
+
BLEND_secaudit_FILE="merlin-security"
|
|
122
|
+
BLEND_secaudit_LABEL="Security Auditor"
|
|
123
|
+
|
|
124
|
+
BLEND_codeorg_PRIMARY="organize structure folder directory file-structure"
|
|
125
|
+
BLEND_codeorg_SECONDARY="barrel index module re-export"
|
|
126
|
+
BLEND_codeorg_FILE="code-organization-supervisor"
|
|
127
|
+
BLEND_codeorg_LABEL="Code Organization Specialist"
|
|
128
|
+
|
|
129
|
+
BLEND_review_PRIMARY="review pr pull-request code-review"
|
|
130
|
+
BLEND_review_SECONDARY="feedback comment approve change"
|
|
131
|
+
BLEND_review_FILE="merlin-reviewer"
|
|
132
|
+
BLEND_review_LABEL="Code Reviewer"
|
|
133
|
+
|
|
134
|
+
BLEND_swift_PRIMARY="swift swiftui appkit ios macos iphone"
|
|
135
|
+
BLEND_swift_SECONDARY="ipad xcode cocoa uikit swiftdata coredata swift6"
|
|
136
|
+
BLEND_swift_FILE="apple-swift-expert"
|
|
137
|
+
BLEND_swift_LABEL="Apple Swift Expert"
|
|
138
|
+
|
|
139
|
+
BLEND_android_PRIMARY="android kotlin compose jetpack"
|
|
140
|
+
BLEND_android_SECONDARY="material gradle room hilt viewmodel coroutine play-store aab apk"
|
|
141
|
+
BLEND_android_FILE="android-expert"
|
|
142
|
+
BLEND_android_LABEL="Android Expert"
|
|
143
|
+
|
|
144
|
+
BLEND_uidesign_PRIMARY="design-system accessibility wcag visual-hierarchy ux"
|
|
145
|
+
BLEND_uidesign_SECONDARY="color-palette typography spacing wireframe mockup hig"
|
|
146
|
+
BLEND_uidesign_FILE="ui-designer"
|
|
147
|
+
BLEND_uidesign_LABEL="UI Designer"
|
|
148
|
+
|
|
149
|
+
BLEND_uibuild_PRIMARY="shadcn radix component-library form table"
|
|
150
|
+
BLEND_uibuild_SECONDARY="card modal dialog sheet dropdown popover cva"
|
|
151
|
+
BLEND_uibuild_FILE="ui-builder"
|
|
152
|
+
BLEND_uibuild_LABEL="UI Builder"
|
|
153
|
+
|
|
154
|
+
BLEND_anim_PRIMARY="animation motion framer gsap transition"
|
|
155
|
+
BLEND_anim_SECONDARY="scroll-animation parallax spring keyframe animate lottie"
|
|
156
|
+
BLEND_anim_FILE="animation-expert"
|
|
157
|
+
BLEND_anim_LABEL="Animation Expert"
|
|
158
|
+
|
|
159
|
+
BLEND_marketing_PRIMARY="email campaign drip marketing funnel"
|
|
160
|
+
BLEND_marketing_SECONDARY="newsletter automation conversion ab-test analytics tracking utm landing-page growth"
|
|
161
|
+
BLEND_marketing_FILE="marketing-automation"
|
|
162
|
+
BLEND_marketing_LABEL="Marketing Automation"
|
|
163
|
+
|
|
164
|
+
BLEND_desktop_PRIMARY="electron tauri desktop native ipc"
|
|
165
|
+
BLEND_desktop_SECONDARY="window tray preload menu dmg nsis appimage code-sign notarize"
|
|
166
|
+
BLEND_desktop_FILE="desktop-app-expert"
|
|
167
|
+
BLEND_desktop_LABEL="Desktop App Expert"
|
|
168
|
+
|
|
169
|
+
# All agent keys (order matters — first match tiebreaker)
|
|
170
|
+
# Domain specialists listed first for priority
|
|
171
|
+
BLEND_ALL_AGENTS="swift android desktop uidesign uibuild anim marketing security architect api frontend test debug refactor deploy video migrate perf secaudit codeorg review docs spec impl"
|
|
172
|
+
|
|
173
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
174
|
+
# Scoring Engine
|
|
175
|
+
# Returns scored agents sorted by relevance
|
|
176
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
177
|
+
|
|
178
|
+
# Score a single agent against a task
|
|
179
|
+
# Returns the score (integer)
|
|
180
|
+
_blend_score_agent() {
|
|
181
|
+
local agent_key="$1"
|
|
182
|
+
local task_lower="$2"
|
|
183
|
+
local score=0
|
|
184
|
+
|
|
185
|
+
# Get primary keywords
|
|
186
|
+
local primary_var="BLEND_${agent_key}_PRIMARY"
|
|
187
|
+
local primary="${!primary_var}"
|
|
188
|
+
|
|
189
|
+
# Get secondary keywords
|
|
190
|
+
local secondary_var="BLEND_${agent_key}_SECONDARY"
|
|
191
|
+
local secondary="${!secondary_var}"
|
|
192
|
+
|
|
193
|
+
# Score primary keywords (3 points each)
|
|
194
|
+
for word in $primary; do
|
|
195
|
+
if echo "$task_lower" | grep -qw "$word"; then
|
|
196
|
+
score=$((score + 3))
|
|
197
|
+
fi
|
|
198
|
+
done
|
|
199
|
+
|
|
200
|
+
# Score secondary keywords (1 point each)
|
|
201
|
+
for word in $secondary; do
|
|
202
|
+
if echo "$task_lower" | grep -qw "$word"; then
|
|
203
|
+
score=$((score + 1))
|
|
204
|
+
fi
|
|
205
|
+
done
|
|
206
|
+
|
|
207
|
+
echo "$score"
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
# Score ALL agents against a task
|
|
211
|
+
# Returns: "score:key:label:file" lines sorted by score descending
|
|
212
|
+
blend_score_all() {
|
|
213
|
+
local task="$1"
|
|
214
|
+
local task_lower
|
|
215
|
+
task_lower=$(echo "$task" | tr '[:upper:]' '[:lower:]')
|
|
216
|
+
|
|
217
|
+
local results=""
|
|
218
|
+
|
|
219
|
+
for key in $BLEND_ALL_AGENTS; do
|
|
220
|
+
local score
|
|
221
|
+
score=$(_blend_score_agent "$key" "$task_lower")
|
|
222
|
+
local label_var="BLEND_${key}_LABEL"
|
|
223
|
+
local file_var="BLEND_${key}_FILE"
|
|
224
|
+
local label="${!label_var}"
|
|
225
|
+
local file="${!file_var}"
|
|
226
|
+
|
|
227
|
+
results="${results}${score}:${key}:${label}:${file}\n"
|
|
228
|
+
done
|
|
229
|
+
|
|
230
|
+
# Sort by score descending, return all with score > 0 plus impl as fallback
|
|
231
|
+
echo -e "$results" | sort -t: -k1 -nr | head -20
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
# Get the top N agents for a task (those with score > 0)
|
|
235
|
+
# Returns newline-separated "score:key:label:file" entries
|
|
236
|
+
blend_get_top_agents() {
|
|
237
|
+
local task="$1"
|
|
238
|
+
local max_agents="${2:-4}" # Default: blend up to 4 agents
|
|
239
|
+
|
|
240
|
+
local all_scored
|
|
241
|
+
all_scored=$(blend_score_all "$task")
|
|
242
|
+
|
|
243
|
+
local count=0
|
|
244
|
+
local result=""
|
|
245
|
+
|
|
246
|
+
while IFS=: read -r score key label file; do
|
|
247
|
+
[ -z "$score" ] && continue
|
|
248
|
+
# Always include agents with score > 0
|
|
249
|
+
if [ "$score" -gt 0 ] && [ "$count" -lt "$max_agents" ]; then
|
|
250
|
+
result="${result}${score}:${key}:${label}:${file}\n"
|
|
251
|
+
count=$((count + 1))
|
|
252
|
+
fi
|
|
253
|
+
done <<< "$all_scored"
|
|
254
|
+
|
|
255
|
+
# If nothing scored, use implementation-dev as default
|
|
256
|
+
if [ "$count" -eq 0 ]; then
|
|
257
|
+
result="0:impl:Implementation Developer:implementation-dev\n"
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
echo -e "$result"
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
264
|
+
# Prompt Extraction
|
|
265
|
+
# Read an agent .md file and extract its content (without frontmatter)
|
|
266
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
267
|
+
|
|
268
|
+
# Extract the body (everything after frontmatter --- ... ---) from an agent file
|
|
269
|
+
_blend_extract_body() {
|
|
270
|
+
local agent_file="$1"
|
|
271
|
+
if [ ! -f "$agent_file" ]; then
|
|
272
|
+
echo ""
|
|
273
|
+
return
|
|
274
|
+
fi
|
|
275
|
+
|
|
276
|
+
# Skip YAML frontmatter (between first --- and second ---)
|
|
277
|
+
awk 'BEGIN{skip=0; found_first=0}
|
|
278
|
+
/^---$/ {
|
|
279
|
+
if (!found_first) { found_first=1; skip=1; next }
|
|
280
|
+
else if (skip) { skip=0; next }
|
|
281
|
+
}
|
|
282
|
+
!skip { print }
|
|
283
|
+
' "$agent_file"
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
# Extract a condensed version of an agent (key guidelines and rules only)
|
|
287
|
+
# This is used for secondary agents in the blend — we want their expertise
|
|
288
|
+
# but not their full operational instructions
|
|
289
|
+
_blend_extract_condensed() {
|
|
290
|
+
local agent_file="$1"
|
|
291
|
+
local body
|
|
292
|
+
body=$(_blend_extract_body "$agent_file")
|
|
293
|
+
|
|
294
|
+
if [ -z "$body" ]; then
|
|
295
|
+
echo ""
|
|
296
|
+
return
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
# Strategy: Extract sections that contain guidelines, rules, patterns, principles
|
|
300
|
+
# Skip operational stuff like "When called:", "MERLIN CHECK", "Output format"
|
|
301
|
+
local condensed=""
|
|
302
|
+
local in_relevant_section=false
|
|
303
|
+
local section_lines=0
|
|
304
|
+
local max_section_lines=30 # Cap each section
|
|
305
|
+
|
|
306
|
+
while IFS= read -r line; do
|
|
307
|
+
# Detect section headers
|
|
308
|
+
if echo "$line" | grep -qE '^#{1,3} '; then
|
|
309
|
+
local header_lower
|
|
310
|
+
header_lower=$(echo "$line" | tr '[:upper:]' '[:lower:]')
|
|
311
|
+
|
|
312
|
+
# Relevant sections: anything about guidelines, rules, patterns, expertise
|
|
313
|
+
if echo "$header_lower" | grep -qiE "guideline|rule|pattern|principle|check|security|validation|error|logging|performance|best.practice|convention|architecture|design|testing|approach"; then
|
|
314
|
+
in_relevant_section=true
|
|
315
|
+
section_lines=0
|
|
316
|
+
condensed="${condensed}\n${line}\n"
|
|
317
|
+
continue
|
|
318
|
+
else
|
|
319
|
+
in_relevant_section=false
|
|
320
|
+
fi
|
|
321
|
+
fi
|
|
322
|
+
|
|
323
|
+
# Include lines from relevant sections (capped)
|
|
324
|
+
if [ "$in_relevant_section" = true ] && [ "$section_lines" -lt "$max_section_lines" ]; then
|
|
325
|
+
condensed="${condensed}${line}\n"
|
|
326
|
+
section_lines=$((section_lines + 1))
|
|
327
|
+
fi
|
|
328
|
+
done <<< "$body"
|
|
329
|
+
|
|
330
|
+
# If we couldn't extract sections, take the first ~40 lines as a summary
|
|
331
|
+
if [ -z "$condensed" ]; then
|
|
332
|
+
echo "$body" | head -40
|
|
333
|
+
else
|
|
334
|
+
echo -e "$condensed"
|
|
335
|
+
fi
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
339
|
+
# Sights Context Injection
|
|
340
|
+
# Pull fresh codebase context for the task
|
|
341
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
342
|
+
|
|
343
|
+
_blend_fetch_sights_context() {
|
|
344
|
+
local task="$1"
|
|
345
|
+
local context=""
|
|
346
|
+
|
|
347
|
+
# Try merlin CLI first (fastest)
|
|
348
|
+
if command -v merlin &>/dev/null; then
|
|
349
|
+
local sights_context
|
|
350
|
+
sights_context=$(merlin context "$task" 2>/dev/null || echo "")
|
|
351
|
+
if [ -n "$sights_context" ] && ! echo "$sights_context" | grep -qi "error\|not found\|not connected"; then
|
|
352
|
+
context="$sights_context"
|
|
353
|
+
fi
|
|
354
|
+
|
|
355
|
+
# Also pull relevant files
|
|
356
|
+
local files_context
|
|
357
|
+
files_context=$(merlin files "$task" 2>/dev/null || echo "")
|
|
358
|
+
if [ -n "$files_context" ] && ! echo "$files_context" | grep -qi "error\|not found"; then
|
|
359
|
+
context="${context}\n\n${files_context}"
|
|
360
|
+
fi
|
|
361
|
+
|
|
362
|
+
# Pull conventions
|
|
363
|
+
local conventions
|
|
364
|
+
conventions=$(merlin rules 2>/dev/null || echo "")
|
|
365
|
+
if [ -n "$conventions" ] && ! echo "$conventions" | grep -qi "no coding rules"; then
|
|
366
|
+
context="${context}\n\n## Coding Rules (MUST FOLLOW)\n${conventions}"
|
|
367
|
+
fi
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
echo -e "$context"
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
374
|
+
# Cloud Agent Pull
|
|
375
|
+
# Check if there are specialist agents in Merlin cloud for this domain
|
|
376
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
377
|
+
|
|
378
|
+
_blend_pull_cloud_agents() {
|
|
379
|
+
local task="$1"
|
|
380
|
+
local api_key="${MERLIN_API_KEY:-}"
|
|
381
|
+
local api_url="${MERLIN_API_URL:-https://api.merlin.build}"
|
|
382
|
+
|
|
383
|
+
# Skip if no API key
|
|
384
|
+
[ -z "$api_key" ] && return
|
|
385
|
+
|
|
386
|
+
# Check cloud for relevant agent definitions
|
|
387
|
+
local cloud_response
|
|
388
|
+
cloud_response=$(curl -s --max-time 5 \
|
|
389
|
+
-H "Authorization: Bearer $api_key" \
|
|
390
|
+
"$api_url/api/agents-sync/recommend?task=$(echo "$task" | head -c 100 | sed 's/ /+/g')" 2>/dev/null || echo "")
|
|
391
|
+
|
|
392
|
+
if [ -n "$cloud_response" ] && echo "$cloud_response" | grep -q '"content"'; then
|
|
393
|
+
# Extract agent content and inject
|
|
394
|
+
local agent_content
|
|
395
|
+
agent_content=$(echo "$cloud_response" | python3 -c "
|
|
396
|
+
import sys, json
|
|
397
|
+
try:
|
|
398
|
+
data = json.load(sys.stdin)
|
|
399
|
+
agents = data if isinstance(data, list) else data.get('agents', [])
|
|
400
|
+
for a in agents[:2]: # Max 2 cloud agents
|
|
401
|
+
print(f\"## Cloud Specialist: {a.get('name', 'Unknown')}\")
|
|
402
|
+
print(a.get('content', a.get('instructions', '')))
|
|
403
|
+
print()
|
|
404
|
+
except: pass
|
|
405
|
+
" 2>/dev/null || echo "")
|
|
406
|
+
echo "$agent_content"
|
|
407
|
+
fi
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
411
|
+
# Chain-of-Verification (CoVe) — Selective Self-Verification
|
|
412
|
+
# Based on Meta AI Research. Only injected for high-stakes task types where
|
|
413
|
+
# being wrong has real consequences. Zero overhead for simple tasks.
|
|
414
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
415
|
+
|
|
416
|
+
# Task types that benefit from CoVe verification
|
|
417
|
+
COVE_TASK_TYPES="security architect debug review secaudit migrate"
|
|
418
|
+
|
|
419
|
+
# Check if task warrants CoVe
|
|
420
|
+
_blend_needs_cove() {
|
|
421
|
+
local primary_key="$1"
|
|
422
|
+
for cove_type in $COVE_TASK_TYPES; do
|
|
423
|
+
if [ "$primary_key" = "$cove_type" ]; then
|
|
424
|
+
return 0
|
|
425
|
+
fi
|
|
426
|
+
done
|
|
427
|
+
return 1
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
# Generate the CoVe instruction block
|
|
431
|
+
_blend_cove_block() {
|
|
432
|
+
local task_domain="$1"
|
|
433
|
+
|
|
434
|
+
cat << 'COVE_BLOCK'
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## Reasoning Protocol: Chain-of-Verification (CoVe)
|
|
439
|
+
|
|
440
|
+
This task domain requires high accuracy. Follow this 4-step verification process:
|
|
441
|
+
|
|
442
|
+
### Step 1: Generate Baseline
|
|
443
|
+
Produce your initial solution/analysis.
|
|
444
|
+
|
|
445
|
+
### Step 2: Plan Verification Questions
|
|
446
|
+
Generate 3-5 specific, falsifiable questions that would expose errors in your baseline:
|
|
447
|
+
- Factual claims that could be wrong
|
|
448
|
+
- Assumptions that might not hold
|
|
449
|
+
- Edge cases you might have missed
|
|
450
|
+
- Security or correctness gaps
|
|
451
|
+
|
|
452
|
+
### Step 3: Independent Verification
|
|
453
|
+
Answer each verification question AS IF you had never seen your baseline.
|
|
454
|
+
Do NOT reference or defend your initial answer. Treat each as a standalone query.
|
|
455
|
+
Examine the actual codebase independently for each question.
|
|
456
|
+
|
|
457
|
+
### Step 4: Final Verified Response
|
|
458
|
+
Compare verification answers against your baseline. Where they conflict,
|
|
459
|
+
trust the verification. Produce a corrected final response.
|
|
460
|
+
|
|
461
|
+
CRITICAL: Step 3 must be truly independent. Never justify your baseline — interrogate it.
|
|
462
|
+
COVE_BLOCK
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
466
|
+
# THE BLEND ENGINE
|
|
467
|
+
# This is the core — creates a custom agent for every task
|
|
468
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
469
|
+
|
|
470
|
+
# Main entry point: blend agents for a task
|
|
471
|
+
# Returns: path to the temporary blended agent .md file
|
|
472
|
+
#
|
|
473
|
+
# Usage:
|
|
474
|
+
# agent_path=$(blend_for_task "refactor the Python authentication API")
|
|
475
|
+
# claude --agent "$agent_path" -p "your prompt"
|
|
476
|
+
#
|
|
477
|
+
blend_for_task() {
|
|
478
|
+
local task="$1"
|
|
479
|
+
local session_dir="${2:-/tmp/merlin-blend-$$}"
|
|
480
|
+
|
|
481
|
+
mkdir -p "$session_dir"
|
|
482
|
+
|
|
483
|
+
# 1. Score all agents
|
|
484
|
+
local top_agents
|
|
485
|
+
top_agents=$(blend_get_top_agents "$task" 4)
|
|
486
|
+
|
|
487
|
+
# Parse the top agents
|
|
488
|
+
local primary_file="" primary_label="" primary_score=0
|
|
489
|
+
local secondary_files=() secondary_labels=()
|
|
490
|
+
local agent_count=0
|
|
491
|
+
local all_labels=""
|
|
492
|
+
|
|
493
|
+
while IFS=: read -r score key label file; do
|
|
494
|
+
[ -z "$score" ] && continue
|
|
495
|
+
[ -z "$file" ] && continue
|
|
496
|
+
|
|
497
|
+
agent_count=$((agent_count + 1))
|
|
498
|
+
|
|
499
|
+
if [ $agent_count -eq 1 ]; then
|
|
500
|
+
# Primary agent — full prompt
|
|
501
|
+
primary_file="$file"
|
|
502
|
+
primary_label="$label"
|
|
503
|
+
primary_score="$score"
|
|
504
|
+
all_labels="**${label}** (primary)"
|
|
505
|
+
else
|
|
506
|
+
# Secondary agents — condensed expertise
|
|
507
|
+
secondary_files+=("$file")
|
|
508
|
+
secondary_labels+=("$label")
|
|
509
|
+
all_labels="${all_labels}, ${label}"
|
|
510
|
+
fi
|
|
511
|
+
done <<< "$top_agents"
|
|
512
|
+
|
|
513
|
+
# 2. Read primary agent's full prompt
|
|
514
|
+
local primary_path="${AGENTS_DIR}/${primary_file}.md"
|
|
515
|
+
local primary_body=""
|
|
516
|
+
if [ -f "$primary_path" ]; then
|
|
517
|
+
primary_body=$(_blend_extract_body "$primary_path")
|
|
518
|
+
fi
|
|
519
|
+
|
|
520
|
+
# 3. Read secondary agents' condensed prompts
|
|
521
|
+
local secondary_sections=""
|
|
522
|
+
for i in "${!secondary_files[@]}"; do
|
|
523
|
+
local sec_file="${secondary_files[$i]}"
|
|
524
|
+
local sec_label="${secondary_labels[$i]}"
|
|
525
|
+
local sec_path="${AGENTS_DIR}/${sec_file}.md"
|
|
526
|
+
|
|
527
|
+
if [ -f "$sec_path" ]; then
|
|
528
|
+
local condensed
|
|
529
|
+
condensed=$(_blend_extract_condensed "$sec_path")
|
|
530
|
+
if [ -n "$condensed" ]; then
|
|
531
|
+
secondary_sections="${secondary_sections}\n\n## Additional Expertise: ${sec_label}\n\n${condensed}"
|
|
532
|
+
fi
|
|
533
|
+
fi
|
|
534
|
+
done
|
|
535
|
+
|
|
536
|
+
# 4. Fetch Sights context
|
|
537
|
+
local sights_context=""
|
|
538
|
+
sights_context=$(_blend_fetch_sights_context "$task")
|
|
539
|
+
|
|
540
|
+
# 5. Pull cloud agents (if any)
|
|
541
|
+
local cloud_context=""
|
|
542
|
+
cloud_context=$(_blend_pull_cloud_agents "$task")
|
|
543
|
+
|
|
544
|
+
# 6. Determine the optimal model
|
|
545
|
+
# Complex multi-domain tasks → opus. Simple single-domain → sonnet.
|
|
546
|
+
local model="sonnet"
|
|
547
|
+
if [ "$agent_count" -ge 3 ] || [ "$primary_score" -ge 6 ]; then
|
|
548
|
+
model="sonnet" # Still sonnet for speed, but could be opus for really complex tasks
|
|
549
|
+
fi
|
|
550
|
+
|
|
551
|
+
# 7. Build the blended agent identity
|
|
552
|
+
local blend_name="merlin-blend-$(date +%s)"
|
|
553
|
+
local description="Custom specialist blending: ${all_labels}"
|
|
554
|
+
|
|
555
|
+
# 8. Assemble the blended agent file
|
|
556
|
+
local blend_path="${session_dir}/${blend_name}.md"
|
|
557
|
+
|
|
558
|
+
cat > "$blend_path" << BLEND_EOF
|
|
559
|
+
---
|
|
560
|
+
name: ${blend_name}
|
|
561
|
+
description: ${description}
|
|
562
|
+
model: ${model}
|
|
563
|
+
tools: Read, Write, Edit, Bash, Grep, Glob, WebSearch
|
|
564
|
+
permissionMode: bypassPermissions
|
|
565
|
+
maxTurns: 200
|
|
566
|
+
---
|
|
567
|
+
|
|
568
|
+
# Custom Specialist — Blended for This Task
|
|
569
|
+
|
|
570
|
+
You are a custom AI specialist dynamically created for this specific task. You combine deep expertise from multiple domains into a single coherent identity.
|
|
571
|
+
|
|
572
|
+
**Your expertise blend:** ${all_labels}
|
|
573
|
+
|
|
574
|
+
You don't just know one thing. You understand the intersections. When security meets API design, when testing meets performance, when frontend meets architecture — you see the full picture and make integrated decisions.
|
|
575
|
+
|
|
576
|
+
---
|
|
577
|
+
|
|
578
|
+
## Primary Expertise: ${primary_label}
|
|
579
|
+
|
|
580
|
+
${primary_body}
|
|
581
|
+
|
|
582
|
+
${secondary_sections}
|
|
583
|
+
|
|
584
|
+
BLEND_EOF
|
|
585
|
+
|
|
586
|
+
# Add Sights context if available
|
|
587
|
+
if [ -n "$sights_context" ]; then
|
|
588
|
+
cat >> "$blend_path" << SIGHTS_EOF
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
592
|
+
## Codebase Context (from Merlin Sights — REAL project knowledge)
|
|
593
|
+
|
|
594
|
+
This is YOUR codebase. These are the actual files, patterns, and conventions. Follow them.
|
|
595
|
+
|
|
596
|
+
${sights_context}
|
|
597
|
+
|
|
598
|
+
SIGHTS_EOF
|
|
599
|
+
fi
|
|
600
|
+
|
|
601
|
+
# Add cloud agents if available
|
|
602
|
+
if [ -n "$cloud_context" ]; then
|
|
603
|
+
cat >> "$blend_path" << CLOUD_EOF
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
## Specialist Knowledge (from Merlin Cloud)
|
|
608
|
+
|
|
609
|
+
${cloud_context}
|
|
610
|
+
|
|
611
|
+
CLOUD_EOF
|
|
612
|
+
fi
|
|
613
|
+
|
|
614
|
+
# Add CoVe verification protocol for high-stakes tasks
|
|
615
|
+
# This is selective — only security, architecture, debugging, review, audit, migration
|
|
616
|
+
local primary_key=""
|
|
617
|
+
if [ -n "$top_agents" ]; then
|
|
618
|
+
primary_key=$(echo "$top_agents" | head -1 | cut -d: -f2)
|
|
619
|
+
fi
|
|
620
|
+
|
|
621
|
+
if _blend_needs_cove "$primary_key" 2>/dev/null; then
|
|
622
|
+
_blend_cove_block "$primary_key" >> "$blend_path"
|
|
623
|
+
fi
|
|
624
|
+
|
|
625
|
+
# Add the task as final instruction
|
|
626
|
+
cat >> "$blend_path" << TASK_EOF
|
|
627
|
+
|
|
628
|
+
---
|
|
629
|
+
|
|
630
|
+
## Your Task
|
|
631
|
+
|
|
632
|
+
${task}
|
|
633
|
+
|
|
634
|
+
Execute this task using your full blended expertise. Be thorough, be smart, and ship clean code.
|
|
635
|
+
TASK_EOF
|
|
636
|
+
|
|
637
|
+
echo "$blend_path"
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
641
|
+
# Display Helpers
|
|
642
|
+
# Show what the blend engine decided (for the user)
|
|
643
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
644
|
+
|
|
645
|
+
# Show the blend decision to the user
|
|
646
|
+
blend_show_decision() {
|
|
647
|
+
local task="$1"
|
|
648
|
+
|
|
649
|
+
local top_agents
|
|
650
|
+
top_agents=$(blend_get_top_agents "$task" 4)
|
|
651
|
+
|
|
652
|
+
echo -e "${MAGENTA}${BOLD} Blend:${RESET}"
|
|
653
|
+
|
|
654
|
+
local count=0
|
|
655
|
+
while IFS=: read -r score key label file; do
|
|
656
|
+
[ -z "$score" ] && continue
|
|
657
|
+
[ -z "$file" ] && continue
|
|
658
|
+
count=$((count + 1))
|
|
659
|
+
|
|
660
|
+
if [ $count -eq 1 ]; then
|
|
661
|
+
echo -e " ${GREEN}${BOLD}[primary]${RESET} ${label} ${DIM}(score: ${score})${RESET}"
|
|
662
|
+
else
|
|
663
|
+
echo -e " ${CYAN}[blend]${RESET} ${label} ${DIM}(score: ${score})${RESET}"
|
|
664
|
+
fi
|
|
665
|
+
done <<< "$top_agents"
|
|
666
|
+
|
|
667
|
+
# Show Sights status
|
|
668
|
+
if command -v merlin &>/dev/null; then
|
|
669
|
+
echo -e " ${CYAN}[sights]${RESET} Codebase context will be injected"
|
|
670
|
+
fi
|
|
671
|
+
|
|
672
|
+
# Show CoVe status
|
|
673
|
+
local primary_key=""
|
|
674
|
+
primary_key=$(echo "$top_agents" | head -1 | cut -d: -f2)
|
|
675
|
+
if _blend_needs_cove "$primary_key" 2>/dev/null; then
|
|
676
|
+
echo -e " ${YELLOW}[CoVe]${RESET} Chain-of-Verification enabled (high-stakes task)"
|
|
677
|
+
fi
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
# Show a one-line summary of the blend
|
|
681
|
+
blend_summary() {
|
|
682
|
+
local task="$1"
|
|
683
|
+
|
|
684
|
+
local top_agents
|
|
685
|
+
top_agents=$(blend_get_top_agents "$task" 4)
|
|
686
|
+
|
|
687
|
+
local labels=""
|
|
688
|
+
while IFS=: read -r score key label file; do
|
|
689
|
+
[ -z "$label" ] && continue
|
|
690
|
+
if [ -z "$labels" ]; then
|
|
691
|
+
labels="$label"
|
|
692
|
+
else
|
|
693
|
+
labels="${labels} + ${label}"
|
|
694
|
+
fi
|
|
695
|
+
done <<< "$top_agents"
|
|
696
|
+
|
|
697
|
+
echo "$labels"
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
701
|
+
# Convenience: Spawn with blend (one function call)
|
|
702
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
703
|
+
|
|
704
|
+
# Blend and spawn in one call
|
|
705
|
+
# Returns the claude output
|
|
706
|
+
blend_and_spawn() {
|
|
707
|
+
local task="$1"
|
|
708
|
+
local extra_context="${2:-}"
|
|
709
|
+
local session_dir="${3:-/tmp/merlin-blend-$$}"
|
|
710
|
+
|
|
711
|
+
# Create the blended agent
|
|
712
|
+
local agent_path
|
|
713
|
+
agent_path=$(blend_for_task "$task" "$session_dir")
|
|
714
|
+
|
|
715
|
+
# Build the prompt (task + any extra context)
|
|
716
|
+
local prompt="$task"
|
|
717
|
+
if [ -n "$extra_context" ]; then
|
|
718
|
+
prompt="${prompt}\n\n## Additional Context\n\n${extra_context}"
|
|
719
|
+
fi
|
|
720
|
+
|
|
721
|
+
# Spawn
|
|
722
|
+
local output
|
|
723
|
+
output=$(claude --agent "$agent_path" --output-format text -p "$prompt" 2>&1)
|
|
724
|
+
local exit_code=$?
|
|
725
|
+
|
|
726
|
+
# Clean up temp agent file
|
|
727
|
+
# rm -f "$agent_path" # Keep for debugging — uncomment for production
|
|
728
|
+
|
|
729
|
+
echo "$output"
|
|
730
|
+
return $exit_code
|
|
731
|
+
}
|
|
@@ -40,6 +40,7 @@ source "$SCRIPT_DIR/lib/context.sh"
|
|
|
40
40
|
source "$SCRIPT_DIR/lib/modes.sh"
|
|
41
41
|
source "$SCRIPT_DIR/lib/sights.sh" 2>/dev/null || true # Sights integration
|
|
42
42
|
source "$SCRIPT_DIR/lib/agents.sh" 2>/dev/null || true # Agent profiles and routing
|
|
43
|
+
source "$SCRIPT_DIR/lib/blend.sh" 2>/dev/null || true # Dynamic agent blending engine
|
|
43
44
|
source "$SCRIPT_DIR/lib/teams.sh" 2>/dev/null || true # Agent Teams integration
|
|
44
45
|
source "$SCRIPT_DIR/lib/boot.sh" 2>/dev/null || true # Boot sequence
|
|
45
46
|
source "$SCRIPT_DIR/lib/session-end.sh" 2>/dev/null || true # Session end protocol
|
|
@@ -402,23 +403,40 @@ run_iteration() {
|
|
|
402
403
|
local output
|
|
403
404
|
local exit_code=0
|
|
404
405
|
|
|
405
|
-
#
|
|
406
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
407
|
+
# BLEND ENGINE: Create a custom specialist for this task
|
|
408
|
+
# Instead of routing to a single static agent, blend multiple agents
|
|
409
|
+
# into a custom-optimized specialist for every iteration.
|
|
410
|
+
# Fallback: use boot-selected agent if blend engine not available.
|
|
411
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
406
412
|
local agent_cli_name="merlin"
|
|
407
|
-
|
|
413
|
+
local agent_display_name="merlin"
|
|
414
|
+
|
|
415
|
+
if type blend_for_task &>/dev/null; then
|
|
416
|
+
# Blend engine available — create custom specialist
|
|
417
|
+
mkdir -p "$LOOP_DIR/blends"
|
|
418
|
+
local blend_path
|
|
419
|
+
blend_path=$(blend_for_task "$current_task_desc" "$LOOP_DIR/blends")
|
|
420
|
+
agent_cli_name="$blend_path"
|
|
421
|
+
agent_display_name=$(blend_summary "$current_task_desc" 2>/dev/null || echo "Blended Specialist")
|
|
422
|
+
|
|
423
|
+
if type blend_show_decision &>/dev/null; then
|
|
424
|
+
blend_show_decision "$current_task_desc"
|
|
425
|
+
fi
|
|
426
|
+
elif [ -n "${BOOT_SELECTED_AGENT:-}" ] && type get_agent_cli_name &> /dev/null; then
|
|
427
|
+
# Fallback: static agent routing (pre-blend behavior)
|
|
408
428
|
agent_cli_name=$(get_agent_cli_name "$BOOT_SELECTED_AGENT")
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
if type get_agent_display_name &> /dev/null && [ -n "${BOOT_SELECTED_AGENT:-}" ]; then
|
|
413
|
-
agent_display_name=$(get_agent_display_name "$BOOT_SELECTED_AGENT")
|
|
429
|
+
if type get_agent_display_name &> /dev/null; then
|
|
430
|
+
agent_display_name=$(get_agent_display_name "$BOOT_SELECTED_AGENT")
|
|
431
|
+
fi
|
|
414
432
|
fi
|
|
415
433
|
|
|
416
434
|
echo -e "${BLUE}Spawning Claude as ${BOLD}${agent_display_name}${RESET}${BLUE}...${RESET}"
|
|
417
|
-
echo -e "${BLUE}Agent: --agent $
|
|
435
|
+
echo -e "${BLUE}Agent: --agent $(basename "$agent_cli_name" .md) | Task list: ${TASK_LIST_ID}${RESET}"
|
|
418
436
|
|
|
419
437
|
# Capture output and exit code
|
|
420
438
|
# CLAUDE_CODE_TASK_LIST_ID enables cross-session task coordination
|
|
421
|
-
# --agent routes to the specialist
|
|
439
|
+
# --agent routes to the blended specialist (or fallback agent)
|
|
422
440
|
set +e
|
|
423
441
|
output=$(CLAUDE_CODE_TASK_LIST_ID="$TASK_LIST_ID" claude --agent "$agent_cli_name" --output-format stream-json 2>&1 <<< "$full_prompt")
|
|
424
442
|
exit_code=$?
|
|
@@ -44,6 +44,7 @@ source "$SCRIPT_DIR/lib/tui.sh" 2>/dev/null || true
|
|
|
44
44
|
source "$SCRIPT_DIR/lib/agents.sh" 2>/dev/null || true
|
|
45
45
|
source "$SCRIPT_DIR/lib/sights.sh" 2>/dev/null || true
|
|
46
46
|
source "$SCRIPT_DIR/lib/boot.sh" 2>/dev/null || true
|
|
47
|
+
source "$SCRIPT_DIR/lib/blend.sh" 2>/dev/null || true
|
|
47
48
|
|
|
48
49
|
# Colors
|
|
49
50
|
: "${RESET:=\033[0m}"
|
|
@@ -77,7 +78,7 @@ banner() {
|
|
|
77
78
|
echo " / / / / __/ / / / / / / /"
|
|
78
79
|
echo "/_/ /_/\___/_/ /_/_/_/ /_/"
|
|
79
80
|
echo -e "${RESET}"
|
|
80
|
-
echo -e "${DIM}Interactive Session —
|
|
81
|
+
echo -e "${DIM}Interactive Session — Blended agents = custom specialist per task${RESET}"
|
|
81
82
|
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
|
82
83
|
echo ""
|
|
83
84
|
}
|
|
@@ -400,34 +401,143 @@ spawn_agent() {
|
|
|
400
401
|
}
|
|
401
402
|
|
|
402
403
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
403
|
-
#
|
|
404
|
+
# Blended Spawn — THE DEFAULT PATH
|
|
405
|
+
# Every task gets a custom-optimized specialist
|
|
404
406
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
405
407
|
|
|
406
|
-
#
|
|
407
|
-
|
|
408
|
+
# Spawn a blended agent for a task (replaces route_to_agent + spawn_agent)
|
|
409
|
+
spawn_blended() {
|
|
410
|
+
local task="$1"
|
|
411
|
+
|
|
412
|
+
# Create session directory
|
|
413
|
+
mkdir -p "$SESSION_DIR"
|
|
414
|
+
|
|
415
|
+
# Create the blended agent
|
|
416
|
+
local blend_path
|
|
417
|
+
blend_path=$(blend_for_task "$task" "$SESSION_DIR")
|
|
418
|
+
|
|
419
|
+
local blend_label
|
|
420
|
+
blend_label=$(blend_summary "$task")
|
|
421
|
+
|
|
422
|
+
# Build additional context (pipeline context from previous agents)
|
|
423
|
+
local prompt="$task"
|
|
424
|
+
if [ -n "$PIPELINE_CONTEXT" ]; then
|
|
425
|
+
prompt="${prompt}
|
|
426
|
+
|
|
427
|
+
## Previous Agent Output
|
|
428
|
+
|
|
429
|
+
${PIPELINE_CONTEXT}"
|
|
430
|
+
fi
|
|
431
|
+
if [ -f "PROJECT.md" ]; then
|
|
432
|
+
prompt="${prompt}
|
|
433
|
+
|
|
434
|
+
## Project Info
|
|
435
|
+
|
|
436
|
+
See PROJECT.md in the repo root."
|
|
437
|
+
fi
|
|
438
|
+
|
|
439
|
+
# Show spawn info
|
|
440
|
+
echo ""
|
|
441
|
+
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
|
442
|
+
echo -e "${MAGENTA}${BOLD} Spawning: ${blend_label}${RESET}"
|
|
443
|
+
echo -e "${DIM} Fresh Claude session (200K context, custom blended specialist)${RESET}"
|
|
444
|
+
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
|
445
|
+
echo ""
|
|
446
|
+
|
|
447
|
+
# Spawn fresh Claude with blended agent
|
|
448
|
+
local output=""
|
|
449
|
+
local exit_code=0
|
|
450
|
+
local start_time
|
|
451
|
+
start_time=$(date +%s)
|
|
452
|
+
|
|
453
|
+
set +e
|
|
454
|
+
output=$(claude --agent "$blend_path" --output-format text -p "$prompt" 2>&1)
|
|
455
|
+
exit_code=$?
|
|
456
|
+
set -e
|
|
457
|
+
|
|
458
|
+
local end_time
|
|
459
|
+
end_time=$(date +%s)
|
|
460
|
+
local duration=$((end_time - start_time))
|
|
461
|
+
|
|
462
|
+
# Save result
|
|
463
|
+
echo "$output" > "$RESULT_FILE"
|
|
464
|
+
|
|
465
|
+
# Update state
|
|
466
|
+
AGENT_RUNS=$((AGENT_RUNS + 1))
|
|
467
|
+
LAST_AGENT="$blend_label"
|
|
468
|
+
LAST_RESULT="$output"
|
|
469
|
+
PIPELINE_CONTEXT="$output"
|
|
470
|
+
|
|
471
|
+
# Log to history
|
|
472
|
+
echo "{\"agent\":\"blend:${blend_label}\",\"task\":\"$(echo "$task" | head -c 100)\",\"duration\":$duration,\"exit_code\":$exit_code,\"timestamp\":$(date +%s)}" >> "$HISTORY_FILE"
|
|
473
|
+
|
|
474
|
+
# Show result
|
|
475
|
+
echo ""
|
|
476
|
+
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
|
477
|
+
if [ $exit_code -eq 0 ]; then
|
|
478
|
+
echo -e "${GREEN}${BOLD} ✓ Completed${RESET} ${DIM}(${duration}s)${RESET}"
|
|
479
|
+
else
|
|
480
|
+
echo -e "${RED}${BOLD} ✗ Failed${RESET} ${DIM}(exit $exit_code, ${duration}s)${RESET}"
|
|
481
|
+
fi
|
|
482
|
+
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
|
483
|
+
echo ""
|
|
484
|
+
|
|
485
|
+
# Show the output (truncated for display)
|
|
486
|
+
local line_count
|
|
487
|
+
line_count=$(echo "$output" | wc -l | tr -d ' ')
|
|
488
|
+
if [ "$line_count" -gt 50 ]; then
|
|
489
|
+
echo "$output" | head -40
|
|
490
|
+
echo ""
|
|
491
|
+
echo -e "${DIM} ... ($((line_count - 40)) more lines — full output in $RESULT_FILE)${RESET}"
|
|
492
|
+
echo ""
|
|
493
|
+
echo "$output" | tail -10
|
|
494
|
+
else
|
|
495
|
+
echo "$output"
|
|
496
|
+
fi
|
|
497
|
+
|
|
498
|
+
return $exit_code
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
502
|
+
# Pipeline Support — Each step gets its own custom-blended specialist
|
|
503
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
504
|
+
|
|
505
|
+
# Run a blended pipeline — each phase gets a custom specialist
|
|
408
506
|
run_pipeline() {
|
|
409
507
|
local task="$1"
|
|
410
|
-
shift
|
|
411
|
-
local agents=("$@")
|
|
412
508
|
|
|
413
|
-
|
|
414
|
-
|
|
509
|
+
local phases=(
|
|
510
|
+
"Define the requirements and write a clear spec for: $task"
|
|
511
|
+
"Design the architecture and data models for: $task"
|
|
512
|
+
"Implement the full feature: $task"
|
|
513
|
+
"Write comprehensive tests for: $task"
|
|
514
|
+
)
|
|
515
|
+
local phase_names=("Spec" "Architecture" "Implementation" "Testing")
|
|
516
|
+
|
|
517
|
+
echo -e "\n${MAGENTA}${BOLD}Pipeline:${RESET} Spec → Architecture → Implementation → Testing"
|
|
518
|
+
echo -e "${DIM}Each phase gets a custom-blended specialist with fresh 200K context.${RESET}\n"
|
|
415
519
|
|
|
416
520
|
PIPELINE_CONTEXT=""
|
|
417
521
|
|
|
418
|
-
for i in "${!
|
|
419
|
-
local
|
|
522
|
+
for i in "${!phases[@]}"; do
|
|
523
|
+
local phase="${phases[$i]}"
|
|
524
|
+
local name="${phase_names[$i]}"
|
|
420
525
|
local step=$((i + 1))
|
|
421
|
-
local total=${#
|
|
526
|
+
local total=${#phases[@]}
|
|
422
527
|
|
|
423
|
-
echo -e "${CYAN}[${step}/${total}]${RESET} ${BOLD}${
|
|
528
|
+
echo -e "\n${CYAN}[${step}/${total}]${RESET} ${BOLD}${name}${RESET}"
|
|
424
529
|
|
|
425
|
-
|
|
530
|
+
# Show the blend for this phase
|
|
531
|
+
if type blend_show_decision &>/dev/null; then
|
|
532
|
+
blend_show_decision "$phase"
|
|
533
|
+
fi
|
|
534
|
+
|
|
535
|
+
spawn_blended "$phase"
|
|
426
536
|
local exit_code=$?
|
|
427
537
|
|
|
428
538
|
if [ $exit_code -ne 0 ]; then
|
|
429
|
-
echo -e "\n${RED}Pipeline stopped at ${
|
|
430
|
-
echo -
|
|
539
|
+
echo -e "\n${RED}Pipeline stopped at ${name} (exit $exit_code)${RESET}"
|
|
540
|
+
echo -ne "${YELLOW}Continue anyway? [y/N]: ${RESET}"
|
|
431
541
|
read -r continue_choice
|
|
432
542
|
if [ "$continue_choice" != "y" ] && [ "$continue_choice" != "Y" ]; then
|
|
433
543
|
return 1
|
|
@@ -436,8 +546,9 @@ run_pipeline() {
|
|
|
436
546
|
|
|
437
547
|
# Between pipeline steps, ask user if they want to continue
|
|
438
548
|
if [ $step -lt $total ]; then
|
|
549
|
+
local next_name="${phase_names[$((i+1))]}"
|
|
439
550
|
echo ""
|
|
440
|
-
echo -e "${WHITE}[enter]${RESET} Continue to ${
|
|
551
|
+
echo -e "${WHITE}[enter]${RESET} Continue to ${BOLD}${next_name}${RESET} ${WHITE}[s]${RESET} Skip ${WHITE}[q]${RESET} Stop pipeline"
|
|
441
552
|
read -r pipeline_choice
|
|
442
553
|
case "$pipeline_choice" in
|
|
443
554
|
s|S) continue ;;
|
|
@@ -447,7 +558,7 @@ run_pipeline() {
|
|
|
447
558
|
fi
|
|
448
559
|
done
|
|
449
560
|
|
|
450
|
-
echo -e "\n${GREEN}${BOLD}✓ Pipeline complete${RESET}"
|
|
561
|
+
echo -e "\n${GREEN}${BOLD}✓ Pipeline complete${RESET} (4 blended specialists ran)"
|
|
451
562
|
}
|
|
452
563
|
|
|
453
564
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -466,9 +577,9 @@ handle_special_commands() {
|
|
|
466
577
|
help|h|\?)
|
|
467
578
|
echo ""
|
|
468
579
|
echo -e "${BOLD}Commands:${RESET}"
|
|
469
|
-
echo -e " ${WHITE}Any text${RESET} →
|
|
580
|
+
echo -e " ${WHITE}Any text${RESET} → Blends the best agents into a custom specialist"
|
|
470
581
|
echo -e " ${WHITE}@agent task${RESET} → Force a specific agent (e.g. @tests-qa add tests)"
|
|
471
|
-
echo -e " ${WHITE}pipeline${RESET} → Run
|
|
582
|
+
echo -e " ${WHITE}pipeline${RESET} → Run blended pipeline: spec → arch → impl → test"
|
|
472
583
|
echo -e " ${WHITE}history${RESET} → Show agent history this session"
|
|
473
584
|
echo -e " ${WHITE}result${RESET} → Show last agent's full output"
|
|
474
585
|
echo -e " ${WHITE}status${RESET} → Show project status"
|
|
@@ -516,7 +627,7 @@ handle_special_commands() {
|
|
|
516
627
|
echo -ne "${MAGENTA}❯${RESET} "
|
|
517
628
|
read -r pipeline_task
|
|
518
629
|
if [ -n "$pipeline_task" ]; then
|
|
519
|
-
run_pipeline "$pipeline_task"
|
|
630
|
+
run_pipeline "$pipeline_task"
|
|
520
631
|
fi
|
|
521
632
|
return 0
|
|
522
633
|
;;
|
|
@@ -603,13 +714,23 @@ main() {
|
|
|
603
714
|
continue
|
|
604
715
|
fi
|
|
605
716
|
|
|
606
|
-
#
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
717
|
+
# ═════════════════════════════════════════════════════════════════════════
|
|
718
|
+
# BLEND ENGINE: Show what custom specialist will be created for this task
|
|
719
|
+
# ═════════════════════════════════════════════════════════════════════════
|
|
720
|
+
local use_blend=false
|
|
721
|
+
if type blend_show_decision &>/dev/null; then
|
|
722
|
+
echo ""
|
|
723
|
+
blend_show_decision "$user_input"
|
|
724
|
+
use_blend=true
|
|
725
|
+
else
|
|
726
|
+
# Fallback: static routing
|
|
727
|
+
local agent
|
|
728
|
+
agent=$(route_to_agent "$user_input")
|
|
729
|
+
echo -e "${DIM}Routed to: ${CYAN}${agent}${RESET}"
|
|
730
|
+
fi
|
|
611
731
|
|
|
612
732
|
# Ask for confirmation (quick — enter to confirm, or change)
|
|
733
|
+
echo ""
|
|
613
734
|
echo -ne "${DIM}[enter] Go [c] Change agent [p] Full pipeline: ${RESET}"
|
|
614
735
|
read -r confirm
|
|
615
736
|
|
|
@@ -619,24 +740,34 @@ main() {
|
|
|
619
740
|
echo -e " product-spec, system-architect, implementation-dev, dry-refactor"
|
|
620
741
|
echo -e " hardening-guard, tests-qa, ops-railway, docs-keeper"
|
|
621
742
|
echo -e " merlin-frontend, merlin-api-designer, merlin-debugger, remotion"
|
|
743
|
+
echo -e " apple-swift-expert, android-expert, ui-designer, ui-builder"
|
|
744
|
+
echo -e " animation-expert, marketing-automation, desktop-app-expert"
|
|
622
745
|
echo -ne "\n${WHITE}Agent: ${RESET}"
|
|
623
746
|
read -r new_agent
|
|
624
747
|
if [ -n "$new_agent" ]; then
|
|
625
|
-
|
|
748
|
+
spawn_agent "$new_agent" "$user_input"
|
|
749
|
+
use_blend=false
|
|
626
750
|
fi
|
|
627
751
|
;;
|
|
628
752
|
p|P|pipeline)
|
|
629
|
-
run_pipeline "$user_input"
|
|
753
|
+
run_pipeline "$user_input"
|
|
630
754
|
continue
|
|
631
755
|
;;
|
|
632
756
|
*)
|
|
633
|
-
;; # proceed with
|
|
757
|
+
;; # proceed with blend
|
|
634
758
|
esac
|
|
635
759
|
|
|
636
|
-
# Spawn the agent
|
|
637
|
-
|
|
760
|
+
# Spawn the blended specialist (or skip if user changed agent)
|
|
761
|
+
if [ "$use_blend" = true ] && [ "$confirm" != "c" ] && [ "$confirm" != "C" ]; then
|
|
762
|
+
spawn_blended "$user_input"
|
|
763
|
+
elif [ "$use_blend" = false ] && [ "$confirm" != "c" ] && [ "$confirm" != "C" ]; then
|
|
764
|
+
# Fallback: no blend engine, use static routing
|
|
765
|
+
local agent
|
|
766
|
+
agent=$(route_to_agent "$user_input")
|
|
767
|
+
spawn_agent "$agent" "$user_input"
|
|
768
|
+
fi
|
|
638
769
|
|
|
639
|
-
# After agent completes, suggest next steps
|
|
770
|
+
# After agent completes, suggest next steps (also blended!)
|
|
640
771
|
echo ""
|
|
641
772
|
echo -e "${WHITE}What's next?${RESET}"
|
|
642
773
|
echo -e " ${DIM}[enter] New task [t] Test this [r] Refactor [d] Docs [q] Quit${RESET}"
|
|
@@ -645,13 +776,25 @@ main() {
|
|
|
645
776
|
|
|
646
777
|
case "$next_choice" in
|
|
647
778
|
t|T|test)
|
|
648
|
-
|
|
779
|
+
if [ "$use_blend" = true ]; then
|
|
780
|
+
spawn_blended "Write comprehensive tests for what was just implemented: $user_input"
|
|
781
|
+
else
|
|
782
|
+
spawn_agent "tests-qa" "Write tests for what was just implemented: $user_input"
|
|
783
|
+
fi
|
|
649
784
|
;;
|
|
650
785
|
r|R|refactor)
|
|
651
|
-
|
|
786
|
+
if [ "$use_blend" = true ]; then
|
|
787
|
+
spawn_blended "Refactor and clean up the code from: $user_input"
|
|
788
|
+
else
|
|
789
|
+
spawn_agent "dry-refactor" "Refactor and clean up the code from: $user_input"
|
|
790
|
+
fi
|
|
652
791
|
;;
|
|
653
792
|
d|D|docs)
|
|
654
|
-
|
|
793
|
+
if [ "$use_blend" = true ]; then
|
|
794
|
+
spawn_blended "Document what was just built: $user_input"
|
|
795
|
+
else
|
|
796
|
+
spawn_agent "docs-keeper" "Document what was just built: $user_input"
|
|
797
|
+
fi
|
|
655
798
|
;;
|
|
656
799
|
q|Q|quit|exit)
|
|
657
800
|
echo -e "\n${MAGENTA}Session ended.${RESET} ${DIM}($AGENT_RUNS agents spawned)${RESET}\n"
|
package/files/merlin/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.6.
|
|
1
|
+
3.6.2
|
package/package.json
CHANGED