create-merlin-brain 3.2.0 → 3.3.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.
Files changed (48) hide show
  1. package/bin/install.cjs +125 -12
  2. package/bin/merlin-cli.cjs +26 -0
  3. package/files/agents/code-organization-supervisor.md +1 -0
  4. package/files/agents/context-guardian.md +1 -0
  5. package/files/agents/docs-keeper.md +2 -1
  6. package/files/agents/dry-refactor.md +2 -1
  7. package/files/agents/elite-code-refactorer.md +1 -0
  8. package/files/agents/hardening-guard.md +2 -1
  9. package/files/agents/implementation-dev.md +2 -1
  10. package/files/agents/merlin-api-designer.md +1 -0
  11. package/files/agents/merlin-codebase-mapper.md +1 -0
  12. package/files/agents/merlin-debugger.md +1 -0
  13. package/files/agents/merlin-executor.md +1 -0
  14. package/files/agents/merlin-frontend.md +1 -0
  15. package/files/agents/merlin-integration-checker.md +1 -0
  16. package/files/agents/merlin-migrator.md +1 -0
  17. package/files/agents/merlin-milestone-auditor.md +1 -0
  18. package/files/agents/merlin-performance.md +1 -0
  19. package/files/agents/merlin-planner.md +2 -0
  20. package/files/agents/merlin-researcher.md +1 -0
  21. package/files/agents/merlin-reviewer.md +2 -0
  22. package/files/agents/merlin-security.md +2 -0
  23. package/files/agents/merlin-verifier.md +2 -0
  24. package/files/agents/merlin-work-verifier.md +1 -0
  25. package/files/agents/merlin.md +1 -0
  26. package/files/agents/ops-railway.md +2 -1
  27. package/files/agents/orchestrator-retrofit.md +1 -0
  28. package/files/agents/product-spec.md +3 -1
  29. package/files/agents/system-architect.md +3 -1
  30. package/files/agents/tests-qa.md +2 -1
  31. package/files/hooks/agent-sync.sh +44 -0
  32. package/files/hooks/lib/analytics.sh +74 -0
  33. package/files/hooks/lib/sights-check.sh +58 -0
  34. package/files/hooks/post-edit-logger.sh +33 -0
  35. package/files/hooks/pre-edit-sights-check.sh +38 -0
  36. package/files/hooks/session-end.sh +27 -0
  37. package/files/hooks/session-start.sh +31 -0
  38. package/files/hooks/stop-check.md +14 -0
  39. package/files/hooks/subagent-context.sh +36 -0
  40. package/files/hooks/task-completed-verify.md +14 -0
  41. package/files/hooks/task-completed-verify.sh +46 -0
  42. package/files/hooks/teammate-idle-verify.sh +41 -0
  43. package/files/loop/lib/sights.sh +15 -4
  44. package/files/loop/lib/teams.sh +143 -0
  45. package/files/loop/merlin-loop.sh +16 -0
  46. package/files/merlin/agents-sync.sh +163 -0
  47. package/files/merlin/analytics.sh +159 -0
  48. package/package.json +1 -1
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Merlin Hooks - Shared Analytics Library
4
+ # Lightweight session tracking and event logging for Claude Code hooks.
5
+ # All functions are POSIX-ish, fast (<10ms), and degrade gracefully.
6
+ #
7
+
8
+ MERLIN_ANALYTICS_DIR="${HOME}/.claude/merlin/analytics"
9
+
10
+ get_session_id() {
11
+ if [ -n "${MERLIN_SESSION_ID:-}" ]; then echo "${MERLIN_SESSION_ID}"; return; fi
12
+ echo "$(date +%s)-$$"
13
+ }
14
+
15
+ MERLIN_SESSION_FILE="${MERLIN_SESSION_FILE:-${MERLIN_ANALYTICS_DIR}/session-$(get_session_id).json}"
16
+
17
+ ensure_session_file() {
18
+ [ -d "${MERLIN_ANALYTICS_DIR}" ] || mkdir -p "${MERLIN_ANALYTICS_DIR}" 2>/dev/null || return 1
19
+ if [ ! -f "${MERLIN_SESSION_FILE}" ]; then
20
+ local stype="raw"
21
+ [ -n "${MERLIN_LOOP_SESSION:-}" ] && stype="loop"
22
+ [ -z "${MERLIN_LOOP_SESSION:-}" ] && [ -n "${CLAUDE_CODE_HOOK_NAME:-}" ] && stype="hooks"
23
+ printf '{"id":"%s","startTime":"%s","sessionType":"%s","events":[]}\n' \
24
+ "$(get_session_id)" "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" "$stype" \
25
+ > "${MERLIN_SESSION_FILE}" 2>/dev/null || return 1
26
+ fi
27
+ }
28
+
29
+ # Core event logger — all specialized loggers call this
30
+ # Usage: log_event "type" '{"key":"value"}'
31
+ log_event() {
32
+ local etype="${1:-unknown}" edata="${2:-{}}"
33
+ ensure_session_file || return 1
34
+ local evt
35
+ evt=$(printf '{"type":"%s","ts":"%s","data":%s}' "$etype" "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" "$edata")
36
+ if command -v jq >/dev/null 2>&1; then
37
+ local tmp; tmp=$(mktemp 2>/dev/null || echo "/tmp/merlin-evt-$$")
38
+ jq --argjson e "$evt" '.events += [$e]' "${MERLIN_SESSION_FILE}" > "$tmp" 2>/dev/null \
39
+ && mv "$tmp" "${MERLIN_SESSION_FILE}" 2>/dev/null || rm -f "$tmp" 2>/dev/null
40
+ else
41
+ echo "$evt" >> "${MERLIN_SESSION_FILE}.log" 2>/dev/null
42
+ fi
43
+ }
44
+
45
+ # Specialized loggers — thin wrappers for structured events
46
+ log_sights_call() { log_event "sights_call" "$(printf '{"tool":"%s","query":"%s"}' "${1:-}" "${2:-}")"; }
47
+ log_agent_route() { log_event "agent_route" "$(printf '{"agent":"%s","task":"%s"}' "${1:-}" "${2:-}")"; }
48
+ log_error() { log_event "error" "$(printf '{"errorType":"%s","message":"%s"}' "${1:-}" "${2:-}")"; }
49
+ log_file_edit() { log_event "file_edit" "$(printf '{"file":"%s"}' "${1:-}")"; }
50
+ log_task_complete() { log_event "task_complete" "$(printf '{"task":"%s"}' "${1:-}")"; }
51
+
52
+ # Finalize session: compute summary stats from events
53
+ finalize_session() {
54
+ [ -f "${MERLIN_SESSION_FILE}" ] || return 1
55
+ command -v jq >/dev/null 2>&1 || return 0
56
+ # Extract start epoch from session ID (format: epoch-pid) for duration calc
57
+ local sid; sid=$(jq -r '.id // ""' "${MERLIN_SESSION_FILE}" 2>/dev/null)
58
+ local start_epoch; start_epoch=$(echo "${sid}" | cut -d'-' -f1 2>/dev/null)
59
+ local end_epoch; end_epoch=$(date +%s)
60
+ local dur; dur=$(( end_epoch - ${start_epoch:-$end_epoch} ))
61
+ local tmp; tmp=$(mktemp 2>/dev/null || echo "/tmp/merlin-fin-$$")
62
+ jq --arg et "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" --argjson dur "${dur}" '
63
+ .endTime = $et | .durationSeconds = $dur
64
+ | .summary = {
65
+ sightsCallCount: [.events[] | select(.type == "sights_call")] | length,
66
+ filesChanged: [.events[] | select(.type == "file_edit")] | length,
67
+ tasksCompleted: [.events[] | select(.type == "task_complete")] | length,
68
+ errorsCount: [.events[] | select(.type == "error")] | length,
69
+ agentRoutes: [.events[] | select(.type == "agent_route")] | length
70
+ }
71
+ | .eventCount = (.events | length)
72
+ ' "${MERLIN_SESSION_FILE}" > "$tmp" 2>/dev/null \
73
+ && mv "$tmp" "${MERLIN_SESSION_FILE}" 2>/dev/null || rm -f "$tmp" 2>/dev/null
74
+ }
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Merlin Hooks - Shared Sights Check Library
4
+ # Tracks when Sights was last consulted so hooks can detect stale context.
5
+ #
6
+
7
+ MERLIN_SIGHTS_LAST_CHECK_FILE="${HOME}/.claude/merlin/.last-sights-check"
8
+
9
+ # Record that Sights was just called
10
+ record_sights_call() {
11
+ local dir
12
+ dir=$(dirname "${MERLIN_SIGHTS_LAST_CHECK_FILE}")
13
+ mkdir -p "$dir" 2>/dev/null || return 1
14
+ date +%s > "${MERLIN_SIGHTS_LAST_CHECK_FILE}" 2>/dev/null || return 1
15
+ return 0
16
+ }
17
+
18
+ # Check if Sights was consulted within N seconds
19
+ # Returns 0 (true) if recent, 1 (false) if stale or never checked
20
+ sights_was_checked_recently() {
21
+ local max_age="${1:-120}"
22
+
23
+ if [ ! -f "${MERLIN_SIGHTS_LAST_CHECK_FILE}" ]; then
24
+ return 1
25
+ fi
26
+
27
+ local last_check
28
+ last_check=$(cat "${MERLIN_SIGHTS_LAST_CHECK_FILE}" 2>/dev/null || echo "0")
29
+ local now
30
+ now=$(date +%s)
31
+ local age=$(( now - ${last_check:-0} ))
32
+
33
+ if [ "$age" -le "$max_age" ]; then
34
+ return 0
35
+ fi
36
+ return 1
37
+ }
38
+
39
+ # Read MERLIN_API_KEY from env or config
40
+ get_merlin_api_key() {
41
+ # Use ${VAR:-} pattern to avoid crash with set -u
42
+ if [ -n "${MERLIN_API_KEY:-}" ]; then
43
+ echo "${MERLIN_API_KEY}"
44
+ return
45
+ fi
46
+
47
+ local config="${HOME}/.claude/config.json"
48
+ if [ -f "$config" ] && command -v jq >/dev/null 2>&1; then
49
+ local key
50
+ key=$(jq -r '.mcpServers.merlin.env.MERLIN_API_KEY // empty' "$config" 2>/dev/null)
51
+ if [ -n "$key" ]; then
52
+ echo "$key"
53
+ return
54
+ fi
55
+ fi
56
+
57
+ echo ""
58
+ }
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Merlin Hook: PostToolUse (Edit/Write)
4
+ # Logs file changes for analytics tracking.
5
+ # Always exits 0 — never blocks Claude Code.
6
+ #
7
+ set -euo pipefail
8
+ trap 'exit 0' ERR
9
+
10
+ HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+
12
+ # Source shared libraries
13
+ # shellcheck source=lib/analytics.sh
14
+ . "${HOOKS_DIR}/lib/analytics.sh"
15
+
16
+ # Read tool output from stdin (Claude Code pipes JSON)
17
+ input=""
18
+ if [ ! -t 0 ]; then
19
+ input=$(cat 2>/dev/null || true)
20
+ fi
21
+
22
+ # Extract file path and tool name
23
+ file_path=""
24
+ tool_name=""
25
+ if [ -n "$input" ] && command -v jq >/dev/null 2>&1; then
26
+ file_path=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.path // empty' 2>/dev/null || true)
27
+ tool_name=$(echo "$input" | jq -r '.tool_name // empty' 2>/dev/null || true)
28
+ fi
29
+
30
+ # Log file change event
31
+ log_event "file_changed" "$(printf '{"file":"%s","tool":"%s"}' "${file_path:-unknown}" "${tool_name:-unknown}")"
32
+
33
+ exit 0
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Merlin Hook: PreToolUse (Edit/Write)
4
+ # Checks if Sights was consulted recently before file edits.
5
+ # Advisory only — always exits 0, never blocks edits.
6
+ #
7
+ set -euo pipefail
8
+ trap 'exit 0' ERR
9
+
10
+ HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+
12
+ # Source shared libraries
13
+ # shellcheck source=lib/analytics.sh
14
+ . "${HOOKS_DIR}/lib/analytics.sh"
15
+ # shellcheck source=lib/sights-check.sh
16
+ . "${HOOKS_DIR}/lib/sights-check.sh"
17
+
18
+ # Read tool input from stdin (Claude Code pipes JSON)
19
+ input=""
20
+ if [ ! -t 0 ]; then
21
+ input=$(cat 2>/dev/null || true)
22
+ fi
23
+
24
+ # Extract file path from tool input
25
+ file_path=""
26
+ if [ -n "$input" ] && command -v jq >/dev/null 2>&1; then
27
+ file_path=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.path // empty' 2>/dev/null || true)
28
+ fi
29
+
30
+ # Check if Sights was consulted recently (within 120 seconds)
31
+ if ! sights_was_checked_recently 120; then
32
+ log_event "sights_skip_warning" "$(printf '{"file":"%s"}' "${file_path:-unknown}")"
33
+ fi
34
+
35
+ # Log the pre-edit event
36
+ log_event "pre_edit" "$(printf '{"file":"%s"}' "${file_path:-unknown}")"
37
+
38
+ exit 0
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Merlin Hook: SessionEnd / Stop
4
+ # Saves checkpoint and finalizes session analytics.
5
+ # Always exits 0 — never blocks Claude Code shutdown.
6
+ #
7
+ set -euo pipefail
8
+ trap 'exit 0' ERR
9
+
10
+ HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+
12
+ # Source shared libraries
13
+ # shellcheck source=lib/analytics.sh
14
+ . "${HOOKS_DIR}/lib/analytics.sh"
15
+
16
+ # Log session end
17
+ log_event "session_end" '{}'
18
+
19
+ # Finalize session analytics (adds endTime, duration, summary)
20
+ finalize_session
21
+
22
+ # If merlin CLI is available, save checkpoint in background
23
+ if command -v merlin >/dev/null 2>&1; then
24
+ merlin save-checkpoint "Session ended" >/dev/null 2>&1 &
25
+ fi
26
+
27
+ exit 0
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Merlin Hook: SessionStart
4
+ # Initializes analytics session and kicks off Sights context in background.
5
+ # Always exits 0 — never blocks Claude Code startup.
6
+ #
7
+ set -euo pipefail
8
+ trap 'exit 0' ERR
9
+
10
+ HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+
12
+ # Source shared libraries
13
+ # shellcheck source=lib/analytics.sh
14
+ . "${HOOKS_DIR}/lib/analytics.sh"
15
+ # shellcheck source=lib/sights-check.sh
16
+ . "${HOOKS_DIR}/lib/sights-check.sh"
17
+
18
+ # Initialize analytics session
19
+ ensure_session_file
20
+
21
+ # Log session start with working directory
22
+ cwd="${PWD:-$(pwd)}"
23
+ log_event "session_start" "$(printf '{"cwd":"%s"}' "$cwd")"
24
+
25
+ # If merlin CLI is available, warm up Sights context in background
26
+ if command -v merlin >/dev/null 2>&1; then
27
+ merlin context "session start" >/dev/null 2>&1 &
28
+ record_sights_call
29
+ fi
30
+
31
+ exit 0
@@ -0,0 +1,14 @@
1
+ # Stop Check - Merlin Quality Gate
2
+
3
+ Before stopping this session, evaluate:
4
+
5
+ 1. **Sights Usage**: Did you check Merlin Sights (merlin_get_context, merlin_find_files, merlin_search) before making code changes? If not, you should check now.
6
+
7
+ 2. **Uncommitted Work**: Are there uncommitted changes that should be saved? If so, suggest committing before stopping.
8
+
9
+ 3. **Checkpoint**: Has a Merlin checkpoint been saved for this session? If significant work was done, save one with merlin_save_checkpoint.
10
+
11
+ 4. **Verification**: If code was written, were basic checks run (build, types, lint)? If not, run merlin_run_verification first.
12
+
13
+ If all checks pass, respond with "STOP_APPROVED".
14
+ If any check fails, explain what should be done first before stopping.
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Merlin Hook: SubagentStart
4
+ # When a subagent/teammate starts, inject Merlin Sights context.
5
+ # Advisory only — always exits 0, never blocks subagent startup.
6
+ #
7
+ set -euo pipefail
8
+ trap 'exit 0' ERR
9
+
10
+ HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+
12
+ # Source shared libraries
13
+ # shellcheck source=lib/analytics.sh
14
+ . "${HOOKS_DIR}/lib/analytics.sh"
15
+
16
+ # Read subagent info from stdin (JSON with agent name, task)
17
+ input=""
18
+ if [ ! -t 0 ]; then
19
+ input=$(cat 2>/dev/null || true)
20
+ fi
21
+
22
+ # Extract task description if available
23
+ task_desc=""
24
+ if [ -n "$input" ] && command -v jq >/dev/null 2>&1; then
25
+ task_desc=$(echo "$input" | jq -r '.task // .description // empty' 2>/dev/null || true)
26
+ fi
27
+
28
+ # Log subagent start event
29
+ log_event "subagent_start" "$(printf '{"task":"%s"}' "${task_desc:-unknown}")"
30
+
31
+ # If Merlin CLI available, fetch and output context for the task
32
+ if [ -n "$task_desc" ] && command -v merlin >/dev/null 2>&1; then
33
+ merlin context "$task_desc" 2>/dev/null || true
34
+ fi
35
+
36
+ exit 0
@@ -0,0 +1,14 @@
1
+ # Task Completion Verification - Merlin Quality Gate
2
+
3
+ Before marking this task as complete, verify:
4
+
5
+ 1. **Code Quality**: Does the implementation follow the project's coding conventions? Check with merlin_get_conventions if unsure.
6
+
7
+ 2. **Sights Alignment**: Is the code consistent with existing patterns? Verify with merlin_get_context for the modified areas.
8
+
9
+ 3. **No Regressions**: Run the project's test suite if it exists. Check build passes.
10
+
11
+ 4. **Documentation**: If the task added new APIs or features, are they documented?
12
+
13
+ If all verifications pass, allow task completion.
14
+ If issues are found, list them and suggest fixes before completing.
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Merlin Hook: Notification (task_completed)
4
+ # Runs lightweight verification checks when a task completes.
5
+ # Informational only — always exits 0 for v1.
6
+ #
7
+ set -euo pipefail
8
+ trap 'exit 0' ERR
9
+
10
+ HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+
12
+ # Source shared libraries
13
+ # shellcheck source=lib/analytics.sh
14
+ . "${HOOKS_DIR}/lib/analytics.sh"
15
+
16
+ # Log task completion
17
+ log_event "task_completed" '{}'
18
+
19
+ # Check for build script in package.json
20
+ if [ -f "package.json" ] && command -v jq >/dev/null 2>&1; then
21
+ build_script=$(jq -r '.scripts.build // empty' package.json 2>/dev/null || true)
22
+ if [ -n "$build_script" ]; then
23
+ build_output=$(npm run build --if-present 2>&1) || true
24
+ build_exit=$?
25
+ if [ "$build_exit" -ne 0 ]; then
26
+ log_event "build_failed" "$(printf '{"exit_code":%d}' "$build_exit")"
27
+ echo "Merlin: Build check failed (exit $build_exit)" >&2
28
+ else
29
+ log_event "build_passed" '{}'
30
+ fi
31
+ fi
32
+ fi
33
+
34
+ # Check TypeScript compilation
35
+ if [ -f "tsconfig.json" ] && command -v npx >/dev/null 2>&1; then
36
+ tsc_output=$(npx tsc --noEmit 2>&1) || true
37
+ tsc_exit=$?
38
+ if [ "$tsc_exit" -ne 0 ]; then
39
+ log_event "typecheck_failed" "$(printf '{"exit_code":%d}' "$tsc_exit")"
40
+ echo "Merlin: Type check failed (exit $tsc_exit)" >&2
41
+ else
42
+ log_event "typecheck_passed" '{}'
43
+ fi
44
+ fi
45
+
46
+ exit 0
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Merlin Hook: TeammateIdle
4
+ # When a teammate finishes work, verify quality against Sights patterns.
5
+ # Advisory only — always exits 0, never blocks teammates.
6
+ #
7
+ set -euo pipefail
8
+ trap 'exit 0' ERR
9
+
10
+ HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+
12
+ # Source shared libraries
13
+ # shellcheck source=lib/analytics.sh
14
+ . "${HOOKS_DIR}/lib/analytics.sh"
15
+
16
+ # Read teammate work summary from stdin (JSON from Claude Code)
17
+ input=""
18
+ if [ ! -t 0 ]; then
19
+ input=$(cat 2>/dev/null || true)
20
+ fi
21
+
22
+ # Extract modified files if available
23
+ modified_files=""
24
+ if [ -n "$input" ] && command -v jq >/dev/null 2>&1; then
25
+ modified_files=$(echo "$input" | jq -r '.files_modified // empty' 2>/dev/null || true)
26
+ fi
27
+
28
+ # Log teammate idle event
29
+ log_event "teammate_idle_verify" "$(printf '{"modified_files":"%s"}' "${modified_files:-none}")"
30
+
31
+ # If Merlin CLI is available, run quick Sights check on modified files
32
+ if [ -n "$modified_files" ] && command -v merlin >/dev/null 2>&1; then
33
+ merlin context "verify changes in ${modified_files}" >/dev/null 2>&1 &
34
+ fi
35
+
36
+ # Quick build check (non-blocking, advisory only)
37
+ if command -v npm >/dev/null 2>&1; then
38
+ npm run build --if-present >/dev/null 2>&1 || true
39
+ fi
40
+
41
+ exit 0
@@ -21,13 +21,24 @@ CACHED_TRAJECTORY=""
21
21
  # ═══════════════════════════════════════════════════════════════════════════════
