create-merlin-brain 3.10.0 → 3.12.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 (151) hide show
  1. package/bin/install.cjs +146 -22
  2. package/bin/runtime-adapters.cjs +396 -0
  3. package/dist/server/cost/tracker.d.ts +38 -2
  4. package/dist/server/cost/tracker.d.ts.map +1 -1
  5. package/dist/server/cost/tracker.js +87 -15
  6. package/dist/server/cost/tracker.js.map +1 -1
  7. package/dist/server/server.d.ts.map +1 -1
  8. package/dist/server/server.js +74 -30
  9. package/dist/server/server.js.map +1 -1
  10. package/dist/server/tools/adaptive.js +1 -1
  11. package/dist/server/tools/adaptive.js.map +1 -1
  12. package/dist/server/tools/agents-index.js +3 -3
  13. package/dist/server/tools/agents-index.js.map +1 -1
  14. package/dist/server/tools/agents.js +5 -5
  15. package/dist/server/tools/agents.js.map +1 -1
  16. package/dist/server/tools/behaviors.js +4 -4
  17. package/dist/server/tools/behaviors.js.map +1 -1
  18. package/dist/server/tools/context.js +7 -7
  19. package/dist/server/tools/context.js.map +1 -1
  20. package/dist/server/tools/cost.d.ts +3 -1
  21. package/dist/server/tools/cost.d.ts.map +1 -1
  22. package/dist/server/tools/cost.js +66 -13
  23. package/dist/server/tools/cost.js.map +1 -1
  24. package/dist/server/tools/discoveries.js +6 -6
  25. package/dist/server/tools/discoveries.js.map +1 -1
  26. package/dist/server/tools/index.d.ts +4 -0
  27. package/dist/server/tools/index.d.ts.map +1 -1
  28. package/dist/server/tools/index.js +4 -0
  29. package/dist/server/tools/index.js.map +1 -1
  30. package/dist/server/tools/learning.d.ts +12 -0
  31. package/dist/server/tools/learning.d.ts.map +1 -0
  32. package/dist/server/tools/learning.js +269 -0
  33. package/dist/server/tools/learning.js.map +1 -0
  34. package/dist/server/tools/project.js +7 -7
  35. package/dist/server/tools/project.js.map +1 -1
  36. package/dist/server/tools/promote.d.ts +11 -0
  37. package/dist/server/tools/promote.d.ts.map +1 -0
  38. package/dist/server/tools/promote.js +315 -0
  39. package/dist/server/tools/promote.js.map +1 -0
  40. package/dist/server/tools/route.d.ts.map +1 -1
  41. package/dist/server/tools/route.js +65 -24
  42. package/dist/server/tools/route.js.map +1 -1
  43. package/dist/server/tools/session-restore.d.ts +18 -0
  44. package/dist/server/tools/session-restore.d.ts.map +1 -0
  45. package/dist/server/tools/session-restore.js +154 -0
  46. package/dist/server/tools/session-restore.js.map +1 -0
  47. package/dist/server/tools/session-search.d.ts +16 -0
  48. package/dist/server/tools/session-search.d.ts.map +1 -0
  49. package/dist/server/tools/session-search.js +240 -0
  50. package/dist/server/tools/session-search.js.map +1 -0
  51. package/dist/server/tools/sights-index.js +2 -2
  52. package/dist/server/tools/sights-index.js.map +1 -1
  53. package/dist/server/tools/smart-route.d.ts.map +1 -1
  54. package/dist/server/tools/smart-route.js +4 -5
  55. package/dist/server/tools/smart-route.js.map +1 -1
  56. package/dist/server/tools/verification.js +1 -1
  57. package/dist/server/tools/verification.js.map +1 -1
  58. package/files/agents/code-organization-supervisor.md +9 -0
  59. package/files/agents/context-guardian.md +9 -0
  60. package/files/agents/docs-keeper.md +11 -1
  61. package/files/agents/dry-refactor.md +12 -1
  62. package/files/agents/elite-code-refactorer.md +10 -0
  63. package/files/agents/hardening-guard.md +13 -1
  64. package/files/agents/implementation-dev.md +12 -1
  65. package/files/agents/merlin-access-control-reviewer.md +248 -0
  66. package/files/agents/merlin-api-designer.md +9 -0
  67. package/files/agents/merlin-codebase-mapper.md +9 -1
  68. package/files/agents/merlin-debugger.md +10 -0
  69. package/files/agents/merlin-dependency-auditor.md +216 -0
  70. package/files/agents/merlin-executor.md +12 -1
  71. package/files/agents/merlin-frontend.md +9 -0
  72. package/files/agents/merlin-input-validator.md +247 -0
  73. package/files/agents/merlin-integration-checker.md +9 -1
  74. package/files/agents/merlin-migrator.md +9 -0
  75. package/files/agents/merlin-milestone-auditor.md +8 -0
  76. package/files/agents/merlin-performance.md +8 -0
  77. package/files/agents/merlin-planner.md +10 -0
  78. package/files/agents/merlin-researcher.md +10 -0
  79. package/files/agents/merlin-reviewer.md +42 -7
  80. package/files/agents/merlin-sast-reviewer.md +182 -0
  81. package/files/agents/merlin-secret-scanner.md +203 -0
  82. package/files/agents/merlin-security.md +9 -0
  83. package/files/agents/merlin-verifier.md +9 -0
  84. package/files/agents/merlin-work-verifier.md +9 -0
  85. package/files/agents/merlin.md +10 -0
  86. package/files/agents/ops-railway.md +11 -1
  87. package/files/agents/orchestrator-retrofit.md +9 -1
  88. package/files/agents/product-spec.md +11 -1
  89. package/files/agents/remotion.md +8 -0
  90. package/files/agents/system-architect.md +11 -1
  91. package/files/agents/tests-qa.md +12 -1
  92. package/files/commands/merlin/course-correct.md +219 -0
  93. package/files/commands/merlin/debug.md +2 -2
  94. package/files/commands/merlin/execute-phase.md +96 -199
  95. package/files/commands/merlin/execute-plan.md +118 -182
  96. package/files/commands/merlin/health.md +385 -0
  97. package/files/commands/merlin/loop-recipes.md +93 -36
  98. package/files/commands/merlin/map-codebase.md +4 -4
  99. package/files/commands/merlin/next.md +240 -0
  100. package/files/commands/merlin/optimize-prompts.md +158 -0
  101. package/files/commands/merlin/plan-phase.md +1 -1
  102. package/files/commands/merlin/profiles.md +215 -0
  103. package/files/commands/merlin/promote.md +176 -0
  104. package/files/commands/merlin/quick.md +229 -0
  105. package/files/commands/merlin/readiness-gate.md +208 -0
  106. package/files/commands/merlin/research-phase.md +2 -2
  107. package/files/commands/merlin/research-project.md +4 -4
  108. package/files/commands/merlin/resume-work.md +27 -1
  109. package/files/commands/merlin/route.md +43 -1
  110. package/files/commands/merlin/sandbox.md +359 -0
  111. package/files/commands/merlin/usage.md +55 -0
  112. package/files/commands/merlin/verify-work.md +1 -1
  113. package/files/docker/Dockerfile.merlin +20 -0
  114. package/files/docker/docker-compose.merlin.yml +23 -0
  115. package/files/hook-templates/auto-commit.sh +64 -0
  116. package/files/hook-templates/auto-format.sh +95 -0
  117. package/files/hook-templates/auto-test.sh +117 -0
  118. package/files/hook-templates/branch-protection.sh +72 -0
  119. package/files/hook-templates/changelog-reminder.sh +76 -0
  120. package/files/hook-templates/complexity-check.sh +112 -0
  121. package/files/hook-templates/import-audit.sh +83 -0
  122. package/files/hook-templates/license-header.sh +84 -0
  123. package/files/hook-templates/pr-description.sh +100 -0
  124. package/files/hook-templates/todo-tracker.sh +80 -0
  125. package/files/hooks/check-file-size.sh +17 -4
  126. package/files/hooks/config-change.sh +44 -16
  127. package/files/hooks/instructions-loaded.sh +22 -5
  128. package/files/hooks/notify-desktop.sh +157 -0
  129. package/files/hooks/notify-webhook.sh +141 -0
  130. package/files/hooks/pre-edit-sights-check.sh +76 -9
  131. package/files/hooks/security-scanner.sh +153 -0
  132. package/files/hooks/session-end-memory-sync.sh +97 -0
  133. package/files/hooks/session-end.sh +274 -1
  134. package/files/hooks/session-start.sh +19 -6
  135. package/files/hooks/smart-approve.sh +270 -0
  136. package/files/hooks/teammate-idle-verify.sh +87 -12
  137. package/files/hooks/worktree-create.sh +20 -3
  138. package/files/hooks/worktree-remove.sh +21 -3
  139. package/files/merlin/references/plan-format.md +37 -9
  140. package/files/merlin/sandbox.json +9 -0
  141. package/files/merlin/security.json +11 -0
  142. package/files/merlin/templates/ci/docs-update.yml +81 -0
  143. package/files/merlin/templates/ci/pr-review.yml +50 -0
  144. package/files/merlin/templates/ci/security-audit.yml +74 -0
  145. package/files/merlin/templates/config.json +9 -1
  146. package/files/rules/api-rules.md +30 -0
  147. package/files/rules/frontend-rules.md +25 -0
  148. package/files/rules/hooks-rules.md +36 -0
  149. package/files/rules/mcp-rules.md +30 -0
  150. package/files/rules/worker-rules.md +29 -0
  151. package/package.json +1 -1
