n8n-mcp-skills
Expert Claude Code skills for building flawless n8n workflows using the n8n-mcp MCP server.
What godotmaker runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
Claude Code
$ npx -y skills add RandallLiuXin/GodotMaker --agent claude-code
Cursor
Cursor also runs Claude Code hooks from .claude/settings.json, so copy these hooks there to use them in Cursor.
Where it lives
"""Hook modules used by GodotMaker runtime checks."""
#!/usr/bin/env python3
"""PreToolUse hook: block active pipeline roles from reading asset images.
The active /gm-* skill in the main session must delegate asset analysis to an
analyst subagent. Regular coding-agent conversations with no current_role are
not pipeline sessions and are allowed to read assets directly. Subagents are
also allowed; only the main agent (empty agent_id) is blocked during a
pipeline role.
"""
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from metrics import get_current_role
IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".svg", ".webp", ".gif", ".bmp", ".tga"}
def main():
try:
data = json.load(sys.stdin)
except (json.JSONDecodeError, EOFError):
sys.exit(0)
if data.get("hook_event_name") != "PreToolUse":
sys.exit(0)
if data.get("tool_name") != "Read":
sys.exit(0)
agent_id = data.get("agent_id", "")
if agent_id:
sys.exit(0)
if not get_current_role():
sys.exit(0)
tool_input = data.get("tool_input", {})
file_path = tool_input.get("file_path", "")
if not file_path:
sys.exit(0)
normalized = file_path.replace("\\", "/").lower()
if "/assets/" not in normalized and not normalized.startswith("assets/"):
sys.exit(0)
_, ext = os.path.splitext(normalized)
if ext not in IMAGE_EXTENSIONS:
sys.exit(0)
reason = (
f"The active pipeline role cannot read image files in assets/ directly. "
f"Dispatch an analyst subagent to analyze '{os.path.basename(file_path)}' instead. "
f"See analyst-dispatch.md for the protocol."
)
print(json.dumps({"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": reason,
}}))
sys.exit(0)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Stop hook: warn the agent once per dirty episode at end of a SKILL.
Block-once per dirty episode — agent gets reminded, can commit/clean/ignore.
Clean state resets the flag so the next dirty episode gets its own reminder.
Subagents skipped.
"""
import json
import os
import subprocess
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from metrics import get_current_role, state
def main():
try:
data = json.load(sys.stdin)
except (json.JSONDecodeError, EOFError):
sys.exit(0)
if data.get("agent_id"):
sys.exit(0)
role = get_current_role()
if not role:
sys.exit(0)
# Role change between Stop events resets the reminder flag, so the new
# role gets its own first-dirty-stop reminder instead of inheriting the
# previous role's "already reminded" state.
if state.get("dirty_reminder_last_role", None) != role:
state.put("dirty_reminded", False)
state.put("dirty_reminder_last_role", role)
r = subprocess.run(
["git", "status", "--porcelain"],
capture_output=True, text=True
)
if r.returncode != 0:
sys.exit(0)
output = r.stdout.strip()
if not output:
state.put("dirty_reminded", False)
sys.exit(0)
if state.get("dirty_reminded", False):
sys.exit(0)
state.put("dirty_reminded", True)
snippet = output[:800] + ("\n... (truncated)" if len(output) > 800 else "")
reminder = (
f"Working tree dirty at end of '{role}':\n{snippet}\n\n"
f'Run `git add -A && git commit -m "..."`, remove unintended files, '
f"or exit again to skip."
)
print(json.dumps({"decision": "block", "reason": reminder}))
sys.exit(0)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Stop hook: verify dispatcher diligence in worker-dispatching roles.
Only enforced when current_role is "build" or "fixgap" — these are the roles
that dispatch workers and require verifier + reviewer rounds. Other roles
(scaffold, gdd, asset, verify, evaluate, accept, finalize) self-enforce via
their SKILL.md Resume Check and skip this hook.
Diligence rule: if workers were dispatched in this session, both verifier
and reviewer must have run too (per gm-build/gm-fixgap Hard Rule 6).
Anti-deadloop: if blocked BLOCK_LIMIT times in the same session, allow with
a warning rather than blocking again.
Only blocks the main agent (the gm-* skill), not subagents.
"""
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from metrics import (
record_event, read_current_events, EventType, state, event_has_role,
get_current_role, WORKER_DISPATCH_ROLES,
)
BLOCK_LIMIT = 5
LIFECYCLE_EVENTS = (EventType.SUBAGENT_START, EventType.SUBAGENT_STOP)
def check_diligence(events: list[dict], require_reviewer: bool = True) -> list[str]:
"""Check that workers had verifiers (and reviewers) dispatched."""
if not events:
return []
worker_count = 0
verifier_seen = False
reviewer_seen = False
for e in events:
ev = e.get("event")
if ev == EventType.SUBAGENT_START and event_has_role(e, "worker"):
worker_count += 1
if ev in LIFECYCLE_EVENTS:
if not verifier_seen and event_has_role(e, "verifier"):
verifier_seen = True
if not reviewer_seen and event_has_role(e, "reviewer"):
reviewer_seen = True
if worker_count == 0:
return []
issues = []
n = worker_count
if not verifier_seen:
issues.append(
f"Dispatched {n} workers but 0 verifiers. "
"Dispatch a verifier (subagent_type: 'verifier') to confirm "
"code/tests are real and the build passes."
)
if require_reviewer and not reviewer_seen:
issues.append(
f"Dispatched {n} workers but 0 reviewers. "
"Dispatch a reviewer (subagent_type: 'reviewer') to check "
"code quality and ECS patterns."
)
return issues
def get_diligence_failure(role: str, events: list[dict]) -> dict | None:
"""Return a structured completion failure without producing side effects.
This is the reusable pre-append decision. Callers own any subsequent
metrics, state, archive, or stage-log mutations.
"""
if role not in WORKER_DISPATCH_ROLES:
return None
issues = check_diligence(events, require_reviewer=True)
if not issues:
return None
reason = (
f"Cannot finish '{role}' role — diligence issues:\n"
+ "\n".join(f" - {line}" for line in issues)
)
return {
"decision": "block",
"role": role,
"issues": issues,
"reason": reason,
}
def main():
try:
data = json.load(sys.stdin)
except (json.JSONDecodeError, EOFError):
sys.exit(0)
if data.get("agent_id", ""):
sys.exit(0)
role = get_current_role()
if role not in WORKER_DISPATCH_ROLES:
record_event(EventType.GATE_CHECK, gate="completion",
result="skip", role=role)
sys.exit(0)
block_count = state.get("stop_block_count", 0)
if block_count >= BLOCK_LIMIT:
record_event(EventType.GATE_CHECK, gate="completion",
result="force_allow", role=role,
reason=f"Blocked {block_count} times, allowing to prevent deadloop")
warning = (
f"Force-allowing completion after {block_count} failed attempts. "
"You MUST tell the user that diligence checks did not fully pass. "
"List the unresolved issues so the user can decide whether to "
"re-run /gm-build or accept as-is."
)
print(json.dumps({"decision": "allow", "reason": warning}), file=sys.stderr)
sys.exit(0)
events = read_current_events()
failure = get_diligence_failure(role, events)
if failure:
state.increment("stop_block_count")
record_event(EventType.GATE_CHECK, gate="completion",
result="fail", role=role, issues=failure["issues"][:5])
print(json.dumps({
"decision": failure["decision"],
"reason": failure["reason"],
}))
sys.exit(0)
record_event(EventType.GATE_CHECK, gate="completion", result="pass", role=role)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""PreToolUse hook: enforce file write permissions per pipeline role.
Reads .godotmaker/current_role and applies the role's write rules. See the
gm-*/SKILL.md files for the canonical per-role rules; this hook enforces
them. When no role is set, no /gm-* pipeline role is active, so regular
coding-agent conversations are allowed to write normally.
"""
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from metrics import record_event, EventType, get_current_role, WORKER_DISPATCH_ROLES
GAME_CODE_EXTENSIONS = {".gd", ".tscn", ".tres"}
# Project-root planning artifacts — subagents may NOT modify these unless
# their agent_type is in PLANNING_WRITER_AGENT_TYPES. Includes the four
# 1c decomposer outputs (PLAN/STRUCTURE/DESIGN/ASSETS + SCENES/TOC), the
# tag-iteration roadmap (ROADMAP.md, owned by /gm-gdd), and GAP.md
# (owned by /gm-fixgap's lead, not subagents).
PLANNING_DOCS = {"plan.md", "structure.md", "design.md", "assets.md", "gap.md",
"scenes.md", "toc.md", "roadmap.md"}
# project.godot is the engine config and changes the whole game. Subagents
# may not edit it unless their agent_type is in PLANNING_WRITER_AGENT_TYPES.
PROJECT_GODOT = "project.godot"
E2E_DIR_PREFIX = "e2e/"
ASSETS_DIR_PREFIX = "assets/"
REFERENCES_DIR_PREFIX = "references/"
GODOTMAKER_DIR = ".godotmaker/"
# Subagent types whose entire purpose is writing planning docs — exempt
# from the general subagent block on PLANNING_DOCS and PROJECT_GODOT.
PLANNING_WRITER_AGENT_TYPES = {"decomposer"}
# Per-role narrow write allow-lists under .godotmaker/. Each role needs
# current_role + stage.jsonl for bookkeeping; evaluate / verify also write
# their structured verdict; rescue is diagnostic-only (chat output only),
# so it gets ONLY the bookkeeping pair — anything else attempted is a SKILL
# violation worth blocking.
EVAL_ALLOWED_GM_FILES = {".godotmaker/evaluation.json",
".godotmaker/stage.jsonl",
".godotmaker/current_role"}
# Evaluator-owned directory: per-subject design rule check request, raw
# findings, and graded result.
DESIGN_CHECKS_DIR = ".godotmaker/design-checks/"
VERIFY_ALLOWED_GM_FILES = {".godotmaker/stage.jsonl",
".godotmaker/current_role",
".godotmaker/verify_report.json"}
RESCUE_ALLOWED_GM_FILES = {".godotmaker/stage.jsonl",
".godotmaker/current_role"}
def _is_e2e_path(path_lower: str) -> bool:
return path_lower.startswith(E2E_DIR_PREFIX) or f"/{E2E_DIR_PREFIX}" in path_lower
def _is_assets_path(path_lower: str) -> bool:
return path_lower.startswith(ASSETS_DIR_PREFIX) or f"/{ASSETS_DIR_PREFIX}" in path_lower
def _is_references_path(path_lower: str) -> bool:
return path_lower.startswith(REFERENCES_DIR_PREFIX) or f"/{REFERENCES_DIR_PREFIX}" in path_lower
def _is_godotmaker_path(path_lower: str) -> bool:
return path_lower.startswith(GODOTMAKER_DIR) or f"/{GODOTMAKER_DIR}" in path_lower
def _is_design_check_path(path_lower: str) -> bool:
"""True for the evaluator-owned design rule check artefacts.
`/gm-evaluate` writes one request, findings, and graded result per subject
here so the chain from a DESIGN.md rule to its verdict stays inspectable.
"""
return (
path_lower.startswith(DESIGN_CHECKS_DIR)
or f"/{DESIGN_CHECKS_DIR}" in path_lower
)
def _is_asset_generation_path(path_lower: str) -> bool:
return (
path_lower.startswith(".godotmaker/asset-generation/")
or "/.godotmaker/asset-generation/" in path_lower
)
def _is_canonical_asset_request_path(path_lower: str) -> bool:
"""True for a top-level canonical asset-generation request.
`/gm-asset` owns these immutable request files. Producers may still write
resolved/internal request artifacts below `work/` or `plans/`, except for
the manager-owned UI-kit pre-dispatch planning set checked separately.
"""
marker = ".godotmaker/asset-generation/"
normalized = path_lower.replace("\\", "/")
marker_index = normalized.find(marker)
if marker_index < 0:
return False
relative = normalized[marker_index + len(marker):]
return "/" not in relative and relative.endswith("-request.json")
def _is_manager_owned_asset_planning_path(path_lower: str) -> bool:
"""True for the manager-owned UI-kit pre-dispatch planning set."""
marker = ".godotmaker/asset-generation/work/"
normalized = path_lower.replace("\\", "/")
marker_index = normalized.find(marker)
if marker_index < 0:
return False
relative = normalized[marker_index + len(marker):]
parts = relative.split("/")
return len(parts) == 2 and parts[1] in {
"planning-request.json",
"theme_plan.json",
"source_sheet_plan.json",
"stylebox_plan.json",
"theme_recipe.json",
}
def _matches_allowed_gm(path_lower: str, allowed: set[str]) -> bool:
"""True if path ends with one of the allowed `.godotmaker/<file>` entries."""
return any(path_lower.endswith(p) for p in allowed)
def _is_project_root_assets_md(path_lower: str) -> bool:
"""True iff path_lower resolves to the project-root ASSETS.md.
The hook runs with cwd = project root, so abspath() of a relative
input is anchored to that root. Accepts both bare `"assets.md"` and
any absolute path that resolves to `<cwd>/ASSETS.md`. Subdirectory
variants (`subdir/ASSETS.md`) and absolute paths to a different
project's ASSETS.md are rejected — the asset role's contract and
the deny message in `_check_main` are explicitly project-root only.
Uses realpath (not abspath) because macOS routes /var/folders/...
through a /private/var/folders/... symlink — abspath leaves the
input untouched but cwd-derived paths often arrive already resolved,
so the two sides drift and a legitimately-rooted ASSETS.md gets
rejected#!/usr/bin/env python3
"""PreToolUse hook (Agent/Task tool): verify role prerequisites before dispatch.
Only enforces for roles that drive worker orchestration:
- build → requires gdd completed + scaffold artifacts present
- fixgap → requires evaluate completed + evaluation.json present
Scaffold artifacts are checked on disk because gm-finalize truncates
stage.jsonl at every tag boundary, so the scaffold event is no longer
in stage.jsonl after the first finalize.
Other dispatching roles (asset → analyst) self-validate via their SKILL.md
Resume Check; their preconditions don't match this hook's stage-schema model.
"""
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from metrics import (
record_event, EventType,
get_current_role, get_completed_roles,
load_stage_schemas, WORKER_DISPATCH_ROLES,
)
PREREQ_ROLE = {
"build": "gdd",
"fixgap": "evaluate",
}
# Sanity check: PREREQ_ROLE must cover exactly the worker-dispatching roles.
# Use an explicit raise instead of assert so the check survives `python -O`.
if frozenset(PREREQ_ROLE) != WORKER_DISPATCH_ROLES:
raise RuntimeError(
f"PREREQ_ROLE keys {sorted(PREREQ_ROLE)} must equal "
f"WORKER_DISPATCH_ROLES {sorted(WORKER_DISPATCH_ROLES)}"
)
# Roles that need scaffold artifacts on disk (lifetime-once, not in stage.jsonl
# after the first tag's finalize truncates the log).
SCAFFOLD_REQUIRED = frozenset({"build"})
def main():
try:
data = json.load(sys.stdin)
except (json.JSONDecodeError, EOFError):
sys.exit(0)
if data.get("tool_name") not in {"Agent", "Task"}:
sys.exit(0)
# Only check main agent (the gm-* skill orchestrating dispatch)
if data.get("agent_id", ""):
sys.exit(0)
role = get_current_role()
prereq = PREREQ_ROLE.get(role)
if not prereq:
sys.exit(0) # No worker-dispatch role active, nothing to enforce
completed = get_completed_roles()
issues = []
if role in SCAFFOLD_REQUIRED and not os.path.isfile("project.godot"):
issues.append("project.godot not found — run /gm-scaffold first")
if prereq not in completed:
issues.append(f"Role '{prereq}' has not completed yet — run /gm-{prereq} first")
schemas = load_stage_schemas()
if schemas:
prereq_schema = schemas.get(prereq, {})
# Inline existence check here (not validate_schema_files) to keep the
# role-aware "{prereq} output missing: X" message style.
for filepath in prereq_schema.get("files", []):
if not os.path.exists(filepath):
issues.append(f"{prereq} output missing: {filepath}")
if issues:
reason = (
f"Cannot dispatch worker — '{role}' role prerequisites missing:\n"
+ "\n".join(f" - {m}" for m in issues)
)
record_event(EventType.HOOK_BLOCK, hook="check_stage_prerequisites",
role=role, missing=issues)
print(json.dumps({"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": reason,
}}))
sys.exit(0)
record_event(EventType.HOOK_ALLOW, hook="check_stage_prerequisites", role=role)
if __name__ == "__main__":
main()
All 15 scripts are listed above. The source is inlined for 6 of them, starting with whatever hooks.json actually runs. See all of them in the repo.
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.
Autonomous text-to-game pipeline for Godot, powered by Claude Code,Codex,Opencode
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.
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.
A Claude Code plugin for crypto trading with skills to run multi-agent market analysis, track a portfolio and open trades, close trades with post-mortem review, and monitor stop-loss and take-profit levels on a schedule.