n8n-mcp-skills
Expert Claude Code skills for building flawless n8n workflows using the n8n-mcp MCP server.
What developer-skills runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
Claude Code
> /plugin marketplace add sgomez/developer-skills> /plugin install developer-skills@sgomez
Cursor
Cursor also runs Claude Code hooks from .claude/settings.json, so copy these hooks there to use them in Cursor.
What fires, and when
PreToolUse
Bash${CLAUDE_PLUGIN_ROOT}/hooks/approve-merge.sh${CLAUDE_PLUGIN_ROOT}/hooks/approve-push.shAgent|Task${CLAUDE_PLUGIN_ROOT}/hooks/require-background-workers.shSubagentStart
${CLAUDE_PLUGIN_ROOT}/hooks/hc-agent-terminals.shSubagentStop
${CLAUDE_PLUGIN_ROOT}/hooks/hc-agent-terminals.shWhere it lives
#!/usr/bin/env bash
#
# PreToolUse (Bash) hook for the /developer pipeline.
#
# Auto-approves the pipeline's one sanctioned unattended merge — and ONLY that —
# so it is never handed to the auto-mode permission classifier. In `auto` mode
# the classifier re-evaluates "the orchestrator is merging a PR that only got a
# subagent COMMENT, with no human approval" as an adversarial pattern and denies
# it, even when `Bash(gh pr merge:*)` is on the allow-list (a wildcard allow rule
# does not exempt it). A PreToolUse `allow` decision runs before that classifier,
# so it is the only deterministic way to let the sanctioned merge through.
#
# Every guard below must hold; otherwise the hook stays silent (exit 0 → defer to
# the normal permission flow), so it can never widen anything unexpectedly:
# 1. jq is available (the hook payload is JSON on stdin).
# 2. The command is EXACTLY `gh pr merge <PR> --(merge|squash|rebase)` — fully
# anchored: no shell chaining, no --admin, no extra flags.
# 3. The call comes from the primary checkout, where the orchestrator runs.
# developer-defaults.md is committed, so guard 4 is equally true inside
# every worker's linked worktree — a worker that merged on its own would
# be auto-approved by guards 1, 2 and 4 alone. Merging is the
# orchestrator's alone, and only it runs where git-dir == git-common-dir.
# 4. The repo opted into unattended merges: docs/agents/developer-defaults.md
# carries a `merge: auto` line. Interactive `--auto-merge` overrides on a
# `merge: manual` repo are deliberately NOT covered — a prompt there is fine.
# Or the PR merges into a spec's integration branch (`agent/developer/spec-<N>`):
# /developer integrates every sub-issue there unattended whatever the
# merge policy, which governs only the spec PR into main. Nothing reaches
# main that way, so `merge: manual` is not being bypassed — and guard 5
# does not apply either: sub-issues are not gated on CI, the spec PR's
# gate runs it once on all of them before anything reaches main.
# 5. The change's CI checks are green — or the repo has none, which is the
# same thing here: nothing to gate on. The Merge step tells the
# orchestrator to gate on this, but that gate is a prompt; this hook is
# what makes it real. Without guard 5 the hook would wave a red build
# straight past the classifier — the one reader that would otherwise have
# stopped it.
#
# Note the asymmetry in guard 5: green approves, and anything else (red,
# pending, no CI, gh failure, no network) merely stays silent. The hook never
# emits a `deny`. Staying silent hands the merge back to the normal permission
# flow, which is free to ask or refuse; that keeps this hook incapable of
# blocking a merge a human would have allowed, while still never approving one
# on red.
command -v jq >/dev/null 2>&1 || exit 0
payload="$(cat)"
cmd="$(printf '%s' "$payload" | jq -r '.tool_input.command // ""' 2>/dev/null)" || exit 0
# 2. Strict, fully-anchored match — the exact form the orchestrator issues.
re='^gh pr merge ([0-9]+) --(merge|squash|rebase)$'
[[ "$cmd" =~ $re ]] || exit 0
pr="${BASH_REMATCH[1]}"
cwd="$(printf '%s' "$payload" | jq -r '.cwd // ""' 2>/dev/null)" || exit 0
[[ -n "$cwd" ]] || cwd="$PWD"
# 3. Only from the primary checkout. In a linked worktree these two paths
# differ; outside a repo both are empty, which must not read as a match.
# --path-format=absolute is required: without it git prints whichever form
# is shortest from cwd, so a subdirectory of the primary checkout yields
# "/abs/.git" and "../.git" — unequal, and the merge would never be approved.
gitdir="$(git -C "$cwd" rev-parse --path-format=absolute --git-dir 2>/dev/null)" || exit 0
commondir="$(git -C "$cwd" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" || exit 0
[[ -n "$gitdir" && "$gitdir" == "$commondir" ]] || exit 0
# 4. Only where the user pre-authorized unattended merges. The defaults file is
# repo-relative, so resolve it from the top level — cwd is wherever the
# orchestrator happens to stand, which is often a subdirectory.
root="$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null)" || exit 0
[[ -n "$root" ]] || exit 0
base="$(cd "$root" && gh pr view "$pr" --json baseRefName --jq .baseRefName 2>/dev/null)" || base=""
if [[ "$base" =~ ^agent/developer/spec-[0-9]+$ ]]; then
integration=1
else
integration=0
grep -qE '^merge:[[:space:]]*auto[[:space:]]*$' "$root/docs/agents/developer-defaults.md" 2>/dev/null || exit 0
fi
# 5. Only on green checks.
#
# `gh pr checks` is the wrong probe here: it exits non-zero both when a
# check failed and when the PR has no checks at all, and those two cases
# must not be conflated — the second is a repo without CI, which this hook
# has always approved and must keep approving. `gh pr view
# --json statusCheckRollup` separates them cleanly: it exits 0 either way
# and returns an empty array when nothing reports on the head commit.
#
# Deliberately no --watch/polling: a PreToolUse hook must answer promptly,
# and waiting for CI is the orchestrator's job in the Merge step. A check
# still running has a null conclusion, counts as not-green, and the hook
# simply stays silent.
if [[ "$integration" -eq 0 ]]; then
rollup="$(cd "$root" && gh pr view "$pr" --json statusCheckRollup \
--jq '.statusCheckRollup // []' 2>/dev/null)" || exit 0
# A check run reports `conclusion` (null while running); a commit status
# reports `state`. Anything that is not a settled success counts against the
# merge — including the empty string a running check yields.
notgreen="$(printf '%s' "$rollup" | jq '
[ .[]
| ((.conclusion // .state // "") | ascii_upcase)
| select(. != "SUCCESS" and . != "NEUTRAL" and . != "SKIPPED")
] | length
' 2>/dev/null)" || notgreen=""
# Non-numeric means gh failed, is unauthenticated, or printed something
#!/usr/bin/env bash
#
# PreToolUse (Bash) hook for the /developer pipeline.
#
# Auto-approves a fix or merge-fix worker's push of its commits onto the PR it
# was sent to fix — and ONLY that — so it is never handed to the auto-mode
# permission classifier. On lab-05 the classifier denied the spec PR's fixer
# `git push origin HEAD:agent/developer/spec-1` as "Modify Shared Resources"
# (the integration branch is the head of a PR into main), after letting the
# very same push through on lab-04: the spec escalated with its fix committed
# locally and never published. A PreToolUse `allow` runs before that
# classifier, so it is the only deterministic way to let the push through.
#
# Every guard below must hold; otherwise the hook stays silent (exit 0 → defer
# to the normal permission flow), so it can never widen anything unexpectedly:
# 1. jq is available (the hook payload is JSON on stdin).
# 2. The command is EXACTLY `git push origin HEAD:agent/developer/<spec|issue>-<N>`
# — fully anchored: no force, no flags, no refspec but HEAD, no chaining.
# Without a force flag the remote refuses anything but a fast-forward,
# so the push can only add commits on top of the PR branch.
# 3. The call comes from a linked worktree — where workers run — never the
# primary checkout.
# 4. That worktree is on a fix branch the workflow names:
# `agent/developer/fix-pr-<PR>`, or a variant of it (`-r2`, `-merge`…).
# 5. PR <PR> exists and its head branch is the push's target: a worker sent
# to fix PR 16 may push onto PR 16's branch, and onto nothing else.
command -v jq >/dev/null 2>&1 || exit 0
payload="$(cat)"
cmd="$(printf '%s' "$payload" | jq -r '.tool_input.command // ""' 2>/dev/null)" || exit 0
# 2. Strict, fully-anchored match — the exact form the fix prompts issue.
re='^git push origin HEAD:(agent/developer/(spec|issue)-[0-9]+)$'
[[ "$cmd" =~ $re ]] || exit 0
target="${BASH_REMATCH[1]}"
cwd="$(printf '%s' "$payload" | jq -r '.cwd // ""' 2>/dev/null)" || exit 0
[[ -n "$cwd" ]] || cwd="$PWD"
# 3. Only from a linked worktree: there git-dir and git-common-dir differ.
# Outside a repo both are empty, which must not read as a worktree.
gitdir="$(git -C "$cwd" rev-parse --path-format=absolute --git-dir 2>/dev/null)" || exit 0
commondir="$(git -C "$cwd" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" || exit 0
[[ -n "$gitdir" && -n "$commondir" && "$gitdir" != "$commondir" ]] || exit 0
# 4. On a fix branch, which names the PR it fixes.
branch="$(git -C "$cwd" symbolic-ref --short -q HEAD 2>/dev/null)" || exit 0
bre='^agent/developer/fix-pr-([0-9]+)(-[a-z0-9]+)?$'
[[ "$branch" =~ $bre ]] || exit 0
pr="${BASH_REMATCH[1]}"
# 5. The push lands on that PR's own head branch.
head="$(cd "$cwd" && gh pr view "$pr" --json headRefName --jq .headRefName 2>/dev/null)" || exit 0
[[ "$head" == "$target" ]] || exit 0
jq -nc '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
permissionDecisionReason: "Sanctioned /developer fix push: a fix worker publishing its commits onto the head of the PR it fixes, fast-forward only — kept out of the auto-mode classifier by design."
}
}'
#!/usr/bin/env bash
#
# SubagentStart / SubagentStop hook: in herdr-core, each /developer worker
# (build, fix, merge-fix, review) gets a child terminal of the Claude terminal,
# named after the workflow label (build-387), that follows the worker's
# transcript and status (hc-agent-view.sh). SubagentStop tells it the worker
# ended; it shows the result for a while, then exits.
#
# The herdr app lists terminals, not OSC 7501 sub-records, so a terminal per
# worker is what makes the workers visible there. Short mechanical agents
# (cleanup, gate…) get none: their agent_type is not a worker's. A no-op
# outside herdr-core, outside a workflow, and without jq or hc.
#
# The payload has no label: it comes from the workflow journal, which holds the
# agent's `started` line when SubagentStart fires. --no-notify keeps the core
# from typing notes about the child into Claude's prompt.
[[ -n ${HC_TERMINAL_ID:-} ]] || exit 0
command -v jq >/dev/null 2>&1 && command -v hc >/dev/null 2>&1 || exit 0
IFS=$'\t' read -r type id event transcript < <(jq -r '[.agent_type // "", .agent_id // "", .hook_event_name // "", .transcript_path // ""] | @tsv' 2>/dev/null) || exit 0
[[ $id =~ ^[A-Za-z0-9_-]+$ ]] || exit 0
case "${type##*:}" in
code-author | diff-reviewer) ;;
*) exit 0 ;;
esac
journal="$(grep -l "\"agentId\":\"$id\"" "${transcript%.jsonl}"/subagents/workflows/*/journal.jsonl 2>/dev/null | head -1)"
[[ -n $journal ]] || exit 0
if [[ $event == SubagentStop ]]; then
hc ls --json 2>/dev/null | jq -r --arg id "$id" '.terminals[] | select(.labels["claude.agent"] == $id) | .address' |
while read -r t; do hc send "$t" stop >/dev/null 2>&1; done
exit 0
fi
[[ $event == SubagentStart ]] || exit 0
label="$(grep "\"agentId\":\"$id\"" "$journal" | jq -r 'select(.type == "started") | .label // empty' 2>/dev/null | head -1)"
label=${label:-agent}
# hc names: 1-32 of a-z 0-9 - _ ., not shaped like an id (t7), unique among live terminals.
name="$(printf %s "$label" | tr 'A-Z' 'a-z' | tr -cs 'a-z0-9._' '-' | sed 's/^[-._]*//; s/-*$//' | cut -c1-25)"
[[ -n $name && ! $name =~ ^t[0-9] ]] || name="agent-$name"
name=${name%-}
for n in "$name" "$name-${id:0:6}"; do
hc run --bg --no-notify --rm --name "$n" --label "claude.agent=$id" \
"$(dirname "$0")/hc-agent-view.sh" "$label" "$(dirname "$journal")/agent-$id.jsonl" "$journal" "$id" >/dev/null 2>&1 && break
done
exit 0
#!/usr/bin/env bash
#
# hc-agent-view.sh <label> <agent transcript> <workflow journal> <agent id> —
# runs in the terminal that hc-agent-terminals.sh opens for a worker: prints
# the worker's messages and tool calls as its transcript grows, reports each
# step to herdr-core as `working`, and the journal's result as `done` or `error`.
#
# A finished terminal loses its status, so the final one lingers
# HC_AGENT_LINGER seconds. A line on stdin (`hc send <t> stop`, from
# SubagentStop) means the worker ended: no result within 5 s is an error.
label=$1 file=$2 journal=$3 id=$4
stty -echo 2>/dev/null
status() { printf '\033]7501;state=%s:app=claude-workflow:msg=%s\033\134' "$1" "$(printf %s "$2" | base64 -w0)"; }
status working "$label"
printf '\033]2;%s\007' "$label"
echo "── $label ──"
for _ in $(seq 50); do [[ -f $file ]] && break; sleep 0.2; done
tail -n +1 -F "$file" 2>/dev/null | jq --unbuffered -j '
def osc($m): "\u001b]7501;state=working:app=claude-workflow:msg=\($m | @base64)\u001b\\";
select(.type == "assistant") | .message.content[]? |
if .type == "text" then "· " + .text + "\n"
elif .type == "tool_use" then
(.name + " " + ((.input.command // .input.description // .input.file_path // .input.pattern // "") | tostring) | .[0:200]) as $s
| "→ \($s)\n" + osc($s | gsub("\\s+"; " ") | .[0:80])
else empty end' &
deadline=
until result="$(jq -c --arg id "$id" 'select(.type == "result" and .agentId == $id) | .result' "$journal" 2>/dev/null | head -1)"; [[ -n $result ]]; do
[[ -n $deadline ]] && ((SECONDS >= deadline)) && break
if read -r -t 1 _; then deadline=$((SECONDS + 5)); elif (($? < 128)); then sleep 1; fi
done
sleep 0.5
pkill -P $$ 2>/dev/null
IFS=$'\t' read -r state msg < <(jq -r '
if . == null then ["error", "stopped without a result"]
elif type != "object" then ["done", tostring]
else ([.status, .verdict, .outcome] | map(select(. != null))[0] // "done") as $v
| if ($v | ascii_downcase) | IN("blocked", "escalate") then ["error", .reason // $v]
elif .pr then ["done", "PR #\(.pr)"]
elif $v == "pr" then ["done", .url // "pushed"]
elif (.fixes // 0) > 0 then ["done", "\($v) (\(.fixes) fixes)"]
else ["done", $v] end
end | .[1] |= (tostring | gsub("\\s+"; " ") | .[0:80]) | @tsv' <<<"${result:-null}")
status "$state" "$msg"
[[ $state == done ]] && echo "✓ $msg" || echo "✗ $msg"
sleep "${HC_AGENT_LINGER:-60}"
#!/usr/bin/env bash
#
# PreToolUse (Agent) hook for the /developer pipeline.
#
# Refuses a foreground spawn of a /developer worker. A foreground spawn holds
# the orchestrator's turn open for the worker's entire run, so anything that
# interrupts that turn — a Ctrl-C, a dropped connection — takes the worker down
# with it: its context, its worktree and its commits are gone, unrecoverably.
# The same interruption leaves a background worker running and reachable with
# SendMessage. SKILL.md says this ("Every spawn is run_in_background: true");
# this hook is what makes it real.
#
# Deliberately narrow, because unlike hooks/approve-merge.sh this one *denies*,
# and a misfire blocks real work:
# 1. jq is available (the hook payload is JSON on stdin).
# 2. The spawn targets one of the three /developer worker types. Any other
# agent — Explore, general-purpose, another plugin's — passes untouched.
# 3. run_in_background is *explicitly* false. The harness default is already
# background, so an omitted field is correct and must not be denied;
# denying a call that would have behaved properly is the false positive
# this hook must never produce. (If that default ever flips, the absent
# case stops being covered here — the SKILL.md rule is what covers it.)
#
# The denial reason is half the point: it has to be actionable enough that the
# model re-issues the same spawn correctly in the same turn instead of giving
# up on the sub-issue.
command -v jq >/dev/null 2>&1 || exit 0
payload="$(cat)"
# 2. Only the pipeline's own workers. The type arrives namespaced when the
# plugin is installed ("developer-skills:code-author") and bare when the
# agents are loaded from a checkout, so match on the part after the colon.
type="$(printf '%s' "$payload" | jq -r '.tool_input.subagent_type // ""' 2>/dev/null)" || exit 0
case "${type##*:}" in
code-author | diff-reviewer) ;;
*) exit 0 ;;
esac
# 3. Only an explicit opt-out of the background default.
bg="$(printf '%s' "$payload" | jq -r '.tool_input.run_in_background' 2>/dev/null)" || exit 0
[[ "$bg" == "false" ]] || exit 0
jq -nc --arg t "$type" '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: ("Foreground spawn of \($t) refused. A /developer worker spawned with run_in_background: false holds the orchestrator turn open for its whole run, so an interruption destroys the worker context, worktree and commits with no way to recover them. Re-issue this identical call with run_in_background: true, then wait for its result before spawning the next worker.")
}
}'
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
Unattended spec delivery for Claude Code: you write specs, a pipeline of isolated agents implements every sub-issue — build → review → fix → merge — and pings you when it's done.
Expert Claude Code skills for building flawless n8n workflows using the n8n-mcp MCP server.
A research assistant plugin for Claude Code that supports literature review, paper writing, citation verification, Zotero and Obsidian integration, and code review and debugging for academic and software work.
Runs multi-AI workflows in Claude Code that split research, requirements scoping, implementation and review across several external AI providers, such as Codex, Gemini-style CLIs, Copilot, Ollama and OpenRouter, to cover each model's blind spots.
Orchestrates multiple Claude Code agents across terminals and machines, giving them persistent memory and agent-to-agent messaging so you no longer have to relay messages between them by hand.
An autonomous pipeline that turns a text description into a playable Godot game, generating design docs, game assets such as backgrounds, characters, props, cards and effects, and running visual checks and playtests.
A personal AI assistant with voice, screen and meeting support that learns your patterns and can update its own code. It runs across several Macs and can talk with other people's assistants.
Helps hardware teams turn product requirements into a reviewable, buildable PCB design plan. Covers embedded architecture, MCU or SoC selection, power tree, interfaces, sensors, connectivity, and BOM risk.
MCP server for reusable prompt templates, multi-step workflow chains, and quality gates. Compose agentic workflows with an operator syntax; export as native skills to Claude Code, Cursor, OpenCode, and Gemini CLI.