/gm-scaffold
Scaffold a new Godot project: project.godot + addons + base directories + e2e/conftest.py + initial git commit. Lifetime-once role — runs only on fresh projects. Explicit invocation only — use /gm-scaffold.
$ npx -y skills add RandallLiuXin/GodotMaker --skill gm-scaffold --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/gm-scaffold
Context preview
The summary Claude sees to decide when to auto-load this skill.
Scaffold a new Godot project: project.godot + addons + base directories + e2e/conftest.py + initial git commit. Lifetime-once role — runs only on fresh projects. Explicit invocation only — use /gm-scaffold.
SKILL.md
gm-scaffold.SKILL.mdname: gm-scaffold
description: |
Scaffold a new Godot project: project.godot + addons + base directories +
e2e/conftest.py + initial git commit. Lifetime-once role — runs only on
fresh projects.
Explicit invocation only — use /gm-scaffold.
disable-model-invocation: true
GodotMaker Scaffold
$ARGUMENTS
You are scaffolding a brand-new Godot project. This is the **lifetime-once** foundation step: project.godot + addons + base directory layout + e2e/conftest.py + initial git commit. No game design happens here — that comes in `/gm-gdd`.
Session Setup
**FIRST ACTION — before anything else:** Write `scaffold` to `.godotmaker/current_role`.
`session_start.py` deletes `.godotmaker/current_role` on every new session (including `/clear`, restart, and resume) so a stale role from a previous session can't grant unintended write permissions. That is why every gm-* skill re-writes its role as the first action — if a session is interrupted mid-scaffold and you don't re-issue `/gm-scaffold`, write hooks will start denying file writes silently. Re-issuing the slash command re-establishes the role.
Resume Check
Read `.godotmaker/stage.jsonl` (treat as empty if missing) — each line is `{"role": X, "ts": Y}`. Find the **last event** in the file.
Scaffold is **lifetime-once** — its event gets cleared after each tag's finalize (stage.jsonl is truncated). So determine "already scaffolded" from project artifacts on disk, not the event log:
- If `project.godot` exists AND `addons/gecs/` exists AND `git log` has at least one commit → STOP. Tell the user:
> "Project is already scaffolded. Recommended next: /gm-gdd. > If you need to re-scaffold (rare — usually addon migrations are handled by `tools/publish.py`), just tell me."
- Otherwise → proceed.
Hard Rules
1. **Do NOT design the game here.** 2. **Use fixed 2D defaults.** Use the project-scaffold default viewport (`1280x720`) and rendering method. 3. **Do NOT create Component/System stubs.** 4. **All addons MUST come from the active runtime's `config/addon_versions.json`** — do not guess versions. 5. **Initial git commit is mandatory and must include import metadata.** Run `<godot_path> --headless --import` before the commit and include generated `.uid` files.
Scaffold Steps
1. Gather minimal inputs
Use `AskUserQuestion` to ask for:
- **Game name** (snake_case, used as project directory name)
Use 2D defaults. GodotMaker currently generates 2D games only; do not ask the user to choose a dimension during scaffold.
Other settings (genre, art style, mechanics) are deferred to `/gm-gdd`.
2a. Run project-scaffold skill
Invoke `.claude/skills/project-scaffold/SKILL.md` with only the gathered game name. Use project-scaffold defaults for every other template variable. Run only Steps 1, 2, and 3. Do not run genre adaptations. From Step 3's template table, fill just these four templates:
- `project.godot.tmpl` → `project.godot`
- `main_scene.tmpl` → `scenes/main.tscn`
- `world_scene.tmpl` → `scenes/game_world.tscn`
- `gitignore.tmpl` → `.gitignore`
That gives you the directory tree, an empty `Main` entry-point scene, the gameplay scene placeholder, and a sensible `.gitignore`.
The remaining items in project-scaffold belong to other parts of the pipeline:
| project-scaffold concern | Owner | |--------------------------|-------| | Component / System / test files (with or without a Game Plan) | decomposer in `/gm-gdd` writes STRUCTURE.md first; workers in `/gm-build` create the actual `.gd` files | | Genre Adaptations — `[physics]` / `[input]` (Step 4) | `/gm-gdd` | | Post-Scaffold publish (Step 5) | already done by `tools/publish.py` before scaffold runs | | Addon install | step 2b below |
2b. Install addon-only directories
Install required addons from the active runtime's `config/addon_versions.json`. Follow `project-scaffold/references/addons.md`.
Required result:
1. `addons/gecs/plugin.cfg` 2. `addons/gdUnit4/plugin.cfg` 3. `addons/gdUnit4/bin/GdUnitCmdTool.gd` 4. `addons/gdUnit4/src/core/runners/GdUnitTestCIRunner.gd` 5. `addons/godot_e2e/plugin.cfg` 6. `addons/godot_e2e/automation_server.gd` 7. no `addons/*/project.godot`
Then enable plugin entries in `project.godot` and register `AutomationServer`.
`.godotmaker/config.yaml` is created by `tools/publish.py` before this skill runs — verify it exists and move on.
E2E conftest.py template
Write the following to `e2e/conftest.py`:
import os
import pytest
from godot_e2e import GodotE2E
GODOT_PROJECT = os.path.join(os.path.dirname(__file__), "..")
GODOT_CONFIG = os.path.join(GODOT_PROJECT, ".claude", "godotmaker.yaml")
def _read_godot_path():
try:
with open(GODOT_CONFIG, "r", encoding="utf-8") as f:
for line in f:
line = line.split("#", 1)[0].strip()
if line.startswith("godot_path:"):
value = line.split(":", 1)[1].strip().strip("\"'")
return value or None
except OSError:
return None
return None
GODOT_PATH = _read_godot_path()
@pytest.fixture(scope="module")
def _game_process():
with GodotE2E.launch(
GODOT_PROJECT,
godot_path=GODOT_PATH,
timeout=15.0,
) as game:
game.wait_for_node("/root/Main", timeout=10.0)
yield game
@pytest.fixture(scope="function")
def game(_game_process):
_game_process.reload_scene()
_game_process.wait_for_node("/root/Main", timeout=5.0)
yield _game_process3. Initial commit
git init # if not already a git repo
<godot_path> --headless --import # generate .godot/ cache + *.uid sidecars
git add -A
git commit -m "Scaffold: initial Godot project with addons"
Required for `isolation: "worktree"` in worker dispatch — without `HEAD`, parallel workers fail with `fatal: not a valid object name: 'HEAD'`.
4. Verify
python tools/check_project.py <project_dir> --build
`--build`
Read more
name: gm-scaffold description: | Scaffold a new Godot project: project.godot + addons + base directories + e2e/conftest.py + initial git commit. Lifetime-once role — runs only on fresh projects. Explicit invocation only — use /gm-scaffold. disable-model-invocation: true
GodotMaker Scaffold
$ARGUMENTS
You are scaffolding a brand-new Godot project. This is the **lifetime-once** foundation step: project.godot + addons + base directory layout + e2e/conftest.py + initial git commit. No game design happens here — that comes in `/gm-gdd`.
Session Setup
**FIRST ACTION — before anything else:** Write `scaffold` to `.godotmaker/current_role`.
`session_start.py` deletes `.godotmaker/current_role` on every new session (including `/clear`, restart, and resume) so a stale role from a previous session can't grant unintended write permissions. That is why every gm-* skill re-writes its role as the first action — if a session is interrupted mid-scaffold and you don't re-issue `/gm-scaffold`, write hooks will start denying file writes silently. Re-issuing the slash command re-establishes the role.
Resume Check
Read `.godotmaker/stage.jsonl` (treat as empty if missing) — each line is `{"role": X, "ts": Y}`. Find the **last event** in the file.
Scaffold is **lifetime-once** — its event gets cleared after each tag's finalize (stage.jsonl is truncated). So determine "already scaffolded" from project artifacts on disk, not the event log:
- If `project.godot` exists AND `addons/gecs/` exists AND `git log` has at least one commit → STOP. Tell the user:
> "Project is already scaffolded. Recommended next: /gm-gdd. > If you need to re-scaffold (rare — usually addon migrations are handled by `tools/publish.py`), just tell me."
- Otherwise → proceed.
Hard Rules
1. **Do NOT design the game here.** 2. **Use fixed 2D defaults.** Use the project-scaffold default viewport (`1280x720`) and rendering method. 3. **Do NOT create Component/System stubs.** 4. **All addons MUST come from the active runtime's `config/addon_versions.json`** — do not guess versions. 5. **Initial git commit is mandatory and must include import metadata.** Run `<godot_path> --headless --import` before the commit and include generated `.uid` files.
Scaffold Steps
1. Gather minimal inputs
Use `AskUserQuestion` to ask for:
- **Game name** (snake_case, used as project directory name)
Use 2D defaults. GodotMaker currently generates 2D games only; do not ask the user to choose a dimension during scaffold.
Other settings (genre, art style, mechanics) are deferred to `/gm-gdd`.
2a. Run project-scaffold skill
Invoke `.claude/skills/project-scaffold/SKILL.md` with only the gathered game name. Use project-scaffold defaults for every other template variable. Run only Steps 1, 2, and 3. Do not run genre adaptations. From Step 3's template table, fill just these four templates:
- `project.godot.tmpl` → `project.godot`
- `main_scene.tmpl` → `scenes/main.tscn`
- `world_scene.tmpl` → `scenes/game_world.tscn`
- `gitignore.tmpl` → `.gitignore`
That gives you the directory tree, an empty `Main` entry-point scene, the gameplay scene placeholder, and a sensible `.gitignore`.
The remaining items in project-scaffold belong to other parts of the pipeline:
| project-scaffold concern | Owner | |--------------------------|-------| | Component / System / test files (with or without a Game Plan) | decomposer in `/gm-gdd` writes STRUCTURE.md first; workers in `/gm-build` create the actual `.gd` files | | Genre Adaptations — `[physics]` / `[input]` (Step 4) | `/gm-gdd` | | Post-Scaffold publish (Step 5) | already done by `tools/publish.py` before scaffold runs | | Addon install | step 2b below |
2b. Install addon-only directories
Install required addons from the active runtime's `config/addon_versions.json`. Follow `project-scaffold/references/addons.md`.
Required result:
1. `addons/gecs/plugin.cfg` 2. `addons/gdUnit4/plugin.cfg` 3. `addons/gdUnit4/bin/GdUnitCmdTool.gd` 4. `addons/gdUnit4/src/core/runners/GdUnitTestCIRunner.gd` 5. `addons/godot_e2e/plugin.cfg` 6. `addons/godot_e2e/automation_server.gd` 7. no `addons/*/project.godot`
Then enable plugin entries in `project.godot` and register `AutomationServer`.
`.godotmaker/config.yaml` is created by `tools/publish.py` before this skill runs — verify it exists and move on.
E2E conftest.py template
Write the following to `e2e/conftest.py`:
import os
import pytest
from godot_e2e import GodotE2E
GODOT_PROJECT = os.path.join(os.path.dirname(__file__), "..")
GODOT_CONFIG = os.path.join(GODOT_PROJECT, ".claude", "godotmaker.yaml")
def _read_godot_path():
try:
with open(GODOT_CONFIG, "r", encoding="utf-8") as f:
for line in f:
line = line.split("#", 1)[0].strip()
if line.startswith("godot_path:"):
value = line.split(":", 1)[1].strip().strip("\"'")
return value or None
except OSError:
return None
return None
GODOT_PATH = _read_godot_path()
@pytest.fixture(scope="module")
def _game_process():
with GodotE2E.launch(
GODOT_PROJECT,
godot_path=GODOT_PATH,
timeout=15.0,
) as game:
game.wait_for_node("/root/Main", timeout=10.0)
yield game
@pytest.fixture(scope="function")
def game(_game_process):
_game_process.reload_scene()
_game_process.wait_for_node("/root/Main", timeout=5.0)
yield _game_process3. Initial commit
git init # if not already a git repo <godot_path> --headless --import # generate .godot/ cache + *.uid sidecars git add -A git commit -m "Scaffold: initial Godot project with addons"
Required for `isolation: "worktree"` in worker dispatch — without `HEAD`, parallel workers fail with `fatal: not a valid object name: 'HEAD'`.
4. Verify
python tools/check_project.py <project_dir> --build
`--build`
Autonomous text-to-game pipeline for Godot, powered by Claude Code,Codex,Opencode
Repo: RandallLiuXin/GodotMaker
Other skills on godotmaker.
- /background-map
Generate and validate a fixed-viewport background, map base, or parallax plate as a ready-to-load Texture2D.
Open skill - /card-kit
Produce reusable card art sources and native Godot card UI resources.
Open skill - /character-bundle
Produce one illustrated character SpriteFrames resource from high-level body-action intent, optional character and style references, and a resolved animation plan.
Open skill - /compact-prop-pack
Produce a reusable compact-prop atlas from one provider source sheet, with independently loadable AtlasTexture resources for every declared prop.
Open skill - /fx-bundle
Produce a standalone static Texture2D effect or one explicitly timed animated SpriteFrames effect.
Open skill - /platform-strip
Generate non-pixel-art, horizontally repeatable platform strips from real image sources as fixed Texture2D cells or AtlasTexture regions.
Open skill