@@ -176,16 +176,29 @@ _merlin_auto_update() {
176
176
  }
177
177
  _merlin_auto_update &
178
178
 
179
- # ── 3. Context injection (the only stdout output) ──────────────
179
+ # ── 3. Voice mode check (background) ──────────────────────────
180
+ # If voice_mode_concise is enabled in settings, export env var for the session.
181
+ _merlin_check_voice_mode() {
182
+ local settings_file="${HOME}/.claude/merlin/settings.local.json"
183
+ [ -f "${settings_file}" ] || return 0
184
+ command -v jq >/dev/null 2>&1 || return 0
185
+ local voice
186
+ voice=$(jq -r '.voice_mode_concise // false' "${settings_file}" 2>/dev/null) || return 0
187
+ [ "${voice}" = "true" ] && export MERLIN_VOICE_MODE=1
188
+ }
189
+ _merlin_check_voice_mode
190
+
191
+ # ── 4. Context injection (the only stdout output) ──────────────
180
192
  # Output additionalContext JSON for Claude to see at session start.
181
193
  # Full boot instructions are in CLAUDE.md — this is a lightweight nudge.
182
- cat <<'CONTEXT_JSON'
183
- {
194
+ _voice_note=""
195
+ [ "${MERLIN_VOICE_MODE:-}" = "1" ] && _voice_note=" Voice mode active: keep all responses short and direct."
196
+
197
+ printf '{
184
198
  "hookSpecificOutput": {
185
199
  "hookEventName": "SessionStart",
186
- "additionalContext": "STOP. Your FIRST action must be: call merlin_get_selected_repo, then call merlin_get_project_status, then show the user a status summary. Do not respond to the user until you complete these calls."
200
+ "additionalContext": "STOP. Your FIRST action must be: call merlin_get_selected_repo, then call merlin_get_project_status, then show the user a status summary. Do not respond to the user until you complete these calls.%s"
187
201
  }
188
- }
189
- CONTEXT_JSON
202
+ }\n' "${_voice_note}"
190
203
 
