Skip to content
Development
Hook

Hooks

What gameforge runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.

From plugin
gameforge
5241 skills11 agents7 hooks

Claude Code

> /plugin marketplace add AlterLab-IEU/AlterLab_GameForge
> /plugin install gameforge@alterlab-gameforge

What fires, and when

SessionStart

Fires once when a session begins, and again after a context compaction. It is where a plugin sets up its environment, or restores state the compaction dropped.

  • sh "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh"

PreCompact

  • sh "${CLAUDE_PLUGIN_ROOT}/hooks/pre-compact.sh"

PreToolUse

  • MatchesBashsh "${CLAUDE_PLUGIN_ROOT}/hooks/validate-commit.sh"
  • MatchesWrite|Edit|MultiEdit|NotebookEditsh "${CLAUDE_PLUGIN_ROOT}/hooks/protect-generated.sh"

SubagentStart

  • sh "${CLAUDE_PLUGIN_ROOT}/hooks/subagent-track.sh"

SubagentStop

  • sh "${CLAUDE_PLUGIN_ROOT}/hooks/subagent-track.sh"

StopFailure

  • sh "${CLAUDE_PLUGIN_ROOT}/hooks/stop-failure.sh"

SessionEnd

  • sh "${CLAUDE_PLUGIN_ROOT}/hooks/session-end.sh"
Read hooks/hooks.json

In the plugin's words

How gameforge describes its own hook set.

GameForge lifecycle hooks. Every hook is silent outside game projects; hooks that write files run only after /gameforge:game-start has created production/session-state or .gameforge/. Set GAMEFORGE_HOOKS=off to disable all of them.