22
22
 
23
23
  get_api_key() {
24
- # Check environment variable first
25
- if [ -n "$MERLIN_API_KEY" ]; then
26
- echo "$MERLIN_API_KEY"
24
+ # Check environment variable first (use :- to avoid crash with set -u)
25
+ if [ -n "${MERLIN_API_KEY:-}" ]; then
26
+ echo "${MERLIN_API_KEY}"
27
27
  return
28
28
  fi
29
29
 
30
- # Check ~/.merlin/config.json
30
+ # Check Claude Code MCP config (where most users have their API key)
31
+ local claude_config="$HOME/.claude/config.json"
32
+ if [ -f "$claude_config" ] && command -v jq &> /dev/null; then
33
+ local key
34
+ key=$(jq -r '.mcpServers.merlin.env.MERLIN_API_KEY // empty' "$claude_config" 2>/dev/null)
35
+ if [ -n "$key" ]; then
36
+ echo "$key"
37
+ return
38
+ fi
39
+ fi
40
+
41
+ # Check ~/.merlin/config.json (legacy location)
31
42
  local config_file="$HOME/.merlin/config.json"
32
43
  if [ -f "$config_file" ] && command -v jq &> /dev/null; then
33
44
  local key
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Merlin Loop - Agent Teams Integration
4
+ # Enables parallel specialist execution using Claude Code Agent Teams.
5
+ #
6
+ # When CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 is set and the user passes
7
+ # --teams, wave plans execute as parallel teammates instead of sequential
8
+ # --agent -p invocations.
9
+ #
10
+ # Architecture:
11
+ # teams_available() - Check if Agent Teams feature flag is set
12
+ # teams_enabled() - Check if user opted in AND feature is available
13
+ # execute_wave_teams() - Run wave plans as parallel teammates
14
+ # execute_wave_sequential() - Fallback: run plans one by one
15
+ # execute_wave() - Dispatcher: picks teams or sequential
16
+ #
17
+
18
+ # ═══════════════════════════════════════════════════════════════════════════════
19
+ # Detection
20
+ # ═══════════════════════════════════════════════════════════════════════════════
21
+
22
+ # Check if Agent Teams runtime is available (feature flag)
23
+ teams_available() {
24
+ [ "${CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS:-0}" = "1" ]
25
+ }
26
+
27
+ # Check if teams mode is enabled by user AND runtime supports it
28
+ teams_enabled() {
29
+ [ "${MERLIN_TEAMS_MODE:-false}" = "true" ] && teams_available
30
+ }
31
+
32
+ # ═══════════════════════════════════════════════════════════════════════════════
33
+ # Wave Execution: Agent Teams (Parallel)
34
+ # ═══════════════════════════════════════════════════════════════════════════════
35
+
36
+ execute_wave_teams() {
37
+ local wave_number="$1"
38
+ shift
39
+ local plan_files=("$@")
40
+
41
+ local plan_count=${#plan_files[@]}
42
+ echo -e "${MAGENTA:-}Agent Teams: Executing wave $wave_number with $plan_count parallel teammates${RESET:-}"
43
+
44
+ # Build the team lead prompt — coordinates teammates, doesn't implement
45
+ local team_prompt="You are the Merlin Team Lead coordinating wave $wave_number execution.
46
+
47
+ You have $plan_count teammates, each assigned to one plan. Delegate each plan to its teammate.
48
+ Do NOT implement yourself — only coordinate and verify results.
49
+
50
+ Plans for this wave:"
51
+
52
+ for plan_file in "${plan_files[@]}"; do
53
+ local plan_name
54
+ plan_name=$(basename "$plan_file" -PLAN.md)
55
+ team_prompt="${team_prompt}
56
+ - ${plan_name}: ${plan_file}"
57
+ done
58
+
59
+ team_prompt="${team_prompt}
60
+
61
+ Instructions:
62
+ 1. Assign each plan to a separate teammate using the Task tool
63
+ 2. Each teammate should read their PLAN.md and execute all tasks
64
+ 3. Each teammate should commit their work atomically
65
+ 4. Wait for all teammates to complete
66
+ 5. Verify results and report back with a summary
67
+
68
+ Use the Task tool to delegate to teammates. Each teammate gets fresh context."
69
+
70
+ # Execute with Agent Teams enabled — the team lead orchestrates
71
+ local exit_code=0
72
+ set +e
73
+ echo "$team_prompt" | CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 \
74
+ claude --agent merlin --output-format stream-json 2>&1
75
+ exit_code=$?
76
+ set -e
77
+
78
+ if [ $exit_code -ne 0 ]; then
79
+ echo -e "${RED:-}Agent Teams wave $wave_number failed (exit $exit_code)${RESET:-}"
80
+ echo -e "${YELLOW:-}Falling back to sequential execution...${RESET:-}"
81
+ execute_wave_sequential "$wave_number" "${plan_files[@]}"
82
+ return $?
83
+ fi
84
+
85
+ return 0
86
+ }
87
+
88
+ # ═══════════════════════════════════════════════════════════════════════════════
89
+ # Wave Execution: Sequential Fallback
90
+ # ═══════════════════════════════════════════════════════════════════════════════
91
+
92
+ execute_wave_sequential() {
93
+ local wave_number="$1"
94
+ shift
95
+ local plan_files=("$@")
96
+
97
+ echo -e "${BLUE:-}Sequential execution: wave $wave_number (${#plan_files[@]} plans)${RESET:-}"
98
+
99
+ for plan_file in "${plan_files[@]}"; do
100
+ local plan_name
101
+ plan_name=$(basename "$plan_file" -PLAN.md)
102
+ echo -e "${CYAN:-}Executing: $plan_name${RESET:-}"
103
+
104
+ # Use existing --agent -p pattern (fresh 200K context per plan)
105
+ local exit_code=0
106
+ set +e
107
+ cat "$plan_file" | claude --agent merlin-executor --output-format stream-json 2>&1
108
+ exit_code=$?
109
+ set -e
110
+
111
+ if [ $exit_code -ne 0 ]; then
112
+ echo -e "${RED:-}Plan $plan_name failed (exit $exit_code)${RESET:-}"
113
+ return $exit_code
114
+ fi
115
+
116
+ echo -e "${GREEN:-}Plan $plan_name completed${RESET:-}"
117
+ done
118
+
119
+ return 0
120
+ }
121
+
122
+ # ═══════════════════════════════════════════════════════════════════════════════
123
+ # Dispatcher
124
+ # ═══════════════════════════════════════════════════════════════════════════════
125
+
126
+ # Main wave execution entry point — picks teams or sequential
127
+ execute_wave() {
128
+ local wave_number="$1"
129
+ shift
130
+ local plan_files=("$@")
131
+
132
+ # Single plan: no point using teams
133
+ if [ ${#plan_files[@]} -le 1 ]; then
134
+ execute_wave_sequential "$wave_number" "${plan_files[@]}"
135
+ return $?
136
+ fi
137
+
138
+ if teams_enabled; then
139
+ execute_wave_teams "$wave_number" "${plan_files[@]}"
140
+ else
141
+ execute_wave_sequential "$wave_number" "${plan_files[@]}"
142
+ fi
143
+ }
@@ -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/teams.sh" 2>/dev/null || true # Agent Teams integration
43
44
  source "$SCRIPT_DIR/lib/boot.sh" 2>/dev/null || true # Boot sequence
44
45
  source "$SCRIPT_DIR/lib/session-end.sh" 2>/dev/null || true # Session end protocol
45
46
  source "$SCRIPT_DIR/lib/tui.sh" 2>/dev/null || true # Interactive TUI
@@ -182,6 +183,15 @@ show_launch_screen() {
182
183
  echo -e " ${CYAN}Pause Timeout:${RESET} ${BOLD}$(get_timeout_status 2>/dev/null || echo "${PAUSE_TIMEOUT}s")${RESET}"
183
184
  echo -e " ${CYAN}Cloud Sync:${RESET} ${BOLD}$CLOUD_SYNC${RESET}"
184
185
  echo -e " ${CYAN}Task List:${RESET} ${BOLD}${TASK_LIST_ID:-auto}${RESET}"
186
+ if type teams_available &> /dev/null; then
187
+ if teams_enabled 2>/dev/null; then
188
+ echo -e " ${CYAN}Agent Teams:${RESET} ${GREEN}Enabled (parallel waves)${RESET}"
189
+ elif teams_available 2>/dev/null; then
190
+ echo -e " ${CYAN}Agent Teams:${RESET} ${YELLOW}Available${RESET} (use --teams to enable)"
191
+ else
192
+ echo -e " ${CYAN}Agent Teams:${RESET} ${YELLOW}Not available${RESET} (experimental)"
193
+ fi
194
+ fi
185
195
  echo ""
186
196
  echo -e " ${MAGENTA}───────────────────────────────────────────────────────────────${RESET}"
187
197
  echo ""
@@ -747,6 +757,7 @@ usage() {
747
757
  echo " --max N Maximum iterations (default: 50)"
748
758
  echo " --cooldown N Seconds between iterations (default: 2)"
749
759
  echo " --no-sync Disable cloud sync"
760
+ echo " --teams Enable Agent Teams for parallel wave execution (experimental)"
750
761
  echo " --afk AFK mode (stricter safety, shorter timeout, auto mode)"
751
762
  echo " --quiet Minimal output"
752
763
  echo ""
@@ -756,6 +767,7 @@ usage() {
756
767
  echo " merlin-loop --mode auto build # Fully automated, no pauses"
757
768
  echo " merlin-loop --mode interactive build # Pause after every task"
758
769
  echo " merlin-loop --max 100 build # Allow up to 100 iterations"
770
+ echo " merlin-loop --teams build # Parallel wave execution (experimental)"
759
771
  echo " merlin-loop --afk auto # Run unattended (auto mode)"
760
772
  }
761
773
 
@@ -799,6 +811,10 @@ parse_args() {
799
811
  CLOUD_SYNC="false"
800
812
  shift
801
813
  ;;
814
+ --teams)
815
+ MERLIN_TEAMS_MODE="true"
816
+ shift
817
+ ;;
802
818
  --afk)
803
819
  # AFK mode: stricter limits, auto mode, shorter timeout, more safety
804
820
  CIRCUIT_BREAKER_THRESHOLD=3