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.
- package/bin/install.cjs +125 -12
- package/bin/merlin-cli.cjs +26 -0
- package/files/agents/code-organization-supervisor.md +1 -0
- package/files/agents/context-guardian.md +1 -0
- package/files/agents/docs-keeper.md +2 -1
- package/files/agents/dry-refactor.md +2 -1
- package/files/agents/elite-code-refactorer.md +1 -0
- package/files/agents/hardening-guard.md +2 -1
- package/files/agents/implementation-dev.md +2 -1
- package/files/agents/merlin-api-designer.md +1 -0
- package/files/agents/merlin-codebase-mapper.md +1 -0
- package/files/agents/merlin-debugger.md +1 -0
- package/files/agents/merlin-executor.md +1 -0
- package/files/agents/merlin-frontend.md +1 -0
- package/files/agents/merlin-integration-checker.md +1 -0
- package/files/agents/merlin-migrator.md +1 -0
- package/files/agents/merlin-milestone-auditor.md +1 -0
- package/files/agents/merlin-performance.md +1 -0
- package/files/agents/merlin-planner.md +2 -0
- package/files/agents/merlin-researcher.md +1 -0
- package/files/agents/merlin-reviewer.md +2 -0
- package/files/agents/merlin-security.md +2 -0
- package/files/agents/merlin-verifier.md +2 -0
- package/files/agents/merlin-work-verifier.md +1 -0
- package/files/agents/merlin.md +1 -0
- package/files/agents/ops-railway.md +2 -1
- package/files/agents/orchestrator-retrofit.md +1 -0
- package/files/agents/product-spec.md +3 -1
- package/files/agents/system-architect.md +3 -1
- package/files/agents/tests-qa.md +2 -1
- package/files/hooks/agent-sync.sh +44 -0
- package/files/hooks/lib/analytics.sh +74 -0
- package/files/hooks/lib/sights-check.sh +58 -0
- package/files/hooks/post-edit-logger.sh +33 -0
- package/files/hooks/pre-edit-sights-check.sh +38 -0
- package/files/hooks/session-end.sh +27 -0
- package/files/hooks/session-start.sh +31 -0
- package/files/hooks/stop-check.md +14 -0
- package/files/hooks/subagent-context.sh +36 -0
- package/files/hooks/task-completed-verify.md +14 -0
- package/files/hooks/task-completed-verify.sh +46 -0
- package/files/hooks/teammate-idle-verify.sh +41 -0
- package/files/loop/lib/sights.sh +15 -4
- package/files/loop/lib/teams.sh +143 -0
- package/files/loop/merlin-loop.sh +16 -0
- package/files/merlin/agents-sync.sh +163 -0
- package/files/merlin/analytics.sh +159 -0
- 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
|
package/files/loop/lib/sights.sh
CHANGED
|
@@ -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
|
|
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
|