191
204
  exit 0
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Merlin Hook: smart-approve.sh
4
+ # Event: PreToolUse (Bash only)
5
+ #
6
+ # Auto-approves safe read-only commands and blocks known-dangerous ones.
7
+ # Unknown commands pass through (exit 0) to let Claude Code handle them.
8
+ #
9
+ # Exit codes:
10
+ # 0 = allow (safe command or unknown — pass through)
11
+ # 2 = block with JSON reason
12
+ #
13
+ set -euo pipefail
14
+ trap 'echo "{}"; exit 0' ERR
15
+
16
+ HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17
+ [ -f "${HOOKS_DIR}/lib/analytics.sh" ] && . "${HOOKS_DIR}/lib/analytics.sh"
18
+
19
+ # ─────────────────────────────────────────────────────────────────────────────
20
+ # Read stdin
21
+ # ─────────────────────────────────────────────────────────────────────────────
22
+ input=""
23
+ if [ ! -t 0 ]; then
24
+ input=$(cat 2>/dev/null || true)
25
+ fi
26
+
27
+ [ -z "$input" ] && { echo '{}'; exit 0; }
28
+
29
+ # ─────────────────────────────────────────────────────────────────────────────
30
+ # Extract the command string
31
+ # ─────────────────────────────────────────────────────────────────────────────
32
+ command_str=""
33
+ if command -v jq >/dev/null 2>&1; then
34
+ command_str=$(echo "$input" | jq -r '.tool_input.command // empty' 2>/dev/null || true)
35
+ else
36
+ command_str=$(echo "$input" | grep -o '"command":"[^"]*"' | head -1 | cut -d'"' -f4 || true)
37
+ fi
38
+
39
+ [ -z "$command_str" ] && { echo '{}'; exit 0; }
40
+
41
+ # ─────────────────────────────────────────────────────────────────────────────
42
+ # Helpers
43
+ # ─────────────────────────────────────────────────────────────────────────────
44
+ _block_cmd() {
45
+ local reason="$1"
46
+ if declare -f log_event >/dev/null 2>&1; then
47
+ log_event "smart_approve_block" "$(printf '{"reason":"%s"}' "$reason")"
48
+ fi
49
+ printf '{"decision":"block","reason":"%s"}\n' "$reason"
50
+ exit 2
51
+ }
52
+
53
+ # Extract the base (first) word of a command, stripping env var prefixes like KEY=val cmd
54
+ _base_cmd() {
55
+ local segment="$1"
56
+ # Strip leading VAR=val pairs
57
+ local stripped
58
+ stripped=$(echo "$segment" | sed 's/^[A-Z_][A-Z0-9_]*=[^ ]* //g' | sed 's/^[A-Z_][A-Z0-9_]*=[^ ]* //g')
59
+ echo "$stripped" | awk '{print $1}'
60
+ }
61
+
62
+ # ─────────────────────────────────────────────────────────────────────────────
63
+ # DANGEROUS PATTERNS — check first, before safe list
64
+ # These are hard blocks regardless of context.
65
+ # ─────────────────────────────────────────────────────────────────────────────
66
+
67
+ # Pipe-to-shell (curl/wget | bash/sh) — extremely dangerous, check raw command string
68
+ if echo "$command_str" | grep -qE '(curl|wget)\s[^|]*\|\s*(bash|sh|zsh|python|ruby|perl)\b' 2>/dev/null; then
69
+ _block_cmd "Dangerous: pipe-to-shell execution detected (curl/wget | bash/sh)"
70
+ fi
71
+
72
+ # sudo / su privilege escalation
73
+ if echo "$command_str" | grep -qE '(^|\s|;|&&|\|\|)(sudo|su)\s' 2>/dev/null; then
74
+ _block_cmd "Dangerous: privilege escalation (sudo/su) not permitted"
75
+ fi
76
+
77
+ # chmod 777 or chown root
78
+ if echo "$command_str" | grep -qE '(^|\s|;|&&|\|\|)(chmod\s+777|chown\s+root)' 2>/dev/null; then
79
+ _block_cmd "Dangerous: unsafe permission change (chmod 777 / chown root)"
80
+ fi
81
+
82
+ # Global package installs
83
+ if echo "$command_str" | grep -qE '(^|\s|;|&&|\|\|)npm\s+(install|i)\s+-g\b' 2>/dev/null; then
84
+ _block_cmd "Dangerous: global npm install (-g) requires explicit approval"
85
+ fi
86
+ if echo "$command_str" | grep -qE '(^|\s|;|&&|\|\|)pip\s+install\b' 2>/dev/null; then
87
+ # Allow pip install inside venv (if VIRTUAL_ENV is set — can't check at hook time, so just warn)
88
+ if ! echo "$command_str" | grep -qE '\-\-user\b|\bvenv\b|virtualenv' 2>/dev/null; then
89
+ _block_cmd "Dangerous: pip install outside virtualenv requires explicit approval"
90
+ fi
91
+ fi
92
+ if echo "$command_str" | grep -qE '(^|\s|;|&&|\|\|)gem\s+install\b' 2>/dev/null; then
93
+ _block_cmd "Dangerous: gem install requires explicit approval"
94
+ fi
95
+
96
+ # Network firewall mutation
97
+ if echo "$command_str" | grep -qE '(^|\s|;|&&|\|\|)(iptables|ufw|firewall-cmd)\b' 2>/dev/null; then
98
+ _block_cmd "Dangerous: firewall modification requires explicit approval"
99
+ fi
100
+
101
+ # ─────────────────────────────────────────────────────────────────────────────
102
+ # GIT DESTRUCTIVE SUBCOMMANDS — check before generic git safe list
103
+ # ─────────────────────────────────────────────────────────────────────────────
104
+ if echo "$command_str" | grep -qE '(^|\s|;|&&|\|\|)git\b' 2>/dev/null; then
105
+ # git push --force / --force-with-lease
106
+ if echo "$command_str" | grep -qE 'git\s+push\s+.*--force' 2>/dev/null; then
107
+ _block_cmd "Dangerous: git push --force can overwrite remote history"
108
+ fi
109
+ # git reset --hard
110
+ if echo "$command_str" | grep -qE 'git\s+reset\s+.*--hard' 2>/dev/null; then
111
+ _block_cmd "Dangerous: git reset --hard discards uncommitted changes"
112
+ fi
113
+ # git clean -f / -fd / -fx
114
+ if echo "$command_str" | grep -qE 'git\s+clean\s+.*-[a-z]*f' 2>/dev/null; then
115
+ _block_cmd "Dangerous: git clean -f deletes untracked files permanently"
116
+ fi
117
+ # git branch -D (force delete)
118
+ if echo "$command_str" | grep -qE 'git\s+branch\s+.*-D\b' 2>/dev/null; then
119
+ _block_cmd "Dangerous: git branch -D force-deletes without merge check"
120
+ fi
121
+ # git checkout . (discard all working dir changes)
122
+ if echo "$command_str" | grep -qE 'git\s+checkout\s+\.\s*$' 2>/dev/null; then
123
+ _block_cmd "Dangerous: git checkout . discards all working directory changes"
124
+ fi
125
+ fi
126
+
127
+ # ─────────────────────────────────────────────────────────────────────────────
128
+ # rm DESTRUCTIVE — rm -rf or rm -r
129
+ # ─────────────────────────────────────────────────────────────────────────────
130
+ if echo "$command_str" | grep -qE '(^|\s|;|&&|\|\|)rm\s' 2>/dev/null; then
131
+ if echo "$command_str" | grep -qE '\brm\s+(-[a-z]*r[a-z]*f|--recursive.*--force|-rf|-fr)\b' 2>/dev/null; then
132
+ _block_cmd "Dangerous: rm -rf detected — destructive recursive delete"
133
+ fi
134
+ if echo "$command_str" | grep -qE '\brm\s+(-[a-z]*r|-R|--recursive)\b' 2>/dev/null; then
135
+ _block_cmd "Dangerous: rm -r detected — recursive delete requires explicit approval"
136
+ fi
137
+ if echo "$command_str" | grep -qE '\bshred\b' 2>/dev/null; then
138
+ _block_cmd "Dangerous: shred detected — permanent file destruction"
139
+ fi
140
+ fi
141
+
142
+ # ─────────────────────────────────────────────────────────────────────────────
143
+ # SAFE COMMANDS — auto-approve (exit 0, output {})
144
+ # We split the command on pipe/semicolon/&& and check each segment.
145
+ # A command is safe only if ALL segments are safe.
146
+ # ─────────────────────────────────────────────────────────────────────────────
147
+
148
+ # Safe base command list (exact match on first word)
149
+ SAFE_CMDS="cat|head|tail|less|more|wc|file|stat|du|df|which|type|whereis|man|echo|printf|ls|find|tree|fd|grep|rg|ag|ack|fzf|jest|vitest|mocha|pytest|cargo|go|npm|yarn|pnpm|make|tsc|prettier|eslint|rustfmt|black|flake8|mypy|rubocop|uname|hostname|whoami|id|env|printenv|date|uptime|ps|top|git|node|python|python3"
150
+
151
+ # Git read-only subcommands
152
+ GIT_SAFE_SUBS="status|log|diff|branch|show|stash|remote|tag|blame|describe|ls-files|ls-remote|rev-parse|symbolic-ref|config --get|config --list|shortlog|whatchanged"
153
+
154
+ _is_safe_segment() {
155
+ local seg
156
+ seg=$(echo "$1" | xargs 2>/dev/null || echo "$1") # trim whitespace
157
+ [ -z "$seg" ] && return 0
158
+
159
+ local base
160
+ base=$(_base_cmd "$seg")
161
+ [ -z "$base" ] && return 0
162
+
163
+ # Check if base is in safe list
164
+ if ! echo "$base" | grep -qE "^(${SAFE_CMDS})$" 2>/dev/null; then
165
+ return 1 # not in safe list — unknown
166
+ fi
167
+
168
+ # Additional checks for commands that are only safe in certain forms
169
+ case "$base" in
170
+ git)
171
+ # Only allow read-only git subcommands
172
+ local sub
173
+ sub=$(echo "$seg" | awk '{print $2}')
174
+ if ! echo "$sub" | grep -qE "^(status|log|diff|branch|show|stash|remote|tag|blame|describe|ls-files|ls-remote|rev-parse|symbolic-ref|shortlog|whatchanged|config)$" 2>/dev/null; then
175
+ return 1 # git write op — not in our safe list
176
+ fi
177
+ ;;
178
+ make)
179
+ # make with clean or install targets is risky — not safe
180
+ if echo "$seg" | grep -qE '\b(clean|install|uninstall|distclean|mrproper)\b' 2>/dev/null; then
181
+ return 1
182
+ fi
183
+ ;;
184
+ npm)
185
+ # Only safe for read commands
186
+ local npm_sub
187
+ npm_sub=$(echo "$seg" | awk '{print $2}')
188
+ if ! echo "$npm_sub" | grep -qE "^(list|ls|outdated|info|search|audit|run|test|exec)$" 2>/dev/null; then
189
+ return 1
190
+ fi
191
+ ;;
192
+ yarn)
193
+ local yarn_sub
194
+ yarn_sub=$(echo "$seg" | awk '{print $2}')
195
+ if ! echo "$yarn_sub" | grep -qE "^(list|info|why|audit|run|test)$" 2>/dev/null; then
196
+ return 1
197
+ fi
198
+ ;;
199
+ pnpm)
200
+ local pnpm_sub
201
+ pnpm_sub=$(echo "$seg" | awk '{print $2}')
202
+ if ! echo "$pnpm_sub" | grep -qE "^(list|ls|info|why|audit|run|test)$" 2>/dev/null; then
203
+ return 1
204
+ fi
205
+ ;;
206
+ cargo)
207
+ local cargo_sub
208
+ cargo_sub=$(echo "$seg" | awk '{print $2}')
209
+ if ! echo "$cargo_sub" | grep -qE "^(test|check|clippy|build|run|bench|doc|fmt|tree|search)$" 2>/dev/null; then
210
+ return 1
211
+ fi
212
+ ;;
213
+ go)
214
+ local go_sub
215
+ go_sub=$(echo "$seg" | awk '{print $2}')
216
+ if ! echo "$go_sub" | grep -qE "^(test|vet|build|run|fmt|doc|list|mod|env|version)$" 2>/dev/null; then
217
+ return 1
218
+ fi
219
+ ;;
220
+ top)
221
+ # top -l 1 (one snapshot, macOS) or top -n 1 (Linux) is safe; interactive top is not
222
+ if ! echo "$seg" | grep -qE 'top\s+(-l\s+1|-n\s+1|--lines=1)\b' 2>/dev/null; then
223
+ return 1
224
+ fi
225
+ ;;
226
+ tsc)
227
+ # Only safe in check mode
228
+ if ! echo "$seg" | grep -qE 'tsc\s+.*--noEmit\b' 2>/dev/null; then
229
+ return 1
230
+ fi
231
+ ;;
232
+ rustfmt)
233
+ if ! echo "$seg" | grep -qE 'rustfmt\s+.*--check\b' 2>/dev/null; then
234
+ return 1
235
+ fi
236
+ ;;
237
+ black)
238
+ if ! echo "$seg" | grep -qE 'black\s+.*--check\b' 2>/dev/null; then
239
+ return 1
240
+ fi
241
+ ;;
242
+ esac
243
+
244
+ return 0
245
+ }
246
+
247
+ # Split on pipe, semicolon, &&, || and check each segment
248
+ # Replace separators with newlines, then iterate
249
+ all_safe=true
250
+ while IFS= read -r segment; do
251
+ [ -z "$segment" ] && continue
252
+ if ! _is_safe_segment "$segment"; then
253
+ all_safe=false
254
+ break
255
+ fi
256
+ done < <(echo "$command_str" | tr '|;&' '\n')
257
+
258
+ if $all_safe; then
259
+ if declare -f log_event >/dev/null 2>&1; then
260
+ log_event "smart_approve_safe" '{}'
261
+ fi
262
+ echo '{}'
263
+ exit 0
264
+ fi
265
+
266
+ # ─────────────────────────────────────────────────────────────────────────────
267
+ # Unknown command — pass through (let Claude Code decide)
268
+ # ─────────────────────────────────────────────────────────────────────────────
269
+ echo '{}'
270
+ exit 0
@@ -1,8 +1,15 @@
1
1
  #!/usr/bin/env bash
