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.
- package/bin/install.cjs +146 -22
- package/bin/runtime-adapters.cjs +396 -0
- package/dist/server/cost/tracker.d.ts +38 -2
- package/dist/server/cost/tracker.d.ts.map +1 -1
- package/dist/server/cost/tracker.js +87 -15
- package/dist/server/cost/tracker.js.map +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +74 -30
- package/dist/server/server.js.map +1 -1
- package/dist/server/tools/adaptive.js +1 -1
- package/dist/server/tools/adaptive.js.map +1 -1
- package/dist/server/tools/agents-index.js +3 -3
- package/dist/server/tools/agents-index.js.map +1 -1
- package/dist/server/tools/agents.js +5 -5
- package/dist/server/tools/agents.js.map +1 -1
- package/dist/server/tools/behaviors.js +4 -4
- package/dist/server/tools/behaviors.js.map +1 -1
- package/dist/server/tools/context.js +7 -7
- package/dist/server/tools/context.js.map +1 -1
- package/dist/server/tools/cost.d.ts +3 -1
- package/dist/server/tools/cost.d.ts.map +1 -1
- package/dist/server/tools/cost.js +66 -13
- package/dist/server/tools/cost.js.map +1 -1
- package/dist/server/tools/discoveries.js +6 -6
- package/dist/server/tools/discoveries.js.map +1 -1
- package/dist/server/tools/index.d.ts +4 -0
- package/dist/server/tools/index.d.ts.map +1 -1
- package/dist/server/tools/index.js +4 -0
- package/dist/server/tools/index.js.map +1 -1
- package/dist/server/tools/learning.d.ts +12 -0
- package/dist/server/tools/learning.d.ts.map +1 -0
- package/dist/server/tools/learning.js +269 -0
- package/dist/server/tools/learning.js.map +1 -0
- package/dist/server/tools/project.js +7 -7
- package/dist/server/tools/project.js.map +1 -1
- package/dist/server/tools/promote.d.ts +11 -0
- package/dist/server/tools/promote.d.ts.map +1 -0
- package/dist/server/tools/promote.js +315 -0
- package/dist/server/tools/promote.js.map +1 -0
- package/dist/server/tools/route.d.ts.map +1 -1
- package/dist/server/tools/route.js +65 -24
- package/dist/server/tools/route.js.map +1 -1
- package/dist/server/tools/session-restore.d.ts +18 -0
- package/dist/server/tools/session-restore.d.ts.map +1 -0
- package/dist/server/tools/session-restore.js +154 -0
- package/dist/server/tools/session-restore.js.map +1 -0
- package/dist/server/tools/session-search.d.ts +16 -0
- package/dist/server/tools/session-search.d.ts.map +1 -0
- package/dist/server/tools/session-search.js +240 -0
- package/dist/server/tools/session-search.js.map +1 -0
- package/dist/server/tools/sights-index.js +2 -2
- package/dist/server/tools/sights-index.js.map +1 -1
- package/dist/server/tools/smart-route.d.ts.map +1 -1
- package/dist/server/tools/smart-route.js +4 -5
- package/dist/server/tools/smart-route.js.map +1 -1
- package/dist/server/tools/verification.js +1 -1
- package/dist/server/tools/verification.js.map +1 -1
- package/files/agents/code-organization-supervisor.md +9 -0
- package/files/agents/context-guardian.md +9 -0
- package/files/agents/docs-keeper.md +11 -1
- package/files/agents/dry-refactor.md +12 -1
- package/files/agents/elite-code-refactorer.md +10 -0
- package/files/agents/hardening-guard.md +13 -1
- package/files/agents/implementation-dev.md +12 -1
- package/files/agents/merlin-access-control-reviewer.md +248 -0
- package/files/agents/merlin-api-designer.md +9 -0
- package/files/agents/merlin-codebase-mapper.md +9 -1
- package/files/agents/merlin-debugger.md +10 -0
- package/files/agents/merlin-dependency-auditor.md +216 -0
- package/files/agents/merlin-executor.md +12 -1
- package/files/agents/merlin-frontend.md +9 -0
- package/files/agents/merlin-input-validator.md +247 -0
- package/files/agents/merlin-integration-checker.md +9 -1
- package/files/agents/merlin-migrator.md +9 -0
- package/files/agents/merlin-milestone-auditor.md +8 -0
- package/files/agents/merlin-performance.md +8 -0
- package/files/agents/merlin-planner.md +10 -0
- package/files/agents/merlin-researcher.md +10 -0
- package/files/agents/merlin-reviewer.md +42 -7
- package/files/agents/merlin-sast-reviewer.md +182 -0
- package/files/agents/merlin-secret-scanner.md +203 -0
- package/files/agents/merlin-security.md +9 -0
- package/files/agents/merlin-verifier.md +9 -0
- package/files/agents/merlin-work-verifier.md +9 -0
- package/files/agents/merlin.md +10 -0
- package/files/agents/ops-railway.md +11 -1
- package/files/agents/orchestrator-retrofit.md +9 -1
- package/files/agents/product-spec.md +11 -1
- package/files/agents/remotion.md +8 -0
- package/files/agents/system-architect.md +11 -1
- package/files/agents/tests-qa.md +12 -1
- package/files/commands/merlin/course-correct.md +219 -0
- package/files/commands/merlin/debug.md +2 -2
- package/files/commands/merlin/execute-phase.md +96 -199
- package/files/commands/merlin/execute-plan.md +118 -182
- package/files/commands/merlin/health.md +385 -0
- package/files/commands/merlin/loop-recipes.md +93 -36
- package/files/commands/merlin/map-codebase.md +4 -4
- package/files/commands/merlin/next.md +240 -0
- package/files/commands/merlin/optimize-prompts.md +158 -0
- package/files/commands/merlin/plan-phase.md +1 -1
- package/files/commands/merlin/profiles.md +215 -0
- package/files/commands/merlin/promote.md +176 -0
- package/files/commands/merlin/quick.md +229 -0
- package/files/commands/merlin/readiness-gate.md +208 -0
- package/files/commands/merlin/research-phase.md +2 -2
- package/files/commands/merlin/research-project.md +4 -4
- package/files/commands/merlin/resume-work.md +27 -1
- package/files/commands/merlin/route.md +43 -1
- package/files/commands/merlin/sandbox.md +359 -0
- package/files/commands/merlin/usage.md +55 -0
- package/files/commands/merlin/verify-work.md +1 -1
- package/files/docker/Dockerfile.merlin +20 -0
- package/files/docker/docker-compose.merlin.yml +23 -0
- package/files/hook-templates/auto-commit.sh +64 -0
- package/files/hook-templates/auto-format.sh +95 -0
- package/files/hook-templates/auto-test.sh +117 -0
- package/files/hook-templates/branch-protection.sh +72 -0
- package/files/hook-templates/changelog-reminder.sh +76 -0
- package/files/hook-templates/complexity-check.sh +112 -0
- package/files/hook-templates/import-audit.sh +83 -0
- package/files/hook-templates/license-header.sh +84 -0
- package/files/hook-templates/pr-description.sh +100 -0
- package/files/hook-templates/todo-tracker.sh +80 -0
- package/files/hooks/check-file-size.sh +17 -4
- package/files/hooks/config-change.sh +44 -16
- package/files/hooks/instructions-loaded.sh +22 -5
- package/files/hooks/notify-desktop.sh +157 -0
- package/files/hooks/notify-webhook.sh +141 -0
- package/files/hooks/pre-edit-sights-check.sh +76 -9
- package/files/hooks/security-scanner.sh +153 -0
- package/files/hooks/session-end-memory-sync.sh +97 -0
- package/files/hooks/session-end.sh +274 -1
- package/files/hooks/session-start.sh +19 -6
- package/files/hooks/smart-approve.sh +270 -0
- package/files/hooks/teammate-idle-verify.sh +87 -12
- package/files/hooks/worktree-create.sh +20 -3
- package/files/hooks/worktree-remove.sh +21 -3
- package/files/merlin/references/plan-format.md +37 -9
- package/files/merlin/sandbox.json +9 -0
- package/files/merlin/security.json +11 -0
- package/files/merlin/templates/ci/docs-update.yml +81 -0
- package/files/merlin/templates/ci/pr-review.yml +50 -0
- package/files/merlin/templates/ci/security-audit.yml +74 -0
- package/files/merlin/templates/config.json +9 -1
- package/files/rules/api-rules.md +30 -0
- package/files/rules/frontend-rules.md +25 -0
- package/files/rules/hooks-rules.md +36 -0
- package/files/rules/mcp-rules.md +30 -0
- package/files/rules/worker-rules.md +29 -0
- package/package.json +1 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Hook Template: auto-format.sh
|
|
4
|
+
# Event: PostToolUse (Write, Edit)
|
|
5
|
+
#
|
|
6
|
+
# Automatically runs Prettier and/or ESLint after Claude writes or edits a file.
|
|
7
|
+
# Keeps code style consistent without manual intervention.
|
|
8
|
+
#
|
|
9
|
+
# HOW TO INSTALL:
|
|
10
|
+
# Copy this file to ~/.claude/merlin/hooks/auto-format.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-format.sh" }]
|
|
19
|
+
# }
|
|
20
|
+
# ]
|
|
21
|
+
# }
|
|
22
|
+
# }
|
|
23
|
+
#
|
|
24
|
+
# REQUIREMENTS: prettier and/or eslint must be installed in the project.
|
|
25
|
+
# BEHAVIOR: Advisory only — never blocks. Runs format in background and exits.
|
|
26
|
+
#
|
|
27
|
+
set -euo pipefail
|
|
28
|
+
trap 'echo "{}"; exit 0' ERR
|
|
29
|
+
|
|
30
|
+
# Read tool input from stdin
|
|
31
|
+
input=""
|
|
32
|
+
if [ ! -t 0 ]; then
|
|
33
|
+
input=$(cat 2>/dev/null || true)
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
[ -z "$input" ] && { echo '{}'; exit 0; }
|
|
37
|
+
|
|
38
|
+
# Extract file path
|
|
39
|
+
file_path=""
|
|
40
|
+
if command -v jq >/dev/null 2>&1; then
|
|
41
|
+
file_path=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.path // empty' 2>/dev/null || true)
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
[ -z "$file_path" ] || [ ! -f "$file_path" ] && { echo '{}'; exit 0; }
|
|
45
|
+
|
|
46
|
+
# Only format code files
|
|
47
|
+
case "$file_path" in
|
|
48
|
+
*.js|*.jsx|*.ts|*.tsx|*.mjs|*.cjs|*.json|*.css|*.scss|*.html|*.vue|*.svelte|*.md)
|
|
49
|
+
;;
|
|
50
|
+
*)
|
|
51
|
+
echo '{}'
|
|
52
|
+
exit 0
|
|
53
|
+
;;
|
|
54
|
+
esac
|
|
55
|
+
|
|
56
|
+
# Skip node_modules and generated files
|
|
57
|
+
case "$file_path" in
|
|
58
|
+
*node_modules*|*.min.js|*.bundle.js|*dist/*|*build/*)
|
|
59
|
+
echo '{}'
|
|
60
|
+
exit 0
|
|
61
|
+
;;
|
|
62
|
+
esac
|
|
63
|
+
|
|
64
|
+
# Try Prettier first (project-local, then global)
|
|
65
|
+
PRETTIER=""
|
|
66
|
+
if [ -f "node_modules/.bin/prettier" ]; then
|
|
67
|
+
PRETTIER="node_modules/.bin/prettier"
|
|
68
|
+
elif command -v prettier >/dev/null 2>&1; then
|
|
69
|
+
PRETTIER="prettier"
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
if [ -n "$PRETTIER" ]; then
|
|
73
|
+
$PRETTIER --write "$file_path" >/dev/null 2>&1 || true
|
|
74
|
+
echo "auto-format: prettier applied to $file_path" >&2
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# Try ESLint for JS/TS files (fix mode)
|
|
78
|
+
case "$file_path" in
|
|
79
|
+
*.js|*.jsx|*.ts|*.tsx|*.mjs|*.cjs)
|
|
80
|
+
ESLINT=""
|
|
81
|
+
if [ -f "node_modules/.bin/eslint" ]; then
|
|
82
|
+
ESLINT="node_modules/.bin/eslint"
|
|
83
|
+
elif command -v eslint >/dev/null 2>&1; then
|
|
84
|
+
ESLINT="eslint"
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
if [ -n "$ESLINT" ]; then
|
|
88
|
+
$ESLINT --fix "$file_path" >/dev/null 2>&1 || true
|
|
89
|
+
echo "auto-format: eslint --fix applied to $file_path" >&2
|
|
90
|
+
fi
|
|
91
|
+
;;
|
|
92
|
+
esac
|
|
93
|
+
|
|
94
|
+
echo '{}'
|
|
95
|
+
exit 0
|
|
@@ -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
|