Where it lives

  • hooks/pre-compact.shRunsGitHub
    Read the script
    #!/bin/sh
    # GameForge -- PreCompact (manual | auto)
    #
    # Writes a small, deterministic snapshot of where the work stands so that
    # session-start.sh can re-inject it when the session resumes after compaction.
    # Only runs in projects that opted in (production/session-state or .gameforge exists).
    
    . "${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}/hooks/lib/common.sh"
    gf_read_input
    gf_require_opt_in
    
    TRIGGER=$(gf_json_get .trigger)
    STATE=production/session-state
    mkdir -p "$STATE" 2>/dev/null || exit 0
    OUT="$STATE/compact-snapshot.md"
    
    {
      echo "Snapshot taken $(gf_now) before ${TRIGGER:-unknown} compaction."
      [ -n "$GF_ENGINE_LABEL" ] && echo "Engine: $GF_ENGINE_LABEL"
      if gf_in_git; then
        echo "Branch: $(gf_git rev-parse --abbrev-ref HEAD)"
        echo "Recent commits:"
        gf_git log -5 --pretty='  %h %s'
        echo "Uncommitted changes:"
        gf_git status --porcelain | head -n 15 | sed 's/^/  /'
      fi
      for f in "$STATE"/pipeline-*.json; do
        [ -f "$f" ] || continue
        # Top-level status. Without jq/python3, take the first "status" line: the orchestrator
        # writes the pipeline status before its steps, and step statuses must not win.
        if command -v jq >/dev/null 2>&1 || command -v python3 >/dev/null 2>&1; then
          _status=$(gf_json_get .status "$(cat "$f")")
        else
          _status=$(sed -n 's/.*"status"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$f" | head -n 1)
        fi
        case "$_status" in completed|abandoned) continue ;; esac
        echo "Active pipeline: $(basename "$f" .json) (${_status:-unknown status})"
      done
    } > "$OUT" 2>/dev/null
    
    exit 0
    
  • hooks/protect-generated.shRunsGitHub
    Read the script
    #!/bin/sh
    # GameForge -- PreToolUse (matcher: Write|Edit|MultiEdit|NotebookEdit)
    #
    # Stops Claude from hand-editing files the engine generates or owns. Editing these
    # silently corrupts projects: Unity .meta GUIDs break every scene reference, Godot
    # .godot/ and .uid files are regenerated, Unreal Intermediate/ and binary .uasset
    # files cannot be meaningfully written as text.
    #
    #   deny -> engine caches, build output, binary engine assets
    #   ask  -> files that are engine-managed but occasionally edited on purpose
    #
    # Disable with GAMEFORGE_PROTECT=off.
    
    . "${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}/hooks/lib/common.sh"
    gf_read_input
    [ "${GAMEFORGE_PROTECT:-on}" = off ] && exit 0
    
    FILE=$(gf_json_get .tool_input.file_path)
    [ -n "$FILE" ] || FILE=$(gf_json_get .tool_input.notebook_path)
    [ -n "$FILE" ] || exit 0
    
    gf_require_game_project
    
    # Normalise to a project-relative path: forward slashes, no duplicate slashes, and try the
    # project dir as given, the logical cwd and the physical cwd (macOS /var -> /private/var).
    norm() { printf '%s' "$1" | tr '\\' '/' | sed 's#//*#/#g; s#/$##'; }
    REL=$(norm "$FILE")
    for _root in "$GF_ROOT" "$(pwd)" "$(pwd -P)"; do
      _root=$(norm "$_root")
      [ -n "$_root" ] || continue
      case "$REL" in "$_root"/*) REL=${REL#"$_root"/}; break ;; esac
    done
    case "$REL" in /*) exit 0 ;; esac   # outside the project: not ours to guard
    
    deny() { gf_pretool_decision deny "GameForge: $REL $1"; }
    ask() { gf_pretool_decision ask "GameForge: $REL $1"; }
    
    case "$GF_ENGINE" in
      godot)
        case "$REL" in
          .godot/*|*/.godot/*) deny "is inside .godot/, Godot's regenerated editor cache. Change the source resource or project settings instead." ;;
          *.uid) deny "is a Godot UID file. Godot owns these; editing them breaks resource references. Move or rename files through the editor or keep the .uid next to its script." ;;
          *.import) ask "is a Godot import-settings file. Prefer changing import options in the editor's Import dock; edit by hand only if the user asked for it." ;;
        esac ;;
      unity)
        case "$REL" in
          Library/*|Temp/*|Obj/*|obj/*|Logs/*|UserSettings/*|Build/*|Builds/*) deny "is in a Unity-generated folder. Unity rebuilds it; edit Assets/ or ProjectSettings/ instead." ;;
          *.csproj|*.sln) deny "is regenerated by Unity on every script reload. Use asmdef files or Unity's External Tools settings instead." ;;
          *.meta) ask "is a Unity .meta file. Its GUID links every reference to this asset; changing it can break scenes and prefabs. Let Unity create .meta files, and only edit import settings here if the user asked for it." ;;
        esac ;;
      unreal)
        case "$REL" in
          Binaries/*|Intermediate/*|Saved/*|DerivedDataCache/*|.vs/*|*/Binaries/*|*/Intermediate/*) deny "is in an Unreal-generated folder that the build regenerates. Edit Source/, Config/ or Content/ through the editor instead." ;;
          *.uasset|*.umap) deny "is a binary Unreal asset. It cannot be written as text; make the change in the editor, through Python editor scripting, or an Unreal MCP bridge." ;;
        esac ;;
    esac
    
    # Web and shared rules.
    case "$REL" in
      node_modules/*|*/node_modules/*) deny "is inside node_modules/. Change package.json or patch the dependency with patch-package instead." ;;
      dist/*|build/*|.vite/*|.next/*|.parcel-cache/*)
        case "$GF_ENGINE" in threejs|r3f|babylon|playcanvas|phaser|pixi|kaplay|excalibur|littlejs)
          deny "is build output. Edit the source and rebuild." ;;
        esac ;;
    esac
    
    exit 0
    
  • hooks/session-end.shRunsGitHub
    Read the script
    #!/bin/sh
    # GameForge -- SessionEnd
    #
    # Appends one line per session to production/session-logs/sessions.log.
    # SessionEnd hooks share a 1.5 s budget, so this does no expensive work.
    # Only runs in projects that opted in.
    
    . "${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}/hooks/lib/common.sh"
    gf_read_input
    gf_opted_in || exit 0
    
    REASON=$(gf_json_get .reason)
    SID=$(gf_json_get .session_id)
    BRANCH=""
    gf_in_git && BRANCH=$(gf_git rev-parse --abbrev-ref HEAD)
    
    mkdir -p production/session-logs 2>/dev/null || exit 0
    printf '%s session=%s reason=%s branch=%s\n' "$(gf_now)" "${SID:-unknown}" "${REASON:-unknown}" "${BRANCH:-none}" \
      >> production/session-logs/sessions.log 2>/dev/null
    
    exit 0
    
  • hooks/session-start.shRunsGitHub
    Read the script
    #!/bin/sh
    # GameForge -- SessionStart (startup | resume | clear | compact | fork)
    #
    # Injects a short project briefing into Claude's context: engine + version, phase,
    # git position, saved session state, recovery notes after an API failure, and
    # coverage gaps with the skill that closes each one. On `compact` it also restores
    # the snapshot written by pre-compact.sh.
    #
    # SessionStart stdout on exit 0 is added to Claude's context, so this hook prints
    # plain text. It prints nothing at all outside game projects.
    
    . "${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}/hooks/lib/common.sh"
    gf_read_input
    gf_require_game_project
    
    SOURCE=$(gf_json_get .source)
    [ -n "$SOURCE" ] || SOURCE=startup
    
    # --- Phase heuristic (overridable via .gameforge/project.json "phase") -----------
    has_dir() { for _d in "$@"; do [ -d "$_d" ] && return 0; done; return 1; }
    HAS_DESIGN=no; HAS_SRC=no; HAS_TESTS=no
    has_dir design docs/design Documentation production/gdd && HAS_DESIGN=yes
    has_dir src scripts Scripts Source Assets/Scripts lib game && HAS_SRC=yes
    has_dir tests test Tests Assets/Tests && HAS_TESTS=yes
    case "$GF_ENGINE" in godot|unity|unreal|bevy|gamemaker|defold|love) HAS_SRC=yes ;; esac
    [ -f package.json ] && HAS_SRC=yes
    
    PHASE=$(gf_config phase)
    if [ -z "$PHASE" ]; then
      if [ "$HAS_SRC" = no ] && [ "$HAS_DESIGN" = no ]; then PHASE="concept (empty project)"
      elif [ "$HAS_SRC" = no ]; then PHASE="pre-production (design only)"
      elif [ "$HAS_DESIGN" = no ]; then PHASE="prototype (code, no design docs)"
      else PHASE="production"
      fi
    fi
    
    # --- Specialist routing -----------------------------------------------------------
    case "$GF_ENGINE" in
      godot) SPECIALIST=game-godot-specialist ;;
      unity) SPECIALIST=game-unity-specialist ;;
      unreal) SPECIALIST=game-unreal-specialist ;;
      threejs|r3f|babylon|playcanvas) SPECIALIST=game-threejs-specialist ;;
      phaser|pixi|kaplay|excalibur|littlejs) SPECIALIST=game-web2d-specialist ;;
      *) SPECIALIST="" ;;
    esac
    
    # --- Briefing ---------------------------------------------------------------------
    if [ "$SOURCE" = compact ]; then
      echo "[GameForge] Context restored after compaction."
    else
      echo "[GameForge] Project briefing ($SOURCE)."
    fi
    [ -n "$GF_ENGINE_LABEL" ] && echo "- Engine: $GF_ENGINE_LABEL${SPECIALIST:+ -> route engine questions to /gameforge:$SPECIALIST}"
    echo "- Phase: $PHASE"
    
    if gf_in_git; then
      BRANCH=$(gf_git rev-parse --abbrev-ref HEAD)
      LAST=$(gf_git log -1 --pretty='%h %s (%cr)')
      DIRTY=$(gf_git status --porcelain | wc -l | tr -d ' ')
      echo "- Git: ${BRANCH:-detached} | last: ${LAST:-no commits} | uncommitted files: $DIRTY"
    fi
    
    STATE=production/session-state
    if [ -f "$STATE/active.md" ]; then
      echo "- Saved session state ($STATE/active.md):"
      head -n 12 "$STATE/active.md" | sed 's/^/    /'
    fi
    
    if [ "$SOURCE" = compact ] && [ -f "$STATE/compact-snapshot.md" ]; then
      echo "- Pre-compaction snapshot ($STATE/compact-snapshot.md):"
      head -n 25 "$STATE/compact-snapshot.md" | sed 's/^/    /'
    fi
    
    if [ -f "$STATE/recovery.json" ]; then
      ERR=$(gf_json_get .error "$(cat "$STATE/recovery.json")")
      WHEN=$(gf_json_get .timestamp "$(cat "$STATE/recovery.json")")
      echo "- The previous session ended on an API error (${ERR:-unknown} at ${WHEN:-unknown time}). Offer to resume from $STATE/ before starting new work."
      mv "$STATE/recovery.json" "$STATE/recovery.last.json" 2>/dev/null || true
    fi
    
    # --- Gaps -> the skill that closes them -------------------------------------------
    GAPS=""
    [ "$HAS_SRC" = no ] && [ "$HAS_DESIGN" = no ] && GAPS="$GAPS
        - Nothing here yet: /gameforge:game-start (or /gameforge:game-vibe-code for a fast playable)"
    [ "$HAS_SRC" = yes ] && [ "$HAS_DESIGN" = no ] && GAPS="$GAPS
        - Code without design docs: /gameforge:game-reverse-document"
    [ "$HAS_SRC" = yes ] && [ "$HAS_TESTS" = no ] && GAPS="$GAPS
        - No test directory: /gameforge:game-qa-lead can set up a test harness"
    if [ -n "$GAPS" ]; then
      echo "- Gaps:$GAPS"
    fi
    
    exit 0
    
  • hooks/stop-failure.shRunsGitHub
    Read the script
    #!/bin/sh
    # GameForge -- StopFailure
    #
    # When a turn ends on an API error (rate_limit, overloaded, billing_error, ...), record a
    # recovery note so the next SessionStart can offer to resume. Writes valid JSON with
    # escaped fields. Only runs in opted-in projects.
    
    . "${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}/hooks/lib/common.sh"
    gf_read_input
    gf_require_opt_in
    
    ERR=$(gf_json_get .error_type)
    [ -n "$ERR" ] || ERR=$(gf_json_get .error)
    [ -n "$ERR" ] || ERR=$(gf_json_get .reason)
    [ -n "$ERR" ] || ERR=unknown
    
    BRANCH=""; LAST=""
    if gf_in_git; then
      BRANCH=$(gf_git rev-parse --abbrev-ref HEAD)
      LAST=$(gf_git log -1 --pretty='%h %s')
    fi
    
    mkdir -p production/session-state production/session-logs 2>/dev/null || exit 0
    cat > production/session-state/recovery.json <<EOF
    {
      "timestamp": "$(gf_now)",
      "error": "$(gf_json_escape "$ERR")",
      "session_id": "$(gf_json_escape "$(gf_json_get .session_id)")",
      "engine": "$(gf_json_escape "$GF_ENGINE_LABEL")",
      "branch": "$(gf_json_escape "$BRANCH")",
      "last_commit": "$(gf_json_escape "$LAST")"
    }
    EOF
    printf '%s StopFailure error=%s\n' "$(gf_now)" "$ERR" >> production/session-logs/sessions.log 2>/dev/null
    
    exit 0
    
  • hooks/subagent-track.shRunsGitHub
    Read the script
    #!/bin/sh
    # GameForge -- SubagentStart / SubagentStop
    #
    # Logs delegation for orchestration debugging (production/session-logs/agents.log)
    # and tracks how many subagents are live in *this* session. When more than
    # GAMEFORGE_MAX_PARALLEL_AGENTS (default 6) run at once it shows the user a warning,
    # because parallel agents multiply token spend. Only runs in opted-in projects.
    
    . "${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}/hooks/lib/common.sh"
    gf_read_input
    gf_opted_in || exit 0
    
    EVENT=$(gf_json_get .hook_event_name)
    SID=$(gf_json_get .session_id)
    AID=$(gf_json_get .agent_id)
    ATYPE=$(gf_json_get .agent_type)
    [ -n "$SID" ] || SID=unknown
    [ -n "$AID" ] || AID="anon-$$"
    
    LIVE_DIR="production/session-state/agents/$(printf '%s' "$SID" | tr -c 'A-Za-z0-9_.-' '_')"
    mkdir -p production/session-logs "$LIVE_DIR" 2>/dev/null || exit 0
    KEY=$(printf '%s' "$AID" | tr -c 'A-Za-z0-9_.-' '_')
    
    case "$EVENT" in
      SubagentStart) : > "$LIVE_DIR/$KEY"; TAG=START ;;
      SubagentStop) rm -f "$LIVE_DIR/$KEY"; TAG=STOP ;;
      *) exit 0 ;;
    esac
    
    LIVE=$(find "$LIVE_DIR" -type f 2>/dev/null | wc -l | tr -d ' ')
    printf '%s %s session=%s agent=%s type=%s live=%s\n' "$(gf_now)" "$TAG" "$SID" "$AID" "${ATYPE:-unknown}" "$LIVE" \
      >> production/session-logs/agents.log 2>/dev/null
    
    MAX=${GAMEFORGE_MAX_PARALLEL_AGENTS:-6}
    case "$MAX" in ''|*[!0-9]*) MAX=6 ;; esac
    if [ "$TAG" = START ] && [ "$LIVE" -gt "$MAX" ]; then
      printf '{"systemMessage":"%s"}\n' "$(gf_json_escape "[GameForge] $LIVE subagents are running at once in this session (limit $MAX). Parallel agents multiply token use; consider sequencing non-critical work.")"
    fi
    
    exit 0
    
  • hooks/validate-commit.shRunsGitHub

All 7 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.

@alterlab-ieu
Ships withgameforge

🎮 A full indie game studio for Claude Code: 41 skills, 11 studio subagents with project memory, engine specialists for Godot, Unity, Unreal, Three.js & 2D web, plus vibe-coding, AI-asset and web-publishing workflows. From first idea to launch.

5241 skills11 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
52
Star history of AlterLab-IEU/AlterLab_GameForge on GitHub, a chart by star-history.com

More plugins that ship hooks.

superpowers

superpowers

A development methodology for coding agents that makes the agent brainstorm before building, follow plans, run parallel work and finish branches in a disciplined way. You get fewer skipped steps and more consistent results from your coding assistant.

15 skills1 hook311k
@obra@obra
Install Plugin
ponytail

ponytail

A Claude Code plugin with skills for auditing a whole repo, reviewing a change, listing deferred shortcut comments, and showing measured savings and a quick command reference.

6 skills10 hooks188k
@dietrichgebert@dietrichgebert
Install Plugin
agent-skills

agent-skills

Walks a software change from a written spec through design, testing, code review, CI/CD, and release, applying the matching engineering skill at each stage. Useful for developers using Claude Code on full development workflows.

25 skills+3112k
@addyosmani@addyosmani
Install Plugin
planning-with-files

planning-with-files

Persistent file-based planning for AI coding agents on long-running, multi-step tasks. It keeps task_plan.md, findings.md and progress.md on disk, so work can resume after /clear or context compaction.

6 skills+228k
@othmanadi@othmanadi
Install Plugin
gsd-core

gsd-core

A spec-driven development workflow for Claude Code that guides a project through planning, phases, milestone audits and fixes. Includes skills to generate tests from UAT criteria and to audit completed work.

72 skills+311k
@open-gsd@open-gsd
Install Plugin
superpowers-zh

superpowers-zh

Chinese-language skill pack for Claude Code that adds workflow guidance for brainstorming, TDD, debugging, and code review, plus Chinese conventions for commits, documentation, and domestic Git platforms like Gitee and Coding.net.

21 skills1 hook8.5k
@jnmetacode@jnmetacode
Install Plugin
socraticode

socraticode

Gives Claude Code semantic search, dependency graph and architecture context over a codebase, so an AI assistant can explore and understand a project's code. Includes skills for indexing a project and managing its file watching.

2 skills+33.4k
@giancarloerra@giancarloerra
Install Plugin
cocoindex-code

cocoindex-code

A lightweight code search engine that uses AST-based semantic search with local embeddings and incremental indexing. It helps Claude Code users find code faster and more efficiently.

1 skill1 MCP+13k
@cocoindex-io@cocoindex-io
Install Plugin

Questions about the hooks in gameforge.

Which coding agents can use the hooks in gameforge?
The hooks in gameforge install in Claude Code and Cursor: each runs Claude Code hooks from .claude/settings.json, so copy the hooks there.