GodotMaker Verify
$ARGUMENTS
You are performing mechanical verification of a built Godot game project. This is a non-creative, checklist-driven process.
Session Setup
FIRST ACTION — before anything else: Write verify to .godotmaker/current_role.
Permission: Read-only with three exceptions — you may write .godotmaker/current_role, append to .godotmaker/stage.jsonl, and write .godotmaker/verify_report.json. Verify never modifies game code or planning docs.
Resume Check
Read .godotmaker/stage.jsonl (treat as empty if missing) — each line is {"role": X, "ts": Y}.
- If no event with
role == "build"AND no event withrole == "fixgap"exists anywhere in the file → STOP. Tell user to run/gm-buildfirst. - If the last event has
role == "verify"→ STOP. Tell the user:"Verify already ran at {timestamp} with no state-changing event since. Recommended next: /gm-evaluate. If you need to redo this step or have other plans, just tell me."
- Otherwise → proceed (verify is naturally re-invoked after each build/fixgap cycle).
Run the checks
From the project root:
python tools/run_verify.py
run_verify.py wraps the four mechanical checks (build / unit tests /
lint / static check) and prints a JSON document matching Output Format
Section B to stdout. Capture stdout.
What the script does:
- Reads
godot_pathfrom.claude/godotmaker.yaml; falls back to plaingodotfrom PATH. A missing or broken binary surfaces as atooling_notes[].suggested_fallback = "escalate"entry. - Runs
<godot_path> --headless --quitand writes blocking Godot diagnostics tochecks.build.errors[]. - Runs
<godot_path> --headless ... res://addons/gdUnit4/bin/GdUnitCmdTool.gd --ignoreHeadlessMode --add res://test/ --report-directory <temp>and parses the generated JUnit XML intochecks.unit_tests.{passed, failed, failures[]}. Stdout is diagnostic fallback only. - Stubs
checks.lintaspasswithformat_drift: null. Do NOT re-enable here. - Delegates
checks.static_checktopython tools/check_project.py <project_dir> --build --ecs --tests --plan --mcp. - Exit code 0 = ran to completion (per-check pass/fail is in the JSON).
Sanity-check the script output
Before writing the report, validate. Block on any of these:
- JSON does not parse, or any of
result/ts/checks/tooling_notesis missing - Any of the four
checks.{build,unit_tests,lint,static_check}entries is absent result == "pass"buttooling_notesis non-empty — re-run or escalatechecks.unit_tests.passed + .failed == 0— spot-check by running the gdUnit4 command directly with--report-directory <temp>- Any
tooling_notesentry whosecrashed_onlooks unrelated to the failing check
If any block fires, diagnose by running the implicated command
yourself, then either re-run run_verify.py or surface the issue
verbatim to the user. Do NOT silently rewrite the script's output.
Output Format
You produce two outputs:
A. Human-readable report (chat)
Build this from the JSON the script returned — do not re-run any command for the chat side.
## Verification Report
### Build
Result: PASS | FAIL
{If FAIL, one line per checks.build.errors[] entry: `- {file}:{line}: {message}` (file/line may be empty)}
### Unit Tests
Result: PASS | FAIL
{N passed, M failed}
{If FAIL, one line per checks.unit_tests.failures[]: `- {test}: {message}`}
### Lint
Status: SKIP (gdtoolkit disabled — ROADMAP R-112)
### Static Check
Result: PASS | FAIL
{If FAIL, one line per checks.static_check.issues[]: `- {check}: {detail}`}
### Overall: PASS | FAIL
{If tooling_notes is non-empty, append:
## Tooling Notes
- {tool}: {error} (suggested_fallback: {suggested_fallback})
…}
B. Machine-readable report (.godotmaker/verify_report.json)
Write this file every run (PASS or FAIL). /gm-build and /gm-fixgap read it on their next invocation to translate failures into pending tasks.
Schema:
{
"result": "pass | fail",
"ts": "<UTC ISO 8601 timestamp, e.g. 2026-05-07T14:23:00Z>",
"checks": {
"build": {
"result": "pass | fail | error",
"errors": [
{"file": "src/foo.gd", "line": 42, "message": "Identifier 'bar' not declared"}
]
},
"unit_tests": {
"result": "pass | warn | fail | error",
"passed": 624,
"failed": 0,
"failures": [
{"test": "test_player_input::test_jump", "message": "expected 10, got 0"}
],
"warnings": [
"Found 4 possible orphan nodes."
]
},
"lint": {
"result": "pass | warn | fail | error",
"issues": [
{"file": "src/foo.gd", "rule": "max-line-length", "message": "line too long"}
],
"format_drift": {
"file_count": 92,
"command": "gdformat src/ test/ scenes/"
}
},
"static_check": {
"result": "pass | fail | error",
"issues": [
{"check": "missing_unit_test", "detail": "s_level_up_overlay has no test"}
]
}
},
"tooling_notes": [
{
"tool": "gdlint",
"crashed_on": "src/foo.gd",
"error": "NotImplementedError at gdtoolkit/linter/class_checks.py:144",
"suggested_fallback": "exclude_file",
"narrowed_command": null,
"rule_name": null,
"check_name": null
}
]
}
Field rules:
Top-level
result—"pass"iff everychecks.*.result∈ {pass,warn}. Anyfail/errormakes overallfail.tooling_notesalone never makes overallfail— theerrorit pairs with does.ts— UTC ISO 8601 at the moment you write the file. Consumers compare it against their own last-event timestamp for freshness.All array fields are required (possibly empty
[]). Do not omit them.Per-check
result—pass/failare project-content.warnis non-blocking diagnostic noise (lint style drift or gdUnit warnings such as orphan nodes when every assertion passed).errormeans the tool itself crashed and the project's actual state is unknown for this check; pairerrorwith exactly onetooling_notesentry. Consumers fixerrorvia config, NOT project code.format_drift— object whengdformat --checkreports drift;nullotherwise.suggested_fallback+ matching operand — the producer fills the operand so the consumer can act deterministically:suggested_fallbackRequired operand exclude_filecrashed_on(already required on every note)scope_narrownarrowed_command(replacement command, e.g."gdlint src/")add_gdlintrc_rulerule_name(e.g."class-name")skip_checkcheck_name(e.g."missing_unit_test")escalate— (none) Producer rule: if you cannot fill the required operand for a non-
escalatefallback, emitescalateinstead.Consumer rule (open-enum forward-compat): a missing required operand or an unknown
suggested_fallbackvalue MUST be treated asescalate(surface to user, do NOT auto-fix). Never crash.
On Failure
When the script's JSON has result: "fail":
- Write the script's JSON verbatim to
.godotmaker/verify_report.json. - Emit the chat report (Section A) and tell the user which checks failed. Suggest
/gm-buildif the last state-changing event wasbuild,/gm-fixgapif it wasfixgap. - Do NOT append a
verifyevent tostage.jsonl— only PASS records a stage event.
When Done
When the script's JSON has result: "pass":
- Write the script's JSON verbatim to
.godotmaker/verify_report.json. (Field rules apply:tooling_notes == [], allchecks.*.result∈ {pass,warn} — the script enforces these but spot-check them once more before writing.) - From the project root run
python tools/append_stage_event.py verifyto append a{"role": "verify", "ts": "<server-generated UTC>"}line to.godotmaker/stage.jsonl. Do NOT hand-write the JSON or the timestamp — the helper exists so the timestamp comes from the system clock, not your own output. git add -A && git commit -m "chore(verify): <Tag>"- Inform the user:
Verify complete. Recommended next: /gm-evaluate