{
  "id": "roblox-luau-pure-modules",
  "type": "loadout",
  "title": "Roblox pure Luau modules checked outside Studio",
  "summary": "Keep Roblox game rules in pure --!strict Luau modules that the luau CLI can type-check and test in milliseconds, with a scanner that keeps Roblox APIs out of them.",
  "description": "Roblox logic is usually tried out by pressing Play in Studio, which an agent can't do after every change. Rules that never touch the DataModel can be checked by the standalone Luau tools instead: luau-analyze type-checks them in strict mode, and luau runs their tests from the command line.\n\nThis loadout sets out the split. Prices, inventory, damage and cooldown maths go in src/shared/pure as --!strict modules that require each other by string (require(\"./Money\"), which Roblox and the luau CLI both resolve). Server and client scripts stay as thin edges that own services, Instances and remotes. A standard-library Python scanner flags Roblox-only calls in the pure folder (game, workspace, script.Parent requires, Instance.new, services, task and the deprecated wait/spawn/delay, Roblox datatypes) as errors, and math.random, os.time and print as warnings. A small test runner prints PASS or FAIL per case and exits non-zero on failure. The verifier runs it all with the official Luau 0.743 tools on a Rojo shop project, and shows a broken rule failing the run.\n",
  "highlights": [
    "Pure --!strict modules in src/shared/pure, thin edge scripts for Roblox APIs",
    "Scanner flags game, script.Parent requires, Instance.new and task.* in pure code",
    "luau-analyze --mode=strict checks the rules and their tests outside Studio",
    "A tiny test runner for the luau CLI that exits non-zero on failure",
    "String requires that work in both Roblox and the luau CLI"
  ],
  "version": "1.0.0",
  "license": "MIT",
  "author": {
    "name": "SetupBay",
    "url": "https://setupbay.com"
  },
  "credits": [
    {
      "title": "Luau releases (luau and luau-analyze)",
      "url": "https://github.com/luau-lang/luau/releases/tag/0.743"
    },
    {
      "title": "Luau RFC: require by string with relative paths",
      "url": "https://github.com/luau-lang/rfcs/blob/master/docs/new-require-by-string-semantics.md"
    },
    {
      "title": "Roblox DevForum: introducing require-by-string",
      "url": "https://devforum.roblox.com/t/introducing-require-by-string/3405078"
    },
    {
      "title": "Rojo docs: project format",
      "url": "https://rojo.space/docs/v7/project-format/"
    },
    {
      "title": "@AICoderClass: Roblox Studio now has a built-in MCP server",
      "url": "https://x.com/AICoderClass/status/2111850014351781899"
    }
  ],
  "categories": [
    "gamedev"
  ],
  "stacks": [
    "roblox",
    "luau",
    "rojo"
  ],
  "files": [
    {
      "path": "AGENTS.md",
      "role": "instructions",
      "description": "The pure/edge split, the rules for each, and the done checklist.",
      "skill": null,
      "agent": null,
      "install_to": {
        "claude-code": "AGENTS.md",
        "codex": "AGENTS.md",
        "cursor": "AGENTS.md",
        "gemini-cli": "AGENTS.md",
        "antigravity-cli": "AGENTS.md",
        "copilot": "AGENTS.md"
      },
      "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/AGENTS.md",
      "raw_latest_url": "https://setupbay.com/raw/roblox-luau-pure-modules/latest/AGENTS.md",
      "bytes": 2646,
      "lang": "markdown",
      "content": "# AGENTS.md: Roblox game with pure Luau modules checked outside Studio\n\nGame rules (prices, inventory, damage, cooldown maths, matchmaking) live in **pure modules** in `src/shared/pure/`.\nA pure module never touches the DataModel, so the plain `luau` CLI can type-check and test it in seconds, with no\nStudio open. Scripts in `src/server/` and `src/client/` are the **edge**: they get services, read and write\nInstances, handle remotes, then call the pure modules with plain values.\n\n## Layout (Rojo)\n```\nsrc/shared/pure/   → ReplicatedStorage.Shared.pure   pure ModuleScripts, --!strict, no Roblox APIs\nsrc/shared/        → ReplicatedStorage.Shared        other shared modules and Remotes\nsrc/server/        → ServerScriptService.Server      server edge scripts (*.server.luau)\nsrc/client/        → StarterPlayerScripts            client edge scripts (*.client.luau)\ntests/             (not synced)                      *.spec.luau and run.luau, for the luau CLI\n```\n\n## Rules for pure modules\n- First line `--!strict`. Export the types callers need (`export type State = {...}`).\n- No `game`, `workspace`, `script`, `Instance`, services, `task.*`, `wait`/`spawn`/`delay`, `tick()`, or Roblox\n  datatypes (`Vector3`, `CFrame`, `Color3`, `Enum`, `Random`). Take what you need as parameters.\n- Require other pure modules by string: `require(\"./Money\")`. Roblox (require-by-string) and the luau CLI both\n  resolve it relative to the file. Never `require(script.Parent.Money)` inside `pure/`.\n- No `print`/`warn`, no `math.random` or `os.time` inside rules. Return values and errors, and take an rng or the\n  current time as a parameter, so tests are repeatable.\n- Validate inputs in the pure function too (whole numbers, ranges, NaN). The edge script checks types from remotes;\n  the rules decide what's allowed.\n\n## Rules for edge scripts\n- Keep them thin: read Instances and remote arguments, call the pure module, apply the result.\n- Use `task.wait` / `task.spawn` / `task.delay`, never the deprecated `wait` / `spawn` / `delay`.\n- The server decides: remotes carry intent, and the server runs the pure rules before changing any state.\n\n## Before you say \"done\"\n1. `python3 .agents/skills/luau-pure-tests/scripts/pure_luau_guard.py src/shared/pure` reports `0 error(s)`.\n2. `luau-analyze --mode=strict src/shared/pure tests` reports nothing.\n3. `luau tests/run.luau` ends with `0 failed`. Add a test for every rule you change.\n4. List what the human should play-test in Studio for the edge scripts. If Roblox Studio's built-in MCP server is\n   connected, you can run that play-test through it, but the pure tests above still come first.\n"
    },
    {
      "path": "CLAUDE.md",
      "role": "adapter",
      "description": "Imports AGENTS.md for Claude Code.",
      "skill": null,
      "agent": "claude-code",
      "install_to": {
        "claude-code": "CLAUDE.md",
        "codex": null,
        "cursor": null,
        "gemini-cli": null,
        "antigravity-cli": null,
        "copilot": null
      },
      "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/CLAUDE.md",
      "raw_latest_url": "https://setupbay.com/raw/roblox-luau-pure-modules/latest/CLAUDE.md",
      "bytes": 393,
      "lang": "markdown",
      "content": "# CLAUDE.md\n\n@AGENTS.md\n\n## Claude Code notes\n\n- Skills from this loadout live in `.claude/skills/`: `luau-pure-tests`. Use them when their description matches the task.\n- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.\n\n<!-- From SetupBay \"Roblox pure Luau modules checked outside Studio\" v1.0.0 — https://setupbay.com/loadouts/roblox-luau-pure-modules/ -->\n"
    },
    {
      "path": "GEMINI.md",
      "role": "adapter",
      "description": "Imports AGENTS.md for Gemini CLI.",
      "skill": null,
      "agent": "gemini-cli",
      "install_to": {
        "claude-code": null,
        "codex": null,
        "cursor": null,
        "gemini-cli": "GEMINI.md",
        "antigravity-cli": null,
        "copilot": null
      },
      "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/GEMINI.md",
      "raw_latest_url": "https://setupbay.com/raw/roblox-luau-pure-modules/latest/GEMINI.md",
      "bytes": 394,
      "lang": "markdown",
      "content": "# GEMINI.md\n\n@./AGENTS.md\n\n## Gemini CLI notes\n\n- Skills from this loadout live in `.agents/skills/`: `luau-pure-tests`. Use them when their description matches the task.\n- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.\n\n<!-- From SetupBay \"Roblox pure Luau modules checked outside Studio\" v1.0.0 — https://setupbay.com/loadouts/roblox-luau-pure-modules/ -->\n"
    },
    {
      "path": ".cursor/rules/luau-pure-modules.mdc",
      "role": "rules",
      "description": "Never/instead table for pure modules and tests.",
      "skill": null,
      "agent": null,
      "install_to": {
        "claude-code": null,
        "codex": null,
        "cursor": ".cursor/rules/luau-pure-modules.mdc",
        "gemini-cli": null,
        "antigravity-cli": null,
        "copilot": null
      },
      "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/.cursor/rules/luau-pure-modules.mdc",
      "raw_latest_url": "https://setupbay.com/raw/roblox-luau-pure-modules/latest/.cursor/rules/luau-pure-modules.mdc",
      "bytes": 900,
      "lang": "markdown",
      "content": "---\ndescription: Pure Luau modules in src/shared/pure must run outside Studio\nglobs: \"src/shared/pure/**/*.luau,tests/**/*.luau\"\nalwaysApply: false\n---\n\nFiles in `src/shared/pure/` are pure: the luau CLI must be able to run them with no Roblox engine.\n\n| Never in `pure/` | Instead |\n|---|---|\n| `game:GetService(...)`, `workspace`, `script.Parent` | pass the values in from the edge script |\n| `require(script.Parent.Money)` | `require(\"./Money\")` |\n| `Instance.new`, `Vector3`, `CFrame`, `Enum`, `Random` | plain numbers and tables |\n| `task.wait`, `wait`, `spawn`, `delay`, `tick()` | return a value; the edge script schedules |\n| `math.random()`, `os.time()` | take `rng` / `now` as parameters |\n| `print`, `warn` | return `false, \"reason\"` |\n\n- `--!strict` on the first line, with exported types.\n- Every change gets a case in `tests/*.spec.luau`. Then run the `luau-pure-tests` skill's checks.\n"
    },
    {
      "path": "skills/luau-pure-tests/SKILL.md",
      "role": "skill",
      "description": "Check purity, type-check in strict mode, then run the tests with the luau CLI.",
      "skill": "luau-pure-tests",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/luau-pure-tests/SKILL.md",
        "codex": ".agents/skills/luau-pure-tests/SKILL.md",
        "cursor": ".agents/skills/luau-pure-tests/SKILL.md",
        "gemini-cli": ".agents/skills/luau-pure-tests/SKILL.md",
        "antigravity-cli": ".agents/skills/luau-pure-tests/SKILL.md",
        "copilot": ".agents/skills/luau-pure-tests/SKILL.md"
      },
      "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/SKILL.md",
      "raw_latest_url": "https://setupbay.com/raw/roblox-luau-pure-modules/latest/skills/luau-pure-tests/SKILL.md",
      "bytes": 2699,
      "lang": "markdown",
      "content": "---\nname: luau-pure-tests\ndescription: Keep a Roblox game's rules in pure Luau modules and run their tests outside Studio with the luau CLI, checking purity with a scanner and types with luau-analyze in strict mode. Use when writing or changing game logic (prices, inventory, damage, cooldowns), when asked to add tests to a Roblox project, or when a module can't run in a test because it calls Roblox APIs.\n---\n\n# Pure Luau modules, checked outside Studio\n\nRoblox logic is usually tried out by pressing Play. Rules that don't touch the DataModel can be checked by the\nstandalone Luau tools instead: `luau-analyze` type-checks them, and `luau` runs their tests in milliseconds.\nThat's something an agent can run after every change.\n\n## Tools\nThe Luau release (https://github.com/luau-lang/luau/releases) ships `luau` (runs scripts) and\n`luau-analyze` (type-checks and lints) for Linux, macOS and Windows. Rokit or Aftman can install them per project;\notherwise download the release zip. Check with `luau-analyze --help`.\n\n## 1. Check purity\n```bash\npython3 .agents/skills/luau-pure-tests/scripts/pure_luau_guard.py src/shared/pure\n```\n(Use the path the skill was installed to: `.claude/skills/...` for Claude Code, `.agents/skills/...` for the others.)\n\nRun it on the pure folders only; edge scripts are meant to use Roblox APIs. Each `error` is a Roblox-only call that\nstops the module running outside Studio. Move that call into the edge script and pass the value in. `warn` lines\n(`math.random`, `os.time`, `print`) make tests unrepeatable; take an rng or the time as a parameter.\n\n## 2. Type-check in strict mode\n```bash\nluau-analyze --mode=strict src/shared/pure tests\n```\nIt must print nothing. `Unknown global 'game'` or `Unknown require: unsupported path` means a Roblox API or an\nInstance-path require got into a pure module.\n\n## 3. Run the tests\nCopy `references/run.luau` to `tests/run.luau` once and list each spec file in it. A spec returns a list of cases:\n```luau\n--!strict\nlocal Inventory = require(\"../src/shared/pure/Inventory\")\nreturn {\n\t{ name = \"Inventory: the stack limit holds\", run = function()\n\t\tlocal s = Inventory.new(500)\n\t\tassert(Inventory.buy(s, { id = \"sword\", price = 150, stack = 1 }, 1))\n\t\tassert(not Inventory.buy(s, { id = \"sword\", price = 150, stack = 1 }, 1))\n\tend },\n}\n```\n```bash\nluau tests/run.luau\n```\nIt prints `PASS`/`FAIL` per case and `N passed, M failed`, and exits non-zero on any failure.\n\n## 4. Report\nSay which rules changed, which tests you added, and that the scanner, `luau-analyze --mode=strict` and\n`luau tests/run.luau` were clean. Then list what still needs a Studio play-test: the edge scripts, remotes and\nanything that touches Instances.\n"
    },
    {
      "path": "skills/luau-pure-tests/scripts/pure_luau_guard.py",
      "role": "reference",
      "description": "Read-only scanner for Roblox-only calls in pure Luau modules.",
      "skill": "luau-pure-tests",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/luau-pure-tests/scripts/pure_luau_guard.py",
        "codex": ".agents/skills/luau-pure-tests/scripts/pure_luau_guard.py",
        "cursor": ".agents/skills/luau-pure-tests/scripts/pure_luau_guard.py",
        "gemini-cli": ".agents/skills/luau-pure-tests/scripts/pure_luau_guard.py",
        "antigravity-cli": ".agents/skills/luau-pure-tests/scripts/pure_luau_guard.py",
        "copilot": ".agents/skills/luau-pure-tests/scripts/pure_luau_guard.py"
      },
      "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/scripts/pure_luau_guard.py",
      "raw_latest_url": "https://setupbay.com/raw/roblox-luau-pure-modules/latest/skills/luau-pure-tests/scripts/pure_luau_guard.py",
      "bytes": 5858,
      "lang": "markdown",
      "content": "#!/usr/bin/env python3\n\"\"\"Find Roblox-only calls in Luau modules meant to be pure (no DataModel), so they run and test outside Studio. Run it on the pure folders only, such as src/shared.\n\nUsage: python3 pure_luau_guard.py [PATH ...]        (default: the current folder)\n       python3 pure_luau_guard.py --list            (print every rule)\n\nA \"guard: ok\" comment on a line silences its warnings (never its errors).\nPrints one line per finding:  path:line: level [rule] what is wrong -> what to use\nExit 0 when nothing at \"error\" level is found, 1 when something is, 2 on bad usage.\nStandard library only. Read-only: it never edits a file and makes no network calls.\n\"\"\"\nimport fnmatch\nimport os\nimport re\nimport sys\n\n# (rule id, level, regex, message, file patterns). Patterns run against each line with comments removed.\nRULES = [\n    ('datamodel', 'error', '\\\\b(game|plugin)\\\\b\\\\s*[:.]|\\\\bworkspace\\\\b|\\\\bscript\\\\.(Parent|Name)\\\\b|\\\\bInstance\\\\.new\\\\(',\n     \"pure modules can't touch the DataModel (game, workspace, script, Instance) -> pass plain values in from the server or client script\", []),\n    ('instance-require', 'error', '\\\\brequire\\\\(\\\\s*(script|game)\\\\b',\n     'Instance-path require only works in Studio -> require(\"./Sibling\") or require(\"../Folder/Module\"), which Roblox and the luau CLI both resolve', []),\n    ('roblox-services', 'error', '\\\\b(Players|ReplicatedStorage|ServerStorage|RunService|DataStoreService|HttpService|TweenService|UserInputService|MarketplaceService|MemoryStoreService)\\\\b\\\\s*[:.]',\n     'a Roblox service in a pure module -> keep services in the edge script and pass what you need in', []),\n    ('task-library', 'error', '\\\\btask\\\\.(wait|spawn|delay|defer|cancel)\\\\(',\n     'the task library is Roblox-only -> keep scheduling in the edge script; pure functions return values', []),\n    ('deprecated-scheduler', 'error', '(?<![\\\\w.:])(wait|spawn|delay)\\\\(',\n     'wait/spawn/delay are deprecated in Roblox and missing in the luau CLI -> task.wait/task.spawn/task.delay in edge scripts only', []),\n    ('roblox-types', 'error', '\\\\b(Vector3|CFrame|Color3|UDim2|BrickColor|TweenInfo|Enum|Random|DateTime)\\\\b\\\\s*[.(]',\n     'a Roblox datatype in a pure module -> use plain numbers and tables, or pass the value in', []),\n    ('tick', 'error', '(?<![\\\\w.:])tick\\\\(\\\\)',\n     'tick() is deprecated -> os.clock() for timing, or pass the time in', []),\n    ('connect-lowercase', 'error', ':connect\\\\(',\n     ':connect is deprecated -> :Connect, and only in edge scripts', []),\n    ('math-random', 'warn', '\\\\bmath\\\\.random\\\\(',\n     'random numbers make pure functions hard to test -> take an rng function or a seed as a parameter', []),\n    ('os-time', 'warn', '\\\\bos\\\\.(time|clock)\\\\(',\n     'reading the clock makes pure functions hard to test -> take the time as a parameter', []),\n    ('print-in-module', 'warn', '(?<![\\\\w.:])(print|warn)\\\\(',\n     'printing from a pure module -> return the value or an error and let the caller log', []),\n]\nFILES = ['*.luau', '*.lua']\nCOMMENT = '--'\nSKIP_DIRS = {\".git\", \".venv\", \"venv\", \"node_modules\", \"__pycache__\", \".agents\", \".claude\", \".cursor\", \"dist\", \"build\", \"target\", \".godot\"}\nCOMPILED = [(rid, lvl, re.compile(rx), msg, globs) for rid, lvl, rx, msg, globs in RULES]\n\n\ndef strip_comment(line):\n    \"\"\"Drop a trailing comment, leaving string literals alone.\"\"\"\n    q = None\n    i = 0\n    while i < len(line):\n        c = line[i]\n        if q:\n            if c == \"\\\\\":\n                i += 2\n                continue\n            if c == q:\n                q = None\n        elif c in \"\\\"'`\":\n            q = c\n        elif line.startswith(COMMENT, i):\n            return line[:i]\n        i += 1\n    return line\n\n\ndef wanted(name):\n    return any(fnmatch.fnmatch(name, g) for g in FILES)\n\n\ndef walk(paths):\n    for p in paths:\n        if os.path.isfile(p):\n            yield p\n            continue\n        for root, dirs, files in os.walk(p):\n            dirs[:] = sorted(d for d in dirs if d not in SKIP_DIRS)\n            for f in sorted(files):\n                if wanted(f):\n                    yield os.path.join(root, f)\n\n\ndef scan(path):\n    out = []\n    name = os.path.basename(path)\n    try:\n        with open(path, encoding=\"utf-8\", errors=\"replace\") as fh:\n            lines = fh.read().splitlines()\n    except OSError as e:\n        return [(path, 0, \"error\", \"unreadable\", str(e))]\n    for n, raw in enumerate(lines, 1):\n        code = strip_comment(raw)\n        if not code.strip():\n            continue\n        accepted = \"guard: ok\" in raw  # a person accepted the warnings on this line\n        for rid, lvl, rx, msg, globs in COMPILED:\n            if accepted and lvl == \"warn\":\n                continue\n            if globs and not any(fnmatch.fnmatch(name, g) for g in globs):\n                continue\n            if rx.search(code):\n                out.append((path, n, lvl, rid, msg))\n    return out\n\n\ndef main(argv):\n    if \"--list\" in argv:\n        for rid, lvl, _, msg, globs in RULES:\n            print(f\"{lvl:5} [{rid}] {msg}\" + (f\"  (only {', '.join(globs)})\" if globs else \"\"))\n        return 0\n    if any(a.startswith(\"-\") for a in argv):\n        print(__doc__)\n        return 2\n    paths = argv or [\".\"]\n    for p in paths:\n        if not os.path.exists(p):\n            print(f\"no such path: {p}\", file=sys.stderr)\n            return 2\n    files = list(walk(paths))\n    findings = []\n    for f in files:\n        findings.extend(scan(f))\n    for path, n, lvl, rid, msg in findings:\n        print(f\"{os.path.relpath(path)}:{n}: {lvl} [{rid}] {msg}\")\n    errors = sum(1 for x in findings if x[2] == \"error\")\n    warns = sum(1 for x in findings if x[2] == \"warn\")\n    print(f\"pure_luau_guard: {len(files)} file(s), {errors} error(s), {warns} warning(s)\")\n    return 1 if errors else 0\n\n\nif __name__ == \"__main__\":\n    sys.exit(main(sys.argv[1:]))\n"
    },
    {
      "path": "skills/luau-pure-tests/references/run.luau",
      "role": "reference",
      "description": "Minimal test runner for spec files under the luau CLI.",
      "skill": "luau-pure-tests",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/luau-pure-tests/references/run.luau",
        "codex": ".agents/skills/luau-pure-tests/references/run.luau",
        "cursor": ".agents/skills/luau-pure-tests/references/run.luau",
        "gemini-cli": ".agents/skills/luau-pure-tests/references/run.luau",
        "antigravity-cli": ".agents/skills/luau-pure-tests/references/run.luau",
        "copilot": ".agents/skills/luau-pure-tests/references/run.luau"
      },
      "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/references/run.luau",
      "raw_latest_url": "https://setupbay.com/raw/roblox-luau-pure-modules/latest/skills/luau-pure-tests/references/run.luau",
      "bytes": 485,
      "lang": "markdown",
      "content": "--!strict\n-- Minimal test runner for the luau CLI: luau tests/run.luau\nlocal specs = { require(\"./Inventory.spec\"), require(\"./Money.spec\") }\n\nlocal passed, failed = 0, 0\nfor _, spec in specs do\n\tfor _, case in spec do\n\t\tlocal ok, err = pcall(case.run)\n\t\tif ok then\n\t\t\tpassed += 1\n\t\t\tprint(`PASS {case.name}`)\n\t\telse\n\t\t\tfailed += 1\n\t\t\tprint(`FAIL {case.name}: {err}`)\n\t\tend\n\tend\nend\nprint(`{passed} passed, {failed} failed`)\nif failed > 0 then\n\terror(`{failed} test(s) failed`, 0)\nend\n"
    },
    {
      "path": "skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
      "role": "reference",
      "description": "A Studio-coupled inventory module for checking the guard.",
      "skill": "luau-pure-tests",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
        "codex": ".agents/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
        "cursor": ".agents/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
        "gemini-cli": ".agents/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
        "antigravity-cli": ".agents/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
        "copilot": ".agents/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt"
      },
      "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
      "raw_latest_url": "https://setupbay.com/raw/roblox-luau-pure-modules/latest/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
      "bytes": 870,
      "lang": "markdown",
      "content": "-- A Studio-coupled inventory module, used to check the guard. It only runs inside Roblox.\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\nlocal Money = require(script.Parent.Money)\nlocal Config = require(game.ReplicatedStorage.Shared.Config)\n\nlocal Inventory = {}\n\nfunction Inventory.buy(player, item, quantity)\n\tlocal stats = player:FindFirstChild(\"leaderstats\")\n\tif stats.Coins.Value < item.price * quantity then\n\t\twarn(\"not enough coins\")\n\t\treturn false\n\tend\n\tstats.Coins.Value -= item.price * quantity\n\tlocal drop = Instance.new(\"Part\")\n\tdrop.Position = Vector3.new(0, 10, 0)\n\tdrop.Parent = workspace\n\tif math.random() < Config.bonusChance then\n\t\tspawn(function()\n\t\t\twait(2)\n\t\t\tstats.Coins.Value += 5\n\t\tend)\n\tend\n\tplayer:SetAttribute(\"lastBuy\", tick())\n\tReplicatedStorage.Remotes.Bought:FireClient(player, item.id)\n\treturn true\nend\n\nreturn Inventory\n"
    }
  ],
  "requires": {
    "tools": [
      "python3",
      "luau"
    ],
    "env": [],
    "mcp": []
  },
  "tested": {
    "status": "untested",
    "last_run": null,
    "fixture": null,
    "per_agent": {
      "claude-code": "untested",
      "codex": "untested",
      "cursor": "untested",
      "gemini-cli": "untested",
      "antigravity-cli": "untested",
      "copilot": "untested"
    }
  },
  "security": {
    "scanned": true,
    "scanner": "intake-precheck/2",
    "verdict": "clean",
    "network": false,
    "scripts": [
      "pure_luau_guard.py"
    ],
    "notes": "The scanner only reads .luau and .lua files and prints findings; it never edits files and makes no network calls. The test runner only runs the project's own spec files."
  },
  "featured": false,
  "deprecated": false,
  "last_checked": "2026-10-29",
  "published": "2026-10-29",
  "updated": "2026-10-29",
  "compatibility": {
    "claude-code": "native",
    "codex": "native",
    "cursor": "native",
    "gemini-cli": "native",
    "antigravity-cli": "native",
    "copilot": "native"
  },
  "counts": {
    "rules": 2,
    "skills": 1,
    "mcp": 0
  },
  "url": "https://setupbay.com/loadouts/roblox-luau-pure-modules/",
  "api_url": "https://setupbay.com/api/loadouts/roblox-luau-pure-modules.json",
  "verification": {
    "level": "verified-install",
    "status": "verified",
    "label": "Verified install",
    "verified": true,
    "checked_at": "2026-10-29T12:58:44Z",
    "commit": "cc18d7ef3e9c98d99f00c17e2ef9a0565a3f9597",
    "content_current": true,
    "agent_tests": "not-run",
    "counts": {
      "pass": 9,
      "fail": 0,
      "skip": 0,
      "not_applicable": 1
    },
    "meaning": "Level 1: automated checks on the files (parse, install paths, MCP config, safety scan, references, size, licence, starter build). No AI agent was run.",
    "checks": [
      {
        "id": "listing",
        "name": "Listing",
        "status": "pass",
        "summary": "Schema valid; 8 files declared, none missing or extra",
        "details": [
          "tested.status: untested (agent tests are separate from Level 1)"
        ]
      },
      {
        "id": "parse",
        "name": "Files parse",
        "status": "pass",
        "summary": "All 8 files parse; front matter valid",
        "details": []
      },
      {
        "id": "adapters",
        "name": "Adapters",
        "status": "pass",
        "summary": "CLAUDE.md and GEMINI.md import AGENTS.md and are up to date",
        "details": [
          "CLAUDE.md: matches the generator",
          "GEMINI.md: matches the generator"
        ]
      },
      {
        "id": "install-paths",
        "name": "Install paths",
        "status": "pass",
        "summary": "Correct paths for all 6 agents; layouts re-discovered",
        "details": [
          "Claude Code: 6 files, 1 skill in .claude/skills/",
          "Codex CLI: 5 files, 1 skill in .agents/skills/",
          "Cursor: 6 files, 1 skill in .agents/skills/, 1 rule",
          "Gemini CLI: 6 files, 1 skill in .agents/skills/",
          "Antigravity CLI: 5 files, 1 skill in .agents/skills/",
          "GitHub Copilot: 5 files, 1 skill in .agents/skills/"
        ]
      },
      {
        "id": "mcp",
        "name": "MCP config",
        "status": "n/a",
        "summary": "No MCP servers in this loadout",
        "details": []
      },
      {
        "id": "safety",
        "name": "Safety scan",
        "status": "pass",
        "summary": "Clean: no credentials, risky shell, hidden text or covert instructions",
        "details": [
          "Scanner intake-precheck/2: verdict clean; network no (listing says no)"
        ]
      },
      {
        "id": "references",
        "name": "References",
        "status": "pass",
        "summary": "1 reference between files resolves for every agent",
        "details": []
      },
      {
        "id": "size",
        "name": "Size limits",
        "status": "pass",
        "summary": "8 files, 13.9 KB total; largest skills/luau-pure-tests/scripts/pure_luau_guard.py 5.7 KB",
        "details": [
          "Limits: 30 files, 32 KB per file, 128 KB total, 500 lines per skill or rule"
        ]
      },
      {
        "id": "licence",
        "name": "Licence",
        "status": "pass",
        "summary": "MIT, by SetupBay; text in the repository LICENSE",
        "details": []
      },
      {
        "id": "starter-build",
        "name": "Starter build",
        "status": "pass",
        "summary": "pure_luau_guard.py passes the shop's pure modules and flags every Roblox-only line in the skill's Studio-coupled sample: 13 errors and 2 warnings. With the official Luau 0.743 tools, luau-analyze --mode=strict passes the pure modules and their tests and rejects the sample's game, script and Instance",
        "details": [
          "Applied 12 loadout files at every agent's paths",
          "luau 0.743",
          "✓ scanner: the pure modules are clean: bash -c python3 .agents/skills/luau-pure-tests/scripts/pure_luau_guard.py src/shared/pure; echo \"exit=$?\"",
          "✓ scanner: the Studio-coupled sample is flagged line by line: bash -c mkdir -p coupled && cp .claude/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt coupled/InventoryOld.luau && python3 .claude/skills/luau-pure-tests/scripts/pure_luau_guard.py coupled; echo \"exit=$?\"",
          "✓ place the skill's test runner: bash -c cp .agents/skills/luau-pure-tests/references/run.luau tests/run.luau && echo placed",
          "✓ luau-analyze --mode=strict: pure modules and tests type-check: bash -c \"$(dirname \"$LUAU_BIN\")/luau-analyze\" --mode=strict src/shared/pure tests; echo \"analyze=$?\"",
          "✓ luau-analyze --mode=strict: the sample's Roblox calls don't resolve outside Studio: bash -c \"$(dirname \"$LUAU_BIN\")/luau-analyze\" --mode=strict coupled/InventoryOld.luau; echo \"analyze=$?\"",
          "✓ luau runs the tests outside Studio: bash -c \"$(dirname \"$LUAU_BIN\")/luau\" tests/run.luau; echo \"exit=$?\"",
          "✓ a broken rule fails the run: bash -c cp -r src broken-src && sed -i 's/math.floor(price/math.ceil(price/' broken-src/shared/pure/Money.luau && sed 's#../src/#../broken-src/#' tests/Money.spec.luau > tests/Broken.spec.luau && sed -i 's#require(\"./Money.spec\")#require(\"./Broken.spec\")#' tests/run.luau && \"$(dirname \"$LUAU_BIN\")/luau\" tests/run.luau; echo \"exit=$?\"",
          "Covers: The scanner's purity rules against a real Studio-coupled module, and clean pure modules",
          "Covers: Luau 0.743's own type checker in strict mode on the pure modules, the specs and the sample",
          "Covers: The skill's test runner under the luau CLI, passing and failing, including a string require between pure modules",
          "Covers: Not covered: the edge scripts and remotes, which need Studio; run a play-test for those"
        ]
      }
    ]
  },
  "install": {
    "claude-code": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "CLAUDE.md",
          "target": "CLAUDE.md",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/CLAUDE.md",
          "role": "adapter"
        },
        {
          "path": "skills/luau-pure-tests/SKILL.md",
          "target": ".claude/skills/luau-pure-tests/SKILL.md",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/luau-pure-tests/scripts/pure_luau_guard.py",
          "target": ".claude/skills/luau-pure-tests/scripts/pure_luau_guard.py",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/scripts/pure_luau_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/luau-pure-tests/references/run.luau",
          "target": ".claude/skills/luau-pure-tests/references/run.luau",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/references/run.luau",
          "role": "reference"
        },
        {
          "path": "skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
          "target": ".claude/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/roblox-luau-pure-modules/claude-code.zip"
    },
    "codex": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/luau-pure-tests/SKILL.md",
          "target": ".agents/skills/luau-pure-tests/SKILL.md",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/luau-pure-tests/scripts/pure_luau_guard.py",
          "target": ".agents/skills/luau-pure-tests/scripts/pure_luau_guard.py",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/scripts/pure_luau_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/luau-pure-tests/references/run.luau",
          "target": ".agents/skills/luau-pure-tests/references/run.luau",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/references/run.luau",
          "role": "reference"
        },
        {
          "path": "skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
          "target": ".agents/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/roblox-luau-pure-modules/codex.zip"
    },
    "cursor": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": ".cursor/rules/luau-pure-modules.mdc",
          "target": ".cursor/rules/luau-pure-modules.mdc",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/.cursor/rules/luau-pure-modules.mdc",
          "role": "rules"
        },
        {
          "path": "skills/luau-pure-tests/SKILL.md",
          "target": ".agents/skills/luau-pure-tests/SKILL.md",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/luau-pure-tests/scripts/pure_luau_guard.py",
          "target": ".agents/skills/luau-pure-tests/scripts/pure_luau_guard.py",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/scripts/pure_luau_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/luau-pure-tests/references/run.luau",
          "target": ".agents/skills/luau-pure-tests/references/run.luau",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/references/run.luau",
          "role": "reference"
        },
        {
          "path": "skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
          "target": ".agents/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/roblox-luau-pure-modules/cursor.zip"
    },
    "gemini-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "GEMINI.md",
          "target": "GEMINI.md",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/GEMINI.md",
          "role": "adapter"
        },
        {
          "path": "skills/luau-pure-tests/SKILL.md",
          "target": ".agents/skills/luau-pure-tests/SKILL.md",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/luau-pure-tests/scripts/pure_luau_guard.py",
          "target": ".agents/skills/luau-pure-tests/scripts/pure_luau_guard.py",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/scripts/pure_luau_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/luau-pure-tests/references/run.luau",
          "target": ".agents/skills/luau-pure-tests/references/run.luau",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/references/run.luau",
          "role": "reference"
        },
        {
          "path": "skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
          "target": ".agents/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/roblox-luau-pure-modules/gemini-cli.zip"
    },
    "antigravity-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/luau-pure-tests/SKILL.md",
          "target": ".agents/skills/luau-pure-tests/SKILL.md",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/luau-pure-tests/scripts/pure_luau_guard.py",
          "target": ".agents/skills/luau-pure-tests/scripts/pure_luau_guard.py",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/scripts/pure_luau_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/luau-pure-tests/references/run.luau",
          "target": ".agents/skills/luau-pure-tests/references/run.luau",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/references/run.luau",
          "role": "reference"
        },
        {
          "path": "skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
          "target": ".agents/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/roblox-luau-pure-modules/antigravity-cli.zip"
    },
    "copilot": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/luau-pure-tests/SKILL.md",
          "target": ".agents/skills/luau-pure-tests/SKILL.md",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/luau-pure-tests/scripts/pure_luau_guard.py",
          "target": ".agents/skills/luau-pure-tests/scripts/pure_luau_guard.py",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/scripts/pure_luau_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/luau-pure-tests/references/run.luau",
          "target": ".agents/skills/luau-pure-tests/references/run.luau",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/references/run.luau",
          "role": "reference"
        },
        {
          "path": "skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
          "target": ".agents/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
          "raw_url": "https://setupbay.com/raw/roblox-luau-pure-modules/1.0.0/skills/luau-pure-tests/references/studio-coupled-sample.luau.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/roblox-luau-pure-modules/copilot.zip"
    }
  },
  "source_zip_url": "https://setupbay.com/download/roblox-luau-pure-modules/source.zip",
  "claude_code_plugin": {
    "name": "roblox-luau-pure-modules",
    "marketplace_url": "https://setupbay.com/marketplace.json",
    "install": [
      "/plugin marketplace add https://setupbay.com/marketplace.json",
      "/plugin install roblox-luau-pure-modules@setupbay"
    ],
    "zip_url": "https://setupbay.com/plugins/roblox-luau-pure-modules.zip",
    "sha256": "e71fc66864dab0e876d4b1a038fd8839a943ead361cc30e51ee473ab6183b046",
    "carries": [
      "skills"
    ],
    "note": "Plugins do not carry project rules (AGENTS.md, CLAUDE.md); install those from install.claude-code."
  }
}