2
2
  #
3
3
  # Merlin Hook: TeammateIdle
4
- # When a teammate finishes work, verify quality against Sights patterns.
4
+ # Fires when a teammate finishes or goes idle in Agent Teams mode.
5
+ #
6
+ # Responsibilities:
7
+ # 1. Verify quality of completed work against Sights patterns
8
+ # 2. Detect if a teammate has been idle for 2+ minutes and alert the Lead
9
+ # 3. Log the event for analytics
10
+ #
5
11
  # Advisory only — always exits 0, never blocks teammates.
12
+ # Alert messages go to stderr (visible to Lead session).
6
13
  #
7
14
  set -euo pipefail
8
15
  trap 'echo "{}"; exit 0' ERR
@@ -11,31 +18,99 @@ HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
18
 
12
19
  # Source shared libraries
13
20
  # shellcheck source=lib/analytics.sh
14
- . "${HOOKS_DIR}/lib/analytics.sh"
21
+ [ -f "${HOOKS_DIR}/lib/analytics.sh" ] && . "${HOOKS_DIR}/lib/analytics.sh"
15
22
 
16
- # Read teammate work summary from stdin (JSON from Claude Code)
23
+ # ---------------------------------------------------------------------------
24
+ # Parse hook input (JSON from Claude Code Agent Teams runtime)
25
+ # ---------------------------------------------------------------------------
17
26
  input=""
