Skill v1.0.1
currentAutomated scan100/100+18 new
version: "1.0.1" name: validate-app description: >- Use to validate a Resident Lua app locally before pushing to a device. Runs the app through a local lua interpreter with auto-deduced stubs and checks for compile errors, missing lifecycle functions, and runtime errors during a few simulated ticks. Triggered by /resident:validate-app or when the user asks "validate this app".
validate-app
Run a Resident Lua app file through the local lua interpreter with permissive stubs to catch compile errors, missing lifecycle, and obvious runtime bugs before sending it to a device.
What you need
- A Lua app file — pass as positional arg, or pipe via stdin.
- A DEVICE-SKILL.md — read from the firmware project root (cwd by
default). Used to deduce which device modules to stub. Optional: if absent, only sandbox-generic stubs are applied.
- `lua` — the Lua interpreter must be on PATH. If missing, the skill
exits with a clear hint to install it (brew install lua).
- Optional `--ref <path>` flags — additional Markdown files
describing extra modules. validate-app processes each ref file the same way as DEVICE-SKILL.md: deduces module names from fenced Lua code blocks and appends any ## Validation stubs block. Repeatable. Plain .lua refs (no Markdown structure) are silently ignored. Useful when an app uses sandbox extensions documented outside DEVICE-SKILL.md.
Usage
"${CLAUDE_PLUGIN_ROOT}/skills/validate-app/tools/validate.sh" path/to/app.luacat app.lua | "${CLAUDE_PLUGIN_ROOT}/skills/validate-app/tools/validate.sh""${CLAUDE_PLUGIN_ROOT}/skills/validate-app/tools/validate.sh" --device-skill path/to/DEVICE-SKILL.md path/to/app.lua"${CLAUDE_PLUGIN_ROOT}/skills/validate-app/tools/validate.sh" --device-skill path/to/DEVICE-SKILL.md --ref path/to/extensions.md path/to/app.lua
${CLAUDE_PLUGIN_ROOT} is set by Claude Code to the absolute path of the installed plugin — always use it to reference bundled tools; the CWD is the user's project, not the skill directory.
Exit codes:
0— passed (compile + lifecycle + 5 ticks all OK)1— validation failed (single-line error to stderr)2— environment error (lua missing, file not found, etc.)
Scope
The validator does NOT run the actual device firmware. It uses loose stubs:
- Sandbox built-ins (
log.*,time.*) are hardcoded with neutral return
values; time.* has the real module's shape (time.localtime() returns a full struct_time table, time.ticks_ms() an integer, etc.).
datetimeis the device's own module (tools/datetime.lua, a verbatim
copy of Resident's), over a fixed clock: synced, 2026-10-03 12:00:00 UTC, zone UTC. Its arithmetic, comparisons and argument errors are real; strftime returns the format unchanged.
- There are no bare math globals (
floor,sin, …) and no
rgb/fract/beat/noise2d, because the sandbox has none: an app calling one fails validation. Apps use math.*.
- Device modules (whatever DEVICE-SKILL.md mentions) get a permissive
metatable that returns a no-op function for any access.
This catches: syntax errors, missing lifecycle, obvious type errors and nil-dereferences. It does not catch: hardware-specific behavior, timing issues, or modules referenced in code but absent from DEVICE-SKILL.md (those will fall back to a global __index no-op trap).
Optional: validation stubs in DEVICE-SKILL.md
If auto-deduced stubs are too loose for your apps (e.g. apps do arithmetic on the result of screen.width()), add a ## Validation stubs section to your DEVICE-SKILL.md with a Lua block that provides concrete return values for getter-style functions. validate-app injects that block after the auto-deduced stubs, so it overrides them.
Example (m5stick-demo's DEVICE-SKILL.md):
screen = setmetatable({width = function() return 240 end,height = function() return 135 end,}, { __index = function() return function() end end })imu = setmetatable({accel = function() return 0, 0, 1 end,gyro = function() return 0, 0, 0 end,}, { __index = function() return function() end end })
Composing with other skills
validate-app is independent. The expected pattern is:
create-app --out apps/foo.lua "<description>"validate-app apps/foo.lua→ if fails, re-prompt create-app with the
error and iterate.
push-app --base-url <url> --device-id <id> apps/foo.lua