create-merlin-brain 3.11.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 (124) 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 +1 -0
  59. package/files/agents/context-guardian.md +1 -0
  60. package/files/agents/docs-keeper.md +1 -0
  61. package/files/agents/dry-refactor.md +1 -0
  62. package/files/agents/elite-code-refactorer.md +1 -0
  63. package/files/agents/hardening-guard.md +1 -0
  64. package/files/agents/implementation-dev.md +1 -0
  65. package/files/agents/merlin-access-control-reviewer.md +248 -0
  66. package/files/agents/merlin-codebase-mapper.md +1 -1
  67. package/files/agents/merlin-dependency-auditor.md +216 -0
  68. package/files/agents/merlin-executor.md +1 -0
  69. package/files/agents/merlin-input-validator.md +247 -0
  70. package/files/agents/merlin-reviewer.md +1 -0
  71. package/files/agents/merlin-sast-reviewer.md +182 -0
  72. package/files/agents/merlin-secret-scanner.md +203 -0
  73. package/files/agents/tests-qa.md +1 -0
  74. package/files/commands/merlin/execute-phase.md +94 -197
  75. package/files/commands/merlin/execute-plan.md +116 -180
  76. package/files/commands/merlin/health.md +385 -0
  77. package/files/commands/merlin/loop-recipes.md +93 -36
  78. package/files/commands/merlin/optimize-prompts.md +158 -0
  79. package/files/commands/merlin/profiles.md +215 -0
  80. package/files/commands/merlin/promote.md +176 -0
  81. package/files/commands/merlin/quick.md +229 -0
  82. package/files/commands/merlin/resume-work.md +27 -1
  83. package/files/commands/merlin/route.md +43 -1
  84. package/files/commands/merlin/sandbox.md +359 -0
  85. package/files/commands/merlin/usage.md +55 -0
  86. package/files/docker/Dockerfile.merlin +20 -0
  87. package/files/docker/docker-compose.merlin.yml +23 -0
  88. package/files/hook-templates/auto-commit.sh +64 -0
  89. package/files/hook-templates/auto-format.sh +95 -0
  90. package/files/hook-templates/auto-test.sh +117 -0
  91. package/files/hook-templates/branch-protection.sh +72 -0
  92. package/files/hook-templates/changelog-reminder.sh +76 -0
  93. package/files/hook-templates/complexity-check.sh +112 -0
  94. package/files/hook-templates/import-audit.sh +83 -0
  95. package/files/hook-templates/license-header.sh +84 -0
  96. package/files/hook-templates/pr-description.sh +100 -0
  97. package/files/hook-templates/todo-tracker.sh +80 -0
  98. package/files/hooks/check-file-size.sh +17 -4
  99. package/files/hooks/config-change.sh +44 -16
  100. package/files/hooks/instructions-loaded.sh +22 -5
  101. package/files/hooks/notify-desktop.sh +157 -0
  102. package/files/hooks/notify-webhook.sh +141 -0
  103. package/files/hooks/pre-edit-sights-check.sh +76 -9
  104. package/files/hooks/security-scanner.sh +153 -0
  105. package/files/hooks/session-end-memory-sync.sh +97 -0
  106. package/files/hooks/session-end.sh +274 -1
  107. package/files/hooks/session-start.sh +19 -6
  108. package/files/hooks/smart-approve.sh +270 -0
  109. package/files/hooks/teammate-idle-verify.sh +87 -12
  110. package/files/hooks/worktree-create.sh +20 -3
  111. package/files/hooks/worktree-remove.sh +21 -3
  112. package/files/merlin/references/plan-format.md +37 -9
  113. package/files/merlin/sandbox.json +9 -0
  114. package/files/merlin/security.json +11 -0
  115. package/files/merlin/templates/ci/docs-update.yml +81 -0
  116. package/files/merlin/templates/ci/pr-review.yml +50 -0
  117. package/files/merlin/templates/ci/security-audit.yml +74 -0
  118. package/files/merlin/templates/config.json +9 -1
  119. package/files/rules/api-rules.md +30 -0
  120. package/files/rules/frontend-rules.md +25 -0
  121. package/files/rules/hooks-rules.md +36 -0
  122. package/files/rules/mcp-rules.md +30 -0
  123. package/files/rules/worker-rules.md +29 -0
  124. package/package.json +1 -1
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Hook Template: auto-test.sh
4
+ # Event: PostToolUse (Write, Edit)
5
+ #
6
+ # Automatically runs the test suite (or targeted test file) after code changes.
7
+ # Detects the test framework and runs only tests related to the changed file.
8
+ #
9
+ # HOW TO INSTALL:
10
+ # Copy this file to ~/.claude/merlin/hooks/auto-test.sh
11
+ # Then add to your .claude/settings.local.json:
12
+ #
13
+ # {
14
+ # "hooks": {
15
+ # "PostToolUse": [
16
+ # {
17
+ # "matcher": "Write|Edit",
18
+ # "hooks": [{ "type": "command", "command": "~/.claude/merlin/hooks/auto-test.sh" }]
19
+ # }
20
+ # ]
21
+ # }
22
+ # }
23
+ #
24
+ # REQUIREMENTS: A test runner (jest, vitest, pytest, go test) must be available.
25
+ # BEHAVIOR: Advisory only — runs tests and echoes results to stderr. Never blocks.
26
+ # NOTE: Keep test suites fast. Slow suites make this hook annoying.
27
+ #
28
+ set -euo pipefail
29
+ trap 'echo "{}"; exit 0' ERR
30
+
31
+ # Read tool input from stdin
32
+ input=""
33
+ if [ ! -t 0 ]; then
34
+ input=$(cat 2>/dev/null || true)
35
+ fi
36
+
37
+ [ -z "$input" ] && { echo '{}'; exit 0; }
38
+
39
+ # Extract file path
40
+ file_path=""
41
+ if command -v jq >/dev/null 2>&1; then
42
+ file_path=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.path // empty' 2>/dev/null || true)
43
+ fi
44
+
45
+ [ -z "$file_path" ] || [ ! -f "$file_path" ] && { echo '{}'; exit 0; }
46
+
47
+ # Skip test files themselves, node_modules, configs
48
+ case "$file_path" in
49
+ *.test.*|*.spec.*|*__tests__*|*node_modules*|*dist/*|*build/*)
50
+ echo '{}'
51
+ exit 0
52
+ ;;
53
+ esac
54
+
55
+ # Only run for source code files
56
+ case "$file_path" in
57
+ *.js|*.jsx|*.ts|*.tsx|*.mjs|*.py|*.go|*.rb|*.java)
58
+ ;;
59
+ *)
60
+ echo '{}'
61
+ exit 0
62
+ ;;
63
+ esac
64
+
65
+ echo "auto-test: running tests for $file_path" >&2
66
+
67
+ # Detect and run test framework
68
+ if [ -f "package.json" ]; then
69
+ # Jest
70
+ if grep -q '"jest"' package.json 2>/dev/null || [ -f "jest.config.js" ] || [ -f "jest.config.ts" ]; then
71
+ RUNNER="node_modules/.bin/jest"
72
+ [ ! -f "$RUNNER" ] && RUNNER="jest"
73
+ if command -v "$RUNNER" >/dev/null 2>&1 || [ -f "$RUNNER" ]; then
74
+ # Try to run related tests only
75
+ $RUNNER --passWithNoTests --findRelatedTests "$file_path" --no-coverage 2>&1 | tail -5 >&2 || true
76
+ echo '{}'
77
+ exit 0
78
+ fi
79
+ fi
80
+
81
+ # Vitest
82
+ if grep -q '"vitest"' package.json 2>/dev/null || [ -f "vitest.config.ts" ] || [ -f "vitest.config.js" ]; then
83
+ RUNNER="node_modules/.bin/vitest"
84
+ [ ! -f "$RUNNER" ] && RUNNER="vitest"
85
+ if command -v "$RUNNER" >/dev/null 2>&1 || [ -f "$RUNNER" ]; then
86
+ $RUNNER run --reporter=verbose 2>&1 | tail -10 >&2 || true
87
+ echo '{}'
88
+ exit 0
89
+ fi
90
+ fi
91
+ fi
92
+
93
+ # Python: pytest
94
+ if [ "${file_path##*.}" = "py" ] && command -v pytest >/dev/null 2>&1; then
95
+ # Derive test file path convention: src/foo.py -> tests/test_foo.py
96
+ base=$(basename "$file_path" .py)
97
+ test_file="tests/test_${base}.py"
98
+ if [ -f "$test_file" ]; then
99
+ pytest "$test_file" -q 2>&1 | tail -5 >&2 || true
100
+ else
101
+ pytest -q --co -q 2>&1 | tail -5 >&2 || true
102
+ fi
103
+ echo '{}'
104
+ exit 0
105
+ fi
106
+
107
+ # Go: go test
108
+ if [ "${file_path##*.}" = "go" ] && command -v go >/dev/null 2>&1; then
109
+ pkg=$(dirname "$file_path")
110
+ go test "./${pkg}/..." 2>&1 | tail -5 >&2 || true
111
+ echo '{}'
112
+ exit 0
113
+ fi
114
+
115
+ echo "auto-test: no test runner detected for $file_path" >&2
116
+ echo '{}'
117
+ exit 0
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Hook Template: branch-protection.sh
4
+ # Event: PreToolUse (Bash)
5
+ #
6
+ # Prevents Claude from directly committing or pushing to protected branches
7
+ # (main, master, production, release/*). Forces work onto feature branches.
8
+ #
9
+ # HOW TO INSTALL:
10
+ # Copy this file to ~/.claude/merlin/hooks/branch-protection.sh
11
+ # Then add to your .claude/settings.local.json:
12
+ #
13
+ # {
14
+ # "hooks": {
15
+ # "PreToolUse": [
16
+ # {
17
+ # "matcher": "Bash",
18
+ # "hooks": [{ "type": "command", "command": "~/.claude/merlin/hooks/branch-protection.sh" }]
19
+ # }
20
+ # ]
21
+ # }
22
+ # }
23
+ #
24
+ # BEHAVIOR: Blocks git commit and git push when on a protected branch.
25
+ # Exit 2 + JSON decision:block to prevent the command.
26
+ #
27
+ # CUSTOMIZE: Edit PROTECTED_BRANCHES below to match your workflow.
28
+ #
29
+ set -euo pipefail
30
+ trap 'echo "{}"; exit 0' ERR
31
+
32
+ # Protected branch patterns (regex)
33
+ PROTECTED_BRANCHES="^(main|master|production|staging|release/.*)$"
34
+
35
+ # Read tool input from stdin
36
+ input=""
37
+ if [ ! -t 0 ]; then
38
+ input=$(cat 2>/dev/null || true)
39
+ fi
40
+
41
+ [ -z "$input" ] && { echo '{}'; exit 0; }
42
+
43
+ # Extract the bash command being run
44
+ command_str=""
45
+ if command -v jq >/dev/null 2>&1; then
46
+ command_str=$(echo "$input" | jq -r '.tool_input.command // empty' 2>/dev/null || true)
47
+ fi
48
+
49
+ [ -z "$command_str" ] && { echo '{}'; exit 0; }
50
+
51
+ # Only check git commit and git push commands
52
+ if ! echo "$command_str" | grep -qE 'git\s+(commit|push)'; then
53
+ echo '{}'
54
+ exit 0
55
+ fi
56
+
57
+ # Only run if we're in a git repo
58
+ git rev-parse --git-dir >/dev/null 2>&1 || { echo '{}'; exit 0; }
59
+
60
+ # Get current branch
61
+ current_branch=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
62
+
63
+ [ -z "$current_branch" ] && { echo '{}'; exit 0; }
64
+
65
+ # Check if current branch is protected
66
+ if echo "$current_branch" | grep -qE "$PROTECTED_BRANCHES"; then
67
+ printf '{"decision":"block","reason":"Branch protection: direct commits to '\''%s'\'' are not allowed. Create a feature branch first: git checkout -b feat/your-change"}\n' "$current_branch"
68
+ exit 2
69
+ fi
70
+
71
+ echo '{}'
72
+ exit 0
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Hook Template: changelog-reminder.sh
4
+ # Event: Stop
5
+ #
6
+ # At the end of a session, checks if source code was changed but CHANGELOG.md
7
+ # was not updated. Injects a reminder as feedback if so.
8
+ #
9
+ # HOW TO INSTALL:
10
+ # Copy this file to ~/.claude/merlin/hooks/changelog-reminder.sh
11
+ # Then add to your .claude/settings.local.json:
12
+ #
13
+ # {
14
+ # "hooks": {
15
+ # "Stop": [
16
+ # {
17
+ # "hooks": [{ "type": "command", "command": "~/.claude/merlin/hooks/changelog-reminder.sh" }]
18
+ # }
19
+ # ]
20
+ # }
21
+ # }
22
+ #
23
+ # BEHAVIOR: Advisory only. Reminds to update CHANGELOG.md when code changed.
24
+ # Checks git diff for source code changes vs changelog changes.
25
+ #
26
+ set -euo pipefail
27
+ trap 'echo "{}"; exit 0' ERR
28
+
29
+ # Only run if we're in a git repo
30
+ git rev-parse --git-dir >/dev/null 2>&1 || { echo '{}'; exit 0; }
31
+
32
+ # Check if CHANGELOG.md (or CHANGELOG) exists at all
33
+ CHANGELOG=""
34
+ for candidate in CHANGELOG.md CHANGELOG.rst CHANGELOG.txt CHANGES.md; do
35
+ if [ -f "$candidate" ]; then
36
+ CHANGELOG="$candidate"
37
+ break
38
+ fi
39
+ done
40
+
41
+ [ -z "$CHANGELOG" ] && { echo '{}'; exit 0; }
42
+
43
+ # Get changed files since last commit (staged + unstaged)
44
+ changed_files=$(git diff --name-only HEAD 2>/dev/null || true)
45
+ staged_files=$(git diff --cached --name-only 2>/dev/null || true)
46
+ all_changed="${changed_files}${staged_files}"
47
+
48
+ [ -z "$all_changed" ] && { echo '{}'; exit 0; }
49
+
50
+ # Check if any source code files changed
51
+ source_changed=false
52
+ while IFS= read -r file; do
53
+ case "$file" in
54
+ *.js|*.jsx|*.ts|*.tsx|*.mjs|*.py|*.go|*.java|*.rb|*.rs|*.swift|*.c|*.cpp|*.h)
55
+ source_changed=true
56
+ break
57
+ ;;
58
+ esac
59
+ done <<< "$all_changed"
60
+
61
+ "$source_changed" || { echo '{}'; exit 0; }
62
+
63
+ # Check if CHANGELOG was also updated
64
+ changelog_updated=false
65
+ if echo "$all_changed" | grep -q "$CHANGELOG"; then
66
+ changelog_updated=true
67
+ fi
68
+
69
+ if ! "$changelog_updated"; then
70
+ echo "REMINDER: Source code was changed but ${CHANGELOG} was not updated. Consider adding an entry describing what changed." >&2
71
+ echo '{}'
72
+ exit 2
73
+ fi
74
+
75
+ echo '{}'
76
+ exit 0
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Hook Template: complexity-check.sh
4
+ # Event: PostToolUse (Write, Edit)
5
+ #
6
+ # Warns when a file has too many functions — a proxy for high cognitive complexity.
7
+ # Encourages splitting large files into smaller, focused modules.
8
+ #
9
+ # HOW TO INSTALL:
10
+ # Copy this file to ~/.claude/merlin/hooks/complexity-check.sh
11
+ # Then add to your .claude/settings.local.json:
12
+ #
13
+ # {
14
+ # "hooks": {
15
+ # "PostToolUse": [
16
+ # {
17
+ # "matcher": "Write|Edit",
18
+ # "hooks": [{ "type": "command", "command": "~/.claude/merlin/hooks/complexity-check.sh" }]
19
+ # }
20
+ # ]
21
+ # }
22
+ # }
23
+ #
24
+ # BEHAVIOR: Advisory only. Warns via exit 2 feedback when function count is high.
25
+ # CUSTOMIZE: Adjust MAX_FUNCTIONS and MAX_LINES below.
26
+ #
27
+ set -euo pipefail
28
+ trap 'echo "{}"; exit 0' ERR
29
+
30
+ MAX_FUNCTIONS=15 # Warn if file has more than this many functions
31
+ MAX_LINES=400 # Warn if file exceeds this many lines
32
+
33
+ # Read tool input from stdin
34
+ input=""
35
+ if [ ! -t 0 ]; then
36
+ input=$(cat 2>/dev/null || true)
37
+ fi
38
+
39
+ [ -z "$input" ] && { echo '{}'; exit 0; }
40
+
41
+ # Extract file path
42
+ file_path=""
43
+ if command -v jq >/dev/null 2>&1; then
44
+ file_path=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.path // empty' 2>/dev/null || true)
45
+ fi
46
+
47
+ [ -z "$file_path" ] || [ ! -f "$file_path" ] && { echo '{}'; exit 0; }
48
+
49
+ # Only check code files
50
+ case "$file_path" in
51
+ *.js|*.jsx|*.ts|*.tsx|*.mjs|*.py|*.go|*.java|*.rb|*.rs|*.swift)
52
+ ;;
53
+ *)
54
+ echo '{}'
55
+ exit 0
56
+ ;;
57
+ esac
58
+
59
+ # Skip generated/vendor files
60
+ case "$file_path" in
61
+ *node_modules*|*dist/*|*build/*|*.min.js|*vendor/*)
62
+ echo '{}'
63
+ exit 0
64
+ ;;
65
+ esac
66
+
67
+ # Count lines
68
+ line_count=$(wc -l < "$file_path" 2>/dev/null | tr -d ' ')
69
+
70
+ # Count function definitions (language-aware patterns)
71
+ func_count=0
72
+ case "$file_path" in
73
+ *.js|*.jsx|*.ts|*.tsx|*.mjs)
74
+ func_count=$(grep -cE '^\s*(export\s+)?(async\s+)?function\s+\w+|^\s*(const|let|var)\s+\w+\s*=\s*(async\s+)?\(' "$file_path" 2>/dev/null || echo 0)
75
+ ;;
76
+ *.py)
77
+ func_count=$(grep -cE '^\s*def\s+\w+' "$file_path" 2>/dev/null || echo 0)
78
+ ;;
79
+ *.go)
80
+ func_count=$(grep -cE '^func\s+' "$file_path" 2>/dev/null || echo 0)
81
+ ;;
82
+ *.java)
83
+ func_count=$(grep -cE '^\s*(public|private|protected|static|final)\s+\w.*\(.*\)\s*\{' "$file_path" 2>/dev/null || echo 0)
84
+ ;;
85
+ *.rb)
86
+ func_count=$(grep -cE '^\s*def\s+' "$file_path" 2>/dev/null || echo 0)
87
+ ;;
88
+ esac
89
+
90
+ # Ensure numeric
91
+ func_count=$(echo "$func_count" | tr -d ' ' | grep -E '^[0-9]+$' || echo 0)
92
+ line_count=$(echo "$line_count" | tr -d ' ' | grep -E '^[0-9]+$' || echo 0)
93
+
94
+ warn_parts=()
95
+
96
+ if [ "$func_count" -gt "$MAX_FUNCTIONS" ] 2>/dev/null; then
97
+ warn_parts+=("${func_count} functions (threshold: ${MAX_FUNCTIONS})")
98
+ fi
99
+
100
+ if [ "$line_count" -gt "$MAX_LINES" ] 2>/dev/null; then
101
+ warn_parts+=("${line_count} lines (threshold: ${MAX_LINES})")
102
+ fi
103
+
104
+ if [ "${#warn_parts[@]}" -gt 0 ]; then
105
+ msg="COMPLEXITY WARNING: ${file_path} has $(IFS=', '; echo "${warn_parts[*]}"). Consider splitting into smaller modules."
106
+ echo "$msg" >&2
107
+ echo '{}'
108
+ exit 2
109
+ fi
110
+
111
+ echo '{}'
112
+ exit 0
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Hook Template: import-audit.sh
4
+ # Event: PostToolUse (Write, Edit)
5
+ #
6
+ # Flags potentially unused imports in JavaScript/TypeScript files after edits.
7
+ # Uses a heuristic approach: checks if imported names appear elsewhere in the file.
8
+ #
9
+ # HOW TO INSTALL:
10
+ # Copy this file to ~/.claude/merlin/hooks/import-audit.sh
11
+ # Then add to your .claude/settings.local.json:
12
+ #
13
+ # {
14
+ # "hooks": {
15
+ # "PostToolUse": [
16
+ # {
17
+ # "matcher": "Write|Edit",
18
+ # "hooks": [{ "type": "command", "command": "~/.claude/merlin/hooks/import-audit.sh" }]
19
+ # }
20
+ # ]
21
+ # }
22
+ # }
23
+ #
24
+ # BEHAVIOR: Advisory only. Warns about likely unused imports via stderr + exit 2.
25
+ # NOTE: This is heuristic-based, not AST-based. False positives are possible.
26
+ # For production use, prefer ESLint's no-unused-vars / unused-imports rules.
27
+ #
28
+ set -euo pipefail
29
+ trap 'echo "{}"; exit 0' ERR
30
+
31
+ # Read tool input from stdin
32
+ input=""
33
+ if [ ! -t 0 ]; then
34
+ input=$(cat 2>/dev/null || true)
35
+ fi
36
+
37
+ [ -z "$input" ] && { echo '{}'; exit 0; }
38
+
39
+ # Extract file path
40
+ file_path=""
41
+ if command -v jq >/dev/null 2>&1; then
42
+ file_path=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.path // empty' 2>/dev/null || true)
43
+ fi
44
+
45
+ [ -z "$file_path" ] || [ ! -f "$file_path" ] && { echo '{}'; exit 0; }
46
+
47
+ # Only check JS/TS files
48
+ case "$file_path" in
49
+ *.js|*.jsx|*.ts|*.tsx|*.mjs) ;;
50
+ *) echo '{}'; exit 0 ;;
51
+ esac
52
+
53
+ # Skip node_modules and generated files
54
+ case "$file_path" in
55
+ *node_modules*|*dist/*|*build/*|*.min.js) echo '{}'; exit 0 ;;
56
+ esac
57
+
58
+ unused=()
59
+
60
+ # Extract named imports: import { Foo, Bar } from '...'
61
+ while IFS= read -r line; do
62
+ # Extract the imports section between { }
63
+ names=$(echo "$line" | grep -oE '\{[^}]+\}' | tr -d '{}' | tr ',' '\n' | sed 's/\s\+as\s\+[^ ]*//' | tr -d ' \t')
64
+ for name in $names; do
65
+ [ -z "$name" ] && continue
66
+ # Count occurrences outside the import line itself
67
+ count=$(grep -c "$name" "$file_path" 2>/dev/null || echo 0)
68
+ # Import line counts as 1, so if total <= 1, it's only in the import
69
+ if [ "$count" -le 1 ] 2>/dev/null; then
70
+ unused+=("$name")
71
+ fi
72
+ done
73
+ done < <(grep -E '^import\s+\{' "$file_path" 2>/dev/null || true)
74
+
75
+ if [ "${#unused[@]}" -gt 0 ]; then
76
+ echo "IMPORT AUDIT: ${file_path} may have unused imports: ${unused[*]}" >&2
77
+ echo " Verify and remove if unused, or suppress with // eslint-disable-line" >&2
78
+ echo '{}'
79
+ exit 2
80
+ fi
81
+
82
+ echo '{}'
83
+ exit 0
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Hook Template: license-header.sh
4
+ # Event: PostToolUse (Write)
5
+ #
6
+ # Checks that newly created source files have a license header.
7
+ # Only triggers on Write (new file creation), not Edit (modifications).
8
+ #
9
+ # HOW TO INSTALL:
10
+ # Copy this file to ~/.claude/merlin/hooks/license-header.sh
11
+ # Then add to your .claude/settings.local.json:
12
+ #
13
+ # {
14
+ # "hooks": {
15
+ # "PostToolUse": [
16
+ # {
17
+ # "matcher": "Write",
18
+ # "hooks": [{ "type": "command", "command": "~/.claude/merlin/hooks/license-header.sh" }]
19
+ # }
20
+ # ]
21
+ # }
22
+ # }
23
+ #
24
+ # CUSTOMIZE: Set LICENSE_KEYWORD below to match the text in your license header.
25
+ # Default: checks for "Copyright", "License", or "SPDX"
26
+ #
27
+ # BEHAVIOR: Advisory only. Warns when a new file lacks a license header.
28
+ #
29
+ set -euo pipefail
30
+ trap 'echo "{}"; exit 0' ERR
31
+
32
+ # Keywords that indicate a license header is present
33
+ LICENSE_KEYWORD="Copyright|License|SPDX|MIT|Apache|GPL|BSD"
34
+
35
+ # Read tool input from stdin
36
+ input=""
37
+ if [ ! -t 0 ]; then
38
+ input=$(cat 2>/dev/null || true)
39
+ fi
40
+
41
+ [ -z "$input" ] && { echo '{}'; exit 0; }
42
+
43
+ # Extract file path and tool name
44
+ file_path=""
45
+ tool_name=""
46
+ if command -v jq >/dev/null 2>&1; then
47
+ file_path=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.path // empty' 2>/dev/null || true)
48
+ tool_name=$(echo "$input" | jq -r '.tool_name // empty' 2>/dev/null || true)
49
+ fi
50
+
51
+ [ -z "$file_path" ] || [ ! -f "$file_path" ] && { echo '{}'; exit 0; }
52
+
53
+ # Only check on Write (new file creation), skip Edit
54
+ [ "${tool_name:-}" = "Edit" ] && { echo '{}'; exit 0; }
55
+
56
+ # Only check source code files
57
+ case "$file_path" in
58
+ *.js|*.jsx|*.ts|*.tsx|*.mjs|*.py|*.go|*.java|*.rb|*.rs|*.swift|*.c|*.cpp|*.h)
59
+ ;;
60
+ *)
61
+ echo '{}'
62
+ exit 0
63
+ ;;
64
+ esac
65
+
66
+ # Skip node_modules, dist, generated
67
+ case "$file_path" in
68
+ *node_modules*|*dist/*|*build/*|*vendor/*|*__generated__*)
69
+ echo '{}'
70
+ exit 0
71
+ ;;
72
+ esac
73
+
74
+ # Check first 10 lines for license keyword
75
+ first_lines=$(head -10 "$file_path" 2>/dev/null || true)
76
+
77
+ if ! echo "$first_lines" | grep -qiE "$LICENSE_KEYWORD"; then
78
+ echo "LICENSE HEADER: ${file_path} is missing a license header. Add a copyright/license comment at the top of the file." >&2
79
+ echo '{}'
80
+ exit 2
81
+ fi
82
+
83
+ echo '{}'
84
+ exit 0
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Hook Template: pr-description.sh
4
+ # Event: Stop
5
+ #
6
+ # At the end of a session, generates a draft PR description from the git diff
7
+ # and outputs it to a file. Surfaces a reminder with the file path.
8
+ #
9
+ # HOW TO INSTALL:
10
+ # Copy this file to ~/.claude/merlin/hooks/pr-description.sh
11
+ # Then add to your .claude/settings.local.json:
12
+ #
13
+ # {
14
+ # "hooks": {
15
+ # "Stop": [
16
+ # {
17
+ # "hooks": [{ "type": "command", "command": "~/.claude/merlin/hooks/pr-description.sh" }]
18
+ # }
19
+ # ]
20
+ # }
21
+ # }
22
+ #
23
+ # BEHAVIOR: Advisory only. Writes a draft PR description to .claude/pr-draft.md
24
+ # and injects a reminder. Does not open PRs — that stays in your control.
25
+ # REQUIRES: git
26
+ #
27
+ set -euo pipefail
28
+ trap 'echo "{}"; exit 0' ERR
29
+
30
+ # Only run if we're in a git repo with changes
31
+ git rev-parse --git-dir >/dev/null 2>&1 || { echo '{}'; exit 0; }
32
+
33
+ # Check if there are commits ahead of the merge base (comparing to main/master)
34
+ BASE_BRANCH=""
35
+ for candidate in main master develop; do
36
+ if git show-ref --verify --quiet "refs/heads/${candidate}" 2>/dev/null || \
37
+ git show-ref --verify --quiet "refs/remotes/origin/${candidate}" 2>/dev/null; then
38
+ BASE_BRANCH="$candidate"
39
+ break
40
+ fi
41
+ done
42
+
43
+ [ -z "$BASE_BRANCH" ] && { echo '{}'; exit 0; }
44
+
45
+ # Current branch
46
+ CURRENT_BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
47
+ [ -z "$CURRENT_BRANCH" ] && { echo '{}'; exit 0; }
48
+
49
+ # Skip if already on base branch
50
+ [ "$CURRENT_BRANCH" = "$BASE_BRANCH" ] && { echo '{}'; exit 0; }
51
+
52
+ # Get commits since diverging from base
53
+ COMMITS=$(git log --oneline "${BASE_BRANCH}..HEAD" 2>/dev/null | head -20)
54
+ [ -z "$COMMITS" ] && { echo '{}'; exit 0; }
55
+
56
+ # Get changed files
57
+ CHANGED_FILES=$(git diff --name-only "${BASE_BRANCH}...HEAD" 2>/dev/null | head -30)
58
+
59
+ # Get summary stats
60
+ FILES_COUNT=$(echo "$CHANGED_FILES" | grep -c . 2>/dev/null || echo 0)
61
+ COMMIT_COUNT=$(echo "$COMMITS" | grep -c . 2>/dev/null || echo 0)
62
+ INSERTIONS=$(git diff --stat "${BASE_BRANCH}...HEAD" 2>/dev/null | tail -1 | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' || echo 0)
63
+ DELETIONS=$(git diff --stat "${BASE_BRANCH}...HEAD" 2>/dev/null | tail -1 | grep -oE '[0-9]+ deletion' | grep -oE '[0-9]+' || echo 0)
64
+
65
+ # Write draft PR description
66
+ DRAFT_FILE=".claude/pr-draft.md"
67
+ mkdir -p ".claude"
68
+
69
+ cat > "$DRAFT_FILE" << PREOF
70
+ ## Summary
71
+
72
+ <!-- Fill in: what does this PR do and why? -->
73
+
74
+ ### Changes
75
+ $(echo "$COMMITS" | sed 's/^/- /')
76
+
77
+ ### Files Changed (${FILES_COUNT} files, +${INSERTIONS}/-${DELETIONS} lines)
78
+ \`\`\`
79
+ $(echo "$CHANGED_FILES")
80
+ \`\`\`
81
+
82
+ ## Test Plan
83
+
84
+ - [ ] Unit tests pass
85
+ - [ ] Manual test: <!-- describe -->
86
+ - [ ] No new linting errors
87
+
88
+ ## Notes
89
+
90
+ <!-- Anything reviewers should know? Breaking changes? Dependencies updated? -->
91
+
92
+ ---
93
+ *Draft generated by Merlin pr-description hook — branch: \`${CURRENT_BRANCH}\`, ${COMMIT_COUNT} commit(s)*
94
+ PREOF
95
+
96
+ echo "PR DESCRIPTION: Draft written to ${DRAFT_FILE}. Review and edit before opening your PR." >&2
97
+ echo " Run: gh pr create --body \"\$(cat ${DRAFT_FILE})\"" >&2
98
+
99
+ echo '{}'
100
+ exit 0