create-merlin-brain 3.5.1 → 3.5.3

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 CHANGED
@@ -937,15 +937,18 @@ async function install() {
937
937
  return hookArray;
938
938
  };
939
939
 
940
- // SessionStart hooks
940
+ // SessionStart: single consolidated hook (analytics + agent sync + context injection)
941
+ // Previously this was 3 separate hooks, causing 3x "startup hook success" messages.
942
+ // Now consolidated into session-start.sh which handles all three concerns.
941
943
  settings.hooks.SessionStart = settings.hooks.SessionStart || [];
942
- addHookIfMissing(settings.hooks.SessionStart, {
943
- hooks: [{ type: 'command', command: 'bash ~/.claude/hooks/session-start.sh' }]
944
+ // Remove old separate hooks if present (from previous installs)
945
+ settings.hooks.SessionStart = settings.hooks.SessionStart.filter(entry => {
946
+ const cmd = entry.hooks?.[0]?.command || '';
947
+ if (cmd.includes('agent-sync.sh') || cmd.includes('session-start-context.sh')) return false;
948
+ return true;
944
949
  });
945
-
946
- // SessionStart hook: Agent sync (background, runs at most once/hour)
947
950
  addHookIfMissing(settings.hooks.SessionStart, {
948
- hooks: [{ type: 'command', command: 'bash ~/.claude/hooks/agent-sync.sh' }]
951
+ hooks: [{ type: 'command', command: 'bash ~/.claude/hooks/session-start.sh' }]
949
952
  });
950
953
 
951
954
  // PreToolUse hook (Edit/Write only)
@@ -980,54 +983,54 @@ async function install() {
980
983
  hooks: [{ type: 'command', command: 'bash ~/.claude/hooks/subagent-context.sh' }]
981
984
  });
982
985
 
983
- // --- Prompt-based hooks (LLM evaluates .md content) ---
984
- // On update, old prompt hooks have stale content. Remove all Merlin prompt hooks
985
- // first (identified by containing 'merlin' or 'Merlin' in their prompt text),
986
- // then re-add fresh ones. This prevents duplicate hooks across version upgrades.
987
- const removeMerlinPromptHooks = (hookArray) => {
986
+ // --- Prompt-based hooks cleanup & registration ---
987
+ // Remove ALL prompt-type hooks from Merlin (identified by type: 'prompt').
988
+ // This prevents duplicates across version upgrades and removes broken hooks.
989
+ const removeAllPromptHooks = (hookArray) => {
988
990
  return hookArray.filter(entry => {
989
991
  if (!entry.hooks || !Array.isArray(entry.hooks)) return true;
990
- // Remove entry if ALL its hooks are Merlin prompt hooks
991
- const allMerlinPrompts = entry.hooks.every(h => {
992
- if (h.type !== 'prompt') return false;
993
- const prompt = (h.prompt || '').toLowerCase();
994
- return prompt.includes('merlin') || prompt.includes('sights');
995
- });
996
- return !allMerlinPrompts;
992
+ // Remove entry if ALL its hooks are prompt-type (Merlin is the only
993
+ // source of prompt hooks in settings.local.json)
994
+ const allPrompts = entry.hooks.every(h => h.type === 'prompt');
995
+ return !allPrompts;
997
996
  });
998
997
  };
999
998
 
1000
- settings.hooks.SessionStart = removeMerlinPromptHooks(settings.hooks.SessionStart);
1001
- settings.hooks.PreToolUse = removeMerlinPromptHooks(settings.hooks.PreToolUse);
1002
- settings.hooks.Stop = removeMerlinPromptHooks(settings.hooks.Stop);
999
+ settings.hooks.SessionStart = removeAllPromptHooks(settings.hooks.SessionStart);
1000
+ settings.hooks.PreToolUse = removeAllPromptHooks(settings.hooks.PreToolUse);
1001
+ settings.hooks.Stop = removeAllPromptHooks(settings.hooks.Stop);
1003
1002
  settings.hooks.Notification = settings.hooks.Notification || [];
1004
- settings.hooks.Notification = removeMerlinPromptHooks(settings.hooks.Notification);
1003
+ settings.hooks.Notification = removeAllPromptHooks(settings.hooks.Notification);
1005
1004
  settings.hooks.TaskCompleted = settings.hooks.TaskCompleted || [];
1006
- settings.hooks.TaskCompleted = removeMerlinPromptHooks(settings.hooks.TaskCompleted);
1005
+ settings.hooks.TaskCompleted = removeAllPromptHooks(settings.hooks.TaskCompleted);
1007
1006
 
1008
- // Now add fresh prompt/command hooks in new format
1009
- // NOTE: SessionStart does NOT support prompt hooks (only command hooks).
1010
- // The Merlin boot sequence is handled via CLAUDE.md instructions instead.
1011
- // We use a command hook to inject additionalContext at session start.
1012
- addHookIfMissing(settings.hooks.SessionStart, {
1013
- hooks: [{ type: 'command', command: 'bash ~/.claude/hooks/session-start-context.sh' }]
1014
- });
1007
+ // NOTE: The PreToolUse prompt hook has been REMOVED. It caused an infinite
1008
+ // rejection loop because the evaluator model cannot see conversation history
1009
+ // to verify whether merlin_get_context was called. The command-type hook
1010
+ // (pre-edit-sights-check.sh) handles advisory logging instead.
1011
+ // Sights enforcement is done via CLAUDE.md instructions, not hook blocking.
1015
1012
 
1016
- // Prompt-based PreToolUse hook: evaluate Sights check before edits
1017
- settings.hooks.PreToolUse.push({
1018
- matcher: 'Edit|Write',
1019
- hooks: [{ type: 'prompt', prompt: fs.readFileSync(path.join(HOOKS_DIR, 'pre-edit-sights-enforce.md'), 'utf8') }]
1020
- });
1021
-
1022
- // Prompt-based Stop hook: evaluate whether stopping is appropriate
1023
- settings.hooks.Stop.push({
1024
- hooks: [{ type: 'prompt', prompt: fs.readFileSync(path.join(HOOKS_DIR, 'stop-check.md'), 'utf8') }]
1025
- });
1026
-
1027
- // Task completion verification hook (using dedicated TaskCompleted event)
1028
- settings.hooks.TaskCompleted.push({
1029
- hooks: [{ type: 'prompt', prompt: fs.readFileSync(path.join(HOOKS_DIR, 'task-completed-verify.md'), 'utf8') }]
1030
- });
1013
+ // ═══════════════════════════════════════════════════════════════
1014
+ // PROMPT HOOKS PERMANENTLY REMOVED (v3.5.3)
1015
+ // ═══════════════════════════════════════════════════════════════
1016
+ // Stop and TaskCompleted prompt hooks have been REMOVED because:
1017
+ //
1018
+ // 1. The evaluator model (Haiku) frequently wraps JSON responses
1019
+ // in markdown code fences or adds explanatory text, causing
1020
+ // "JSON validation failed" errors on every session stop.
1021
+ //
1022
+ // 2. This is inherent to prompt-type hooks — they depend on a
1023
+ // small model producing byte-perfect JSON output, which is
1024
+ // unreliable.
1025
+ //
1026
+ // 3. Stop behavior is already governed by CLAUDE.md instructions.
1027
+ // Task completion verification is handled by the session's own
1028
+ // Claude instance, which has full conversation context (unlike
1029
+ // the tiny evaluator model).
1030
+ //
1031
+ // The removeAllPromptHooks() calls above (lines ~996-1002) will
1032
+ // clean up existing prompt hooks from previous installs.
1033
+ // ═══════════════════════════════════════════════════════════════
1031
1034
 
1032
1035
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
1033
1036
  logSuccess('Configured hooks in settings.local.json');
@@ -1,12 +1 @@
1
- You are evaluating whether a file edit should proceed. Context: $ARGUMENTS
2
-
3
- Check: Has the assistant recently called merlin_get_context, merlin_find_files, or merlin_search for this file's area in the current conversation? Look at recent tool calls in the transcript.
4
-
5
- Rules:
6
- - If Sights was consulted recently (within the last few messages) for this area: allow the edit
7
- - If this is a minor/trivial edit (formatting, typo fix, small change): allow the edit
8
- - If this is a significant edit and Sights was NOT consulted: suggest checking first
9
-
10
- Respond with ONLY valid JSON, no other text:
11
- - Allow the edit: {"ok": true}
12
- - Suggest Sights check first: {"ok": false, "reason": "Call merlin_get_context before editing this file to check for existing patterns and conventions"}
1
+ Always allow this edit. Respond with ONLY: {"ok": true}
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env bash
2
2
  #
3
- # Merlin Hook: SessionStart
4
- # Initializes analytics session and kicks off Sights context in background.
3
+ # Merlin Hook: SessionStart (consolidated)
4
+ # Does three things in a single hook (avoids 3x "success" messages):
5
+ # 1. Analytics: initialize session, log start event
6
+ # 2. Agent sync: refresh agent files from cloud (background, max once/hour)
7
+ # 3. Context injection: output additionalContext for the session
8
+ #
5
9
  # Always exits 0 — never blocks Claude Code startup.
6
10
  #
7
11
  set -euo pipefail
@@ -9,25 +13,73 @@ trap 'echo "{}"; exit 0' ERR
9
13
 
10
14
  HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
15
 
12
- # Source shared libraries
16
+ # ── 1. Analytics ────────────────────────────────────────────────
13
17
  # shellcheck source=lib/analytics.sh
14
18
  . "${HOOKS_DIR}/lib/analytics.sh"
15
19
  # shellcheck source=lib/sights-check.sh
16
20
  . "${HOOKS_DIR}/lib/sights-check.sh"
17
21
 
18
- # Initialize analytics session
19
22
  ensure_session_file
20
-
21
- # Log session start with working directory
22
23
  cwd="${PWD:-$(pwd)}"
23
24
  log_event "session_start" "$(printf '{"cwd":"%s"}' "$cwd")"
24
25
 
25
- # If merlin CLI is available, warm up Sights context in background
26
+ # Warm up Sights context in background
26
27
  if command -v merlin >/dev/null 2>&1; then
27
28
  merlin context "session start" >/dev/null 2>&1 &
28
29
  record_sights_call
29
30
  fi
30
31
 
31
- # Claude Code command hooks must output valid JSON to stdout
32
- echo '{}'
32
+ # ── 2. Agent sync (background, max once/hour) ──────────────────
33
+ _merlin_sync_agents() {
34
+ local agents_dir="${HOME}/.claude/agents"
35
+ local merlin_dir="${HOME}/.claude/merlin"
36
+ local last_sync="${merlin_dir}/.last-agent-sync"
37
+ local api_url="${MERLIN_API_URL:-https://api.merlin.build}"
38
+
39
+ [ -d "${agents_dir}" ] || return 0
40
+
41
+ # Skip if synced within the last hour
42
+ if [ -f "${last_sync}" ]; then
43
+ local last
44
+ last=$(cat "${last_sync}" 2>/dev/null || echo "0")
45
+ [ $(($(date +%s) - last)) -lt 3600 ] && return 0
46
+ fi
47
+
48
+ local installed=""
49
+ for f in "${agents_dir}"/*.md; do
50
+ [ -f "${f}" ] || continue
51
+ local name hash
52
+ name=$(basename "${f}" .md)
53
+ hash=$(md5sum "${f}" 2>/dev/null | cut -c1-8 || md5 -q "${f}" 2>/dev/null | cut -c1-8 || echo "unknown")
54
+ installed="${installed}${name}:${hash},"
55
+ done
56
+
57
+ local response
58
+ response=$(curl -s --max-time 5 "${api_url}/api/agents-sync/check?installed=${installed}" 2>/dev/null) || return 0
59
+ local stale_names
60
+ stale_names=$(echo "${response}" | grep -o '"name":"[^"]*"' | sed 's/"name":"//;s/"//' 2>/dev/null) || return 0
61
+ [ -z "${stale_names}" ] && { date +%s > "${last_sync}"; return 0; }
62
+
63
+ for agent_name in ${stale_names}; do
64
+ local content md_content
65
+ content=$(curl -s --max-time 5 "${api_url}/api/agents-sync/${agent_name}" 2>/dev/null) || continue
66
+ md_content=$(echo "${content}" | python3 -c "import sys,json;print(json.load(sys.stdin).get('content',''))" 2>/dev/null) || continue
67
+ [ -n "${md_content}" ] && echo "${md_content}" > "${agents_dir}/${agent_name}.md"
68
+ done
69
+ date +%s > "${last_sync}"
70
+ }
71
+ _merlin_sync_agents &
72
+
73
+ # ── 3. Context injection (the only stdout output) ──────────────
74
+ # Output additionalContext JSON for Claude to see at session start.
75
+ # Full boot instructions are in CLAUDE.md — this is a lightweight nudge.
76
+ cat <<'CONTEXT_JSON'
77
+ {
78
+ "hookSpecificOutput": {
79
+ "hookEventName": "SessionStart",
80
+ "additionalContext": "You are a Merlin-powered session. Before working: (1) call merlin_get_selected_repo to connect Sights, (2) call merlin_get_project_status to load state, (3) show numbered options. Check Sights before every edit. Route complex tasks to specialists via /merlin:route. Save checkpoints before stopping."
81
+ }
82
+ }
83
+ CONTEXT_JSON
84
+
33
85
  exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-merlin-brain",
3
- "version": "3.5.1",
3
+ "version": "3.5.3",
4
4
  "description": "Merlin - The Ultimate AI Brain for Claude Code. One install: workflows, agents, loop, and Sights MCP server.",
5
5
  "type": "module",
6
6
  "main": "./dist/server/index.js",