18
27
  if [ ! -t 0 ]; then
19
28
  input=$(cat 2>/dev/null || true)
20
29
  fi
21
30
 
22
- # Extract modified files if available
31
+ teammate_id=""
32
+ teammate_name=""
23
33
  modified_files=""
34
+ idle_seconds=""
35
+ status=""
36
+
24
37
  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)
38
+ teammate_id=$(echo "$input" | jq -r '.teammate_id // empty' 2>/dev/null || true)
39
+ teammate_name=$(echo "$input" | jq -r '.teammate_name // empty' 2>/dev/null || true)
40
+ modified_files=$(echo "$input"| jq -r '.files_modified // empty' 2>/dev/null || true)
41
+ idle_seconds=$(echo "$input" | jq -r '.idle_seconds // empty' 2>/dev/null || true)
42
+ status=$(echo "$input" | jq -r '.status // empty' 2>/dev/null || true)
26
43
  fi
27
44
 
28
- # Log teammate idle event
29
- log_event "teammate_idle_verify" "$(printf '{"modified_files":"%s"}' "${modified_files:-none}")"
45
+ # ---------------------------------------------------------------------------
46
+ # Idle detection — alert Lead if teammate stuck for 2+ minutes (120 seconds)
47
+ # ---------------------------------------------------------------------------
48
+ IDLE_THRESHOLD=120 # seconds
49
+ ALERT_ISSUED=false
50
+
51
+ if [ -n "$idle_seconds" ] && [ "$idle_seconds" -ge "$IDLE_THRESHOLD" ] 2>/dev/null; then
52
+ IDLE_MINUTES=$(( idle_seconds / 60 ))
53
+ DISPLAY_NAME="${teammate_name:-${teammate_id:-unknown}}"
54
+
55
+ # Alert goes to stderr — visible to the Lead session in Agent Teams
56
+ echo "[merlin] Alert: Teammate \"${DISPLAY_NAME}\" has been idle for ${IDLE_MINUTES}+ minutes" >&2
57
+ echo "[merlin] Recommend: Check teammate status or consider aborting stuck session" >&2
58
+
59
+ # Write alert to a shared state file the Lead can poll
60
+ MERLIN_STATE_DIR="${HOME}/.claude/merlin/teams-state"
61
+ if [ -d "$MERLIN_STATE_DIR" ] || mkdir -p "$MERLIN_STATE_DIR" 2>/dev/null; then
62
+ ALERT_FILE="${MERLIN_STATE_DIR}/idle-alert-${teammate_id:-unknown}.json"
63
+ printf '{"teammate_id":"%s","teammate_name":"%s","idle_seconds":%s,"alert_time":"%s"}\n' \
64
+ "${teammate_id:-unknown}" \
65
+ "${DISPLAY_NAME}" \
66
+ "${idle_seconds}" \
67
+ "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
68
+ > "$ALERT_FILE" 2>/dev/null || true
69
+ fi
70
+
71
+ ALERT_ISSUED=true
72
+
73
+ # Log idle alert
74
+ if command -v log_event >/dev/null 2>&1; then
75
+ log_event "teammate_idle_alert" \
76
+ "$(printf '{"teammate_id":"%s","idle_seconds":%s}' \
77
+ "${teammate_id:-unknown}" "${idle_seconds}")"
78
+ fi
79
+ fi
80
+
81
+ # ---------------------------------------------------------------------------
82
+ # Completion verification — run when teammate finishes (status = completed)
83
+ # ---------------------------------------------------------------------------
84
+ if [ "${status}" = "completed" ] || [ -z "$status" ]; then
85
+ # Log teammate idle/completion event
86
+ if command -v log_event >/dev/null 2>&1; then
87
+ log_event "teammate_idle_verify" \
88
+ "$(printf '{"teammate_id":"%s","modified_files":"%s","alert_issued":%s}' \
89
+ "${teammate_id:-none}" \
90
+ "${modified_files:-none}" \
91
+ "${ALERT_ISSUED}")"
92
+ fi
93
+
94
+ # If Merlin CLI is available, run quick Sights check on modified files
95
+ if [ -n "$modified_files" ] && command -v merlin >/dev/null 2>&1; then
96
+ merlin context "verify changes in ${modified_files}" >/dev/null 2>&1 &
97
+ fi
30
98
 
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 &
99
+ # Clear any stale idle alert for this teammate (they finished)
100
+ if [ -n "$teammate_id" ]; then
101
+ MERLIN_STATE_DIR="${HOME}/.claude/merlin/teams-state"
102
+ rm -f "${MERLIN_STATE_DIR}/idle-alert-${teammate_id}.json" 2>/dev/null || true
103
+ fi
34
104
  fi
35
105
 
106
+ # ---------------------------------------------------------------------------
36
107
  # 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
108
+ # Only run when not in Agent Teams context to avoid redundant checks
109
+ # ---------------------------------------------------------------------------
110
+ if [ -z "${CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS:-}" ]; then
111
+ if command -v npm >/dev/null 2>&1; then
112
+ npm run build --if-present >/dev/null 2>&1 || true
113
+ fi
39
114
  fi
40
115
 
41
116
  # Claude Code command hooks must output valid JSON to stdout
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # Merlin Hook: WorktreeCreate
4
4
  # Purpose: Propagate .merlin/ config into git worktrees so sub-agents
5
- # spawned in worktrees have Sights access.
5
+ # spawned in worktrees have Sights access and full analytics continuity.
6
6
  #
7
7
  # Always exits 0 — never blocks Claude Code.
8
8
  #
@@ -23,7 +23,7 @@ if [ -z "$WORKTREE_PATH" ]; then
23
23
  exit 0
24
24
  fi
25
25
 
26
- # Copy Merlin config to worktree
26
+ # Copy project-level Merlin config to worktree
27
27
  if [ -d ".merlin" ]; then
28
28
  cp -r .merlin "$WORKTREE_PATH/.merlin" 2>/dev/null || true
29
29
  fi
@@ -33,9 +33,26 @@ if [ -f "CLAUDE.md" ] && [ ! -f "$WORKTREE_PATH/CLAUDE.md" ]; then
33
33
  cp CLAUDE.md "$WORKTREE_PATH/CLAUDE.md" 2>/dev/null || true
34
34
  fi
35
35
 
36
+ # Propagate essential Merlin state files so the worktree agent has
37
+ # immediate access to Sights context without a cold start.
38
+ MERLIN_DIR="${HOME}/.claude/merlin"
39
+ if [ -d "$MERLIN_DIR" ]; then
40
+ WORKTREE_MERLIN_DIR="$WORKTREE_PATH/.merlin-session"
41
+ mkdir -p "$WORKTREE_MERLIN_DIR" 2>/dev/null || true
42
+
43
+ # Copy last-sights-check so pre-edit hook doesn't warn immediately
44
+ [ -f "$MERLIN_DIR/.last-sights-check" ] && \
45
+ cp "$MERLIN_DIR/.last-sights-check" "$WORKTREE_MERLIN_DIR/.last-sights-check" 2>/dev/null || true
46
+
47
+ # Copy selected repo state for Sights context
48
+ [ -f "$MERLIN_DIR/.selected-repo" ] && \
49
+ cp "$MERLIN_DIR/.selected-repo" "$WORKTREE_MERLIN_DIR/.selected-repo" 2>/dev/null || true
50
+ fi
51
+
36
52
  # Log event if analytics available
37
53
  if declare -f log_event >/dev/null 2>&1; then
38
- log_event "worktree_create" "$(printf '{"path":"%s","agent_id":"%s","agent_type":"%s"}' "$WORKTREE_PATH" "$AGENT_ID" "$AGENT_TYPE")"
54
+ log_event "worktree_create" "$(printf '{"path":"%s","agent_id":"%s","agent_type":"%s"}' \
55
+ "$WORKTREE_PATH" "$AGENT_ID" "$AGENT_TYPE")"
39
56
  fi
40
57
 
41
58
  echo "Merlin: propagated config to worktree ${WORKTREE_PATH} (agent: ${AGENT_TYPE})" >&2
@@ -2,6 +2,7 @@
2
2
  #
3
3
  # Merlin Hook: WorktreeRemove
4
4
  # Purpose: Cleanup Merlin state from removed worktrees.
5
+ # Also logs worktree lifecycle duration to session analytics.
5
6
  #
6
7
  # Always exits 0 — never blocks Claude Code.
7
8
  #
@@ -22,15 +23,32 @@ if [ -z "$WORKTREE_PATH" ]; then
22
23
  exit 0
23
24
  fi
24
25
 
26
+ # Calculate worktree lifetime if we can (from creation timestamp)
27
+ CREATED_TS=""
28
+ if [ -f "$WORKTREE_PATH/.merlin-session/.last-sights-check" ]; then
29
+ CREATED_TS=$(cat "$WORKTREE_PATH/.merlin-session/.last-sights-check" 2>/dev/null || echo "")
30
+ fi
31
+ REMOVED_TS=$(date +%s)
32
+ LIFETIME_S=""
33
+ if [ -n "$CREATED_TS" ] && [ "$CREATED_TS" -gt 0 ] 2>/dev/null; then
34
+ LIFETIME_S=$(( REMOVED_TS - CREATED_TS ))
35
+ fi
36
+
25
37
  # Remove Merlin artifacts from worktree (if still present)
26
38
  rm -rf "$WORKTREE_PATH/.merlin" 2>/dev/null || true
39
+ rm -rf "$WORKTREE_PATH/.merlin-session" 2>/dev/null || true
27
40
 
28
- # Log event if analytics available
41
+ # Log worktree lifecycle event if analytics available
29
42
  if declare -f log_event >/dev/null 2>&1; then
30
- log_event "worktree_remove" "$(printf '{"path":"%s","agent_id":"%s","agent_type":"%s"}' "$WORKTREE_PATH" "$AGENT_ID" "$AGENT_TYPE")"
43
+ LIFETIME_VAL="${LIFETIME_S:-null}"
44
+ log_event "worktree_remove" "$(printf \
45
+ '{"path":"%s","agent_id":"%s","agent_type":"%s","lifetime_seconds":%s}' \
46
+ "$WORKTREE_PATH" "$AGENT_ID" "$AGENT_TYPE" "$LIFETIME_VAL")"
31
47
  fi
32
48
 
33
- echo "Merlin: cleaned up worktree ${WORKTREE_PATH} (agent: ${AGENT_TYPE})" >&2
49
+ LIFETIME_MSG=""
50
+ [ -n "$LIFETIME_S" ] && LIFETIME_MSG=" (lifetime: ${LIFETIME_S}s)"
51
+ echo "Merlin: cleaned up worktree ${WORKTREE_PATH}${LIFETIME_MSG} (agent: ${AGENT_TYPE})" >&2
34
52
 
35
53
  echo '{}'
36
54
  exit 0
@@ -345,11 +345,15 @@ Reference files that Claude needs to understand before implementing.
345
345
  </context_references>
346
346
 
347
347
  <verification_section>
348
- Overall phase verification (beyond individual task verification):
348
+ Overall phase verification (beyond individual task verification).
349
+
350
+ **Always use checkboxes** — VS Code plan view renders `- [ ]` as interactive checkboxes.
349
351
 
350
352
  ```markdown
351
353
  <verification>
352
- Before declaring phase complete:
354
+
355
+ ## Verification Checklist {#verification}
356
+
353
357
  - [ ] `npm run build` succeeds without errors
354
358
  - [ ] `npm test` passes all tests
355
359
  - [ ] No TypeScript errors
@@ -360,21 +364,45 @@ Before declaring phase complete:
360
364
  </verification_section>
361
365
 
362
366
  <success_criteria_section>
363
- Measurable criteria for phase completion:
367
+ Measurable criteria for phase completion.
368
+
369
+ **Always use checkboxes** — VS Code plan view renders these as trackable items.
370
+ **Always tag code blocks** with a language identifier for /copy compatibility (e.g., ` ```bash `, ` ```typescript `).
364
371
 
365
372
  ```markdown
366
373
  <success_criteria>
367
374
 
368
- - All tasks completed
369
- - All verification checks pass
370
- - No errors or warnings introduced
371
- - JWT auth flow works end-to-end
372
- - Protected routes redirect unauthenticated users
373
- </success_criteria>
375
+ ## Success Criteria {#success-criteria}
376
+
377
+ - [ ] All tasks completed
378
+ - [ ] All verification checks pass
379
+ - [ ] No errors or warnings introduced
380
+ - [ ] JWT auth flow works end-to-end
381
+ - [ ] Protected routes redirect unauthenticated users
382
+ </success_criteria>
374
383
  ```
375
384
 
376
385
  </success_criteria_section>
377
386
 
387
+ <vscode_plan_view>
388
+ PLAN.md files render in VS Code's plan view. To maximize compatibility:
389
+
390
+ 1. **Checkboxes for every trackable item** — use `- [ ]` in `<verification>` and `<success_criteria>` sections. VS Code renders these as interactive checkboxes.
391
+
392
+ 2. **Anchor headers for navigation** — use `## Section Name {#anchor}` for major sections so the plan view can jump to them.
393
+
394
+ 3. **Language-tagged code blocks** — always specify a language so /copy works cleanly:
395
+ ````markdown
396
+ ```bash
397
+ npm run build
398
+ ```
399
+ ````
400
+
401
+ 4. **Concise task names** — the plan view truncates long headings; keep `<name>` under 60 chars.
402
+
403
+ These rules apply to all generated PLAN.md files.
404
+ </vscode_plan_view>
405
+
378
406
  <output_section>
379
407
  Specify the SUMMARY.md structure:
380
408
 
@@ -0,0 +1,9 @@
1
+ {
2
+ "image": "merlin-sandbox:latest",
3
+ "network": false,
4
+ "cpu_limit": 2,
5
+ "memory_limit": "4g",
6
+ "mount_mode": "ro",
7
+ "auto_destroy": true,
8
+ "allowed_write_paths": ["/workspace-output"]
9
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "injection_scanner": {
3
+ "enabled": true,
4
+ "custom_patterns": []
5
+ },
6
+ "smart_approve": {
7
+ "enabled": true,
8
+ "always_allow": [],
9
+ "always_block": []
10
+ }
11
+ }