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.
- 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 +1 -0
- package/files/agents/context-guardian.md +1 -0
- package/files/agents/docs-keeper.md +1 -0
- package/files/agents/dry-refactor.md +1 -0
- package/files/agents/elite-code-refactorer.md +1 -0
- package/files/agents/hardening-guard.md +1 -0
- package/files/agents/implementation-dev.md +1 -0
- package/files/agents/merlin-access-control-reviewer.md +248 -0
- package/files/agents/merlin-codebase-mapper.md +1 -1
- package/files/agents/merlin-dependency-auditor.md +216 -0
- package/files/agents/merlin-executor.md +1 -0
- package/files/agents/merlin-input-validator.md +247 -0
- package/files/agents/merlin-reviewer.md +1 -0
- package/files/agents/merlin-sast-reviewer.md +182 -0
- package/files/agents/merlin-secret-scanner.md +203 -0
- package/files/agents/tests-qa.md +1 -0
- package/files/commands/merlin/execute-phase.md +94 -197
- package/files/commands/merlin/execute-plan.md +116 -180
- package/files/commands/merlin/health.md +385 -0
- package/files/commands/merlin/loop-recipes.md +93 -36
- package/files/commands/merlin/optimize-prompts.md +158 -0
- 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/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/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,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
|