{
  "id": "polars-2-upgrade-guard",
  "type": "loadout",
  "title": "Polars 2 guard against Polars 1 code",
  "summary": "Stop agents writing melt, with_row_count, how=\"outer\" and other names Polars 2 removed, and flag the calls whose results changed silently in 2.0.",
  "description": "Polars 2.0 came out on 6 October 2026 and removed most of what was deprecated during 0.20 and 1.x: melt, with_row_count, group_by().count(), how=\"outer\", join_nulls, str.concat, collect(streaming=True), min_periods and more. Agents learnt Polars from years of 1.x answers, so they still write those names. The removed ones fail loudly. The worse problem is the changed defaults that don't fail: lazy queries now run on the streaming engine, which doesn't keep row order for joins and group_by, explode() drops empty lists, and horizontal concat stops padding.\n\nThis loadout pairs a short Cursor rule and AGENTS.md (a never/always table) with a skill that checks the work. A standard-library Python scanner reports every removed name as an error and every silently changed call as a warning, using the official upgrade guide. A person can accept a warning on a line with a guard: ok comment, but never an error. The verifier runs the scanner against a Polars 2 pipeline and a Polars 1 sample, then runs both with the pinned polars 2.1.0 to show which lines Polars itself refuses.\n",
  "highlights": [
    "Never/always table for Polars 2 in a .py-scoped Cursor rule",
    "Scanner flags 28 removed Polars 1 names as errors, read-only",
    "Warns on silent 2.0 changes: join row order, explode counts, horizontal concat",
    "A guard ok comment accepts a warning on one line, never an error",
    "Rename table built from the official Polars 2 upgrade guide"
  ],
  "version": "1.0.0",
  "license": "MIT",
  "author": {
    "name": "SetupBay",
    "url": "https://setupbay.com"
  },
  "credits": [
    {
      "title": "Polars docs: upgrading to version 2",
      "url": "https://docs.pola.rs/releases/upgrade/2/"
    },
    {
      "title": "polars 2.1.0 on PyPI (released 25 Oct 2026)",
      "url": "https://pypi.org/project/polars/2.1.0/"
    },
    {
      "title": "Polars docs: Python API reference",
      "url": "https://docs.pola.rs/api/python/stable/reference/"
    },
    {
      "title": "@le_woudar: weekly Python roundup covering the Polars 2.0 release",
      "url": "https://x.com/le_woudar/status/2109612420633440724"
    }
  ],
  "categories": [
    "data"
  ],
  "stacks": [
    "polars",
    "python",
    "uv"
  ],
  "files": [
    {
      "path": "AGENTS.md",
      "role": "instructions",
      "description": "Polars 2 version facts, rules 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/polars-2-upgrade-guard/1.0.0/AGENTS.md",
      "raw_latest_url": "https://setupbay.com/raw/polars-2-upgrade-guard/latest/AGENTS.md",
      "bytes": 1988,
      "lang": "markdown",
      "content": "# AGENTS.md: Polars 2 project (Polars 1 guard)\n\nThis project uses **Polars 2** (`polars>=2.1,<3`). Most Polars code you have seen is Polars 0.19–1.x. Polars 2\nremoved many of those names, and changed some results without raising, so treat remembered Polars as suspect.\n\n## Version facts\n- Check the pinned version first: `uv pip show polars` or the `polars` line in `pyproject.toml`/`uv.lock`.\n- Lazy queries default to the **streaming engine** in 2.0. It does not keep row order for joins, `group_by` or\n  `unpivot` unless you ask (`maintain_order=...`) or sort.\n- Removed APIs raise `AttributeRemovedError` or `ArgumentRemovedError` with a hint. Read the hint; don't guess.\n- If you are unsure an API exists in Polars 2, look it up in the API reference (https://docs.pola.rs/api/python/stable/)\n  or the upgrade guide (https://docs.pola.rs/releases/upgrade/2/). Never copy Polars 1 answers or pandas idioms.\n\n## Rules\n- Write Polars 2 only. The `polars-2-guard` skill has the full table; the common traps are:\n  `unpivot` (not `melt`), `with_row_index` (not `with_row_count`), `group_by(...).len()` (not `.count()`),\n  `how=\"full\"` (not `\"outer\"`), `nulls_equal=` (not `join_nulls=`), `str.join` (not `str.concat`),\n  `engine=\"streaming\"` (not `streaming=True`), `min_samples=` (not `min_periods=`), `str.to_date()` (not `.cast(pl.Date)` on strings).\n- Prefer expressions over `map_elements`. Never use pandas methods on a Polars frame.\n- If row order matters, say so in code: `.sort(...)` or `maintain_order=\"left\"` on joins.\n- Don't upgrade or downgrade Polars, and don't set `POLARS_ENGINE_AFFINITY`, without asking the human.\n\n## Before you say \"done\"\n1. Run the `polars-2-guard` scanner on the files you touched; it must report `0 error(s)`. Read every `warn`.\n2. Run the code or its tests against the pinned Polars (`uv run pytest`, `uv run python <script>.py`).\n3. If you changed a query whose row order or row count could differ in Polars 2, say which and how you checked it.\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/polars-2-upgrade-guard/1.0.0/CLAUDE.md",
      "raw_latest_url": "https://setupbay.com/raw/polars-2-upgrade-guard/latest/CLAUDE.md",
      "bytes": 379,
      "lang": "markdown",
      "content": "# CLAUDE.md\n\n@AGENTS.md\n\n## Claude Code notes\n\n- Skills from this loadout live in `.claude/skills/`: `polars-2-guard`. 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 \"Polars 2 guard against Polars 1 code\" v1.0.0 — https://setupbay.com/loadouts/polars-2-upgrade-guard/ -->\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/polars-2-upgrade-guard/1.0.0/GEMINI.md",
      "raw_latest_url": "https://setupbay.com/raw/polars-2-upgrade-guard/latest/GEMINI.md",
      "bytes": 380,
      "lang": "markdown",
      "content": "# GEMINI.md\n\n@./AGENTS.md\n\n## Gemini CLI notes\n\n- Skills from this loadout live in `.agents/skills/`: `polars-2-guard`. 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 \"Polars 2 guard against Polars 1 code\" v1.0.0 — https://setupbay.com/loadouts/polars-2-upgrade-guard/ -->\n"
    },
    {
      "path": ".cursor/rules/polars-2-only.mdc",
      "role": "rules",
      "description": "Never/always table for Python files.",
      "skill": null,
      "agent": null,
      "install_to": {
        "claude-code": null,
        "codex": null,
        "cursor": ".cursor/rules/polars-2-only.mdc",
        "gemini-cli": null,
        "antigravity-cli": null,
        "copilot": null
      },
      "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/.cursor/rules/polars-2-only.mdc",
      "raw_latest_url": "https://setupbay.com/raw/polars-2-upgrade-guard/latest/.cursor/rules/polars-2-only.mdc",
      "bytes": 1242,
      "lang": "markdown",
      "content": "---\ndescription: Polars 2 only; never Polars 1 names removed in 2.0\nglobs: \"**/*.py,**/*.ipynb\"\nalwaysApply: false\n---\n\nThis project pins Polars 2. Lazy queries run on the streaming engine by default.\n\n| Never (Polars 1 and older) | Always (Polars 2) |\n|---|---|\n| `df.melt(id_vars=, value_vars=)` | `df.unpivot(index=, on=)` |\n| `with_row_count()` | `with_row_index()` (column `index`) |\n| `group_by(k).count()`, `groupby(k)` | `group_by(k).len()` |\n| `join(how=\"outer\")`, `join_nulls=True` | `join(how=\"full\")`, `nulls_equal=True` |\n| `str.concat(\",\")` | `str.join(\",\")` |\n| `collect(streaming=True)` | `collect(engine=\"streaming\")` |\n| `rolling_mean(w, min_periods=1)` | `rolling_mean(w, min_samples=1)` |\n| `read_csv(dtypes=...)` | `read_csv(schema_overrides=...)` |\n| `pl.col(\"d\").cast(pl.Date)` on strings | `pl.col(\"d\").str.to_date()` |\n| `.apply(f)`, `.map_dict(d)` | native expressions, `.replace_strict(d)` |\n| `concat(how=\"horizontal\")` to pad | `concat(how=\"horizontal_extend\")` |\n\n- Row order: add `.sort(...)` or `maintain_order=\"left\"` when the result order matters.\n- `explode()` drops empty lists now; pass `empty_as_null=True` if you need a null row.\n- After editing, run the `polars-2-guard` skill's scanner and the tests.\n"
    },
    {
      "path": "skills/polars-2-guard/SKILL.md",
      "role": "skill",
      "description": "Scan for Polars 1 code and silent 2.0 changes, then run the code on the pinned Polars.",
      "skill": "polars-2-guard",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/polars-2-guard/SKILL.md",
        "codex": ".agents/skills/polars-2-guard/SKILL.md",
        "cursor": ".agents/skills/polars-2-guard/SKILL.md",
        "gemini-cli": ".agents/skills/polars-2-guard/SKILL.md",
        "antigravity-cli": ".agents/skills/polars-2-guard/SKILL.md",
        "copilot": ".agents/skills/polars-2-guard/SKILL.md"
      },
      "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/SKILL.md",
      "raw_latest_url": "https://setupbay.com/raw/polars-2-upgrade-guard/latest/skills/polars-2-guard/SKILL.md",
      "bytes": 2370,
      "lang": "markdown",
      "content": "---\nname: polars-2-guard\ndescription: Check Python for Polars 1.x APIs that Polars 2 removed, and for calls whose results changed silently in 2.0, before saying a Polars change is done. Use after writing or editing Polars code, when Polars raises AttributeRemovedError or ArgumentRemovedError, or when porting older Polars or pandas snippets.\n---\n\n# Polars 2 guard\n\nPolars 2.0 (6 October 2026) removed most names deprecated during 0.20 and 1.x, and changed defaults: lazy\nqueries now run on the streaming engine, which doesn't keep row order, `explode()` drops empty lists, and\nhorizontal `concat` refuses frames of different heights. Removed names fail loudly. The changed defaults don't.\n\n## 1. Confirm the version\n- `uv run python -c \"import polars; print(polars.__version__)\"` must print `2.x`. If it prints `1.x`, stop and ask\n  whether the project is meant to move to Polars 2 before changing anything.\n\n## 2. Scan\n```bash\npython3 .agents/skills/polars-2-guard/scripts/polars2_guard.py src tests\n```\n(Use the path the skill was installed to: `.claude/skills/...` for Claude Code, `.agents/skills/...` for the others.\nWith no arguments it scans the current folder, skipping virtualenvs and agent folders.)\n\n- Every `error` line is an API Polars 2 removed. Fix it with the replacement the line gives;\n  `references/rename-table.md` has the table with examples.\n- `warn` lines are calls that still run but may now give a different answer. Decide for each one:\n  - join or unpivot where order matters → add `maintain_order=\"left\"` or `.sort(...)`;\n  - `explode` where the row count matters → `empty_as_null=True`, or accept the new count;\n  - a `BytesIO` round trip → `buf.seek(0)` before reading.\n  If the current behaviour is what you want, add a `# guard: ok` comment to that line. That silences warnings,\n  never errors.\n- Run it again until it prints `0 error(s)`.\n\n## 3. Let Polars check it\n- Run the tests or the script with the pinned version: `uv run pytest -q` or `uv run python <script>.py`.\n- An `AttributeRemovedError` or `ArgumentRemovedError` names the replacement. Use it.\n- For pipelines whose output is compared elsewhere, compare row counts and a sorted sample before and after.\n\n## 4. Report\nSay how many Polars 1 calls you replaced, which silent changes you checked (order, counts, names) and how, and that\nthe scanner and tests were clean.\n"
    },
    {
      "path": "skills/polars-2-guard/scripts/polars2_guard.py",
      "role": "reference",
      "description": "Read-only scanner for removed Polars 1 names and changed defaults.",
      "skill": "polars-2-guard",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/polars-2-guard/scripts/polars2_guard.py",
        "codex": ".agents/skills/polars-2-guard/scripts/polars2_guard.py",
        "cursor": ".agents/skills/polars-2-guard/scripts/polars2_guard.py",
        "gemini-cli": ".agents/skills/polars-2-guard/scripts/polars2_guard.py",
        "antigravity-cli": ".agents/skills/polars-2-guard/scripts/polars2_guard.py",
        "copilot": ".agents/skills/polars-2-guard/scripts/polars2_guard.py"
      },
      "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/scripts/polars2_guard.py",
      "raw_latest_url": "https://setupbay.com/raw/polars-2-upgrade-guard/latest/skills/polars-2-guard/scripts/polars2_guard.py",
      "bytes": 9116,
      "lang": "markdown",
      "content": "#!/usr/bin/env python3\n\"\"\"Find Polars 1.x code that Polars 2 removed, and calls whose results changed silently in 2.0.\n\nUsage: python3 polars2_guard.py [PATH ...]        (default: the current folder)\n       python3 polars2_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    ('melt', 'error', '\\\\.melt\\\\(',\n     'melt() was removed -> unpivot(index=..., on=...) (id_vars -> index, value_vars -> on)', []),\n    ('with-row-count', 'error', '\\\\.with_row_count\\\\(',\n     \"with_row_count() was removed -> with_row_index() (column is now 'index', not 'row_nr')\", []),\n    ('groupby-count', 'error', 'group_by\\\\([^)]*\\\\)\\\\s*\\\\.count\\\\(\\\\s*\\\\)',\n     'group_by(...).count() was removed -> group_by(...).len()', []),\n    ('groupby-old-name', 'error', '\\\\.groupby\\\\(',\n     'groupby() -> group_by() (renamed in 0.19; also groupby_dynamic -> group_by_dynamic)', []),\n    ('join-outer', 'error', 'how\\\\s*=\\\\s*[\\\\\"\\']outer(_coalesce)?[\\\\\"\\']',\n     \"join(how='outer') -> how='full' (outer_coalesce -> how='full', coalesce=True)\", []),\n    ('join-nulls', 'error', '\\\\bjoin_nulls\\\\s*=',\n     'join_nulls= -> nulls_equal=', []),\n    ('str-concat', 'error', '\\\\.str\\\\.concat\\\\(',\n     'str.concat() was removed -> str.join() (default delimiter is now \"\", not \"-\")', []),\n    ('lazy-fetch', 'error', '\\\\.fetch\\\\(\\\\s*\\\\d*\\\\s*\\\\)',\n     'LazyFrame.fetch() was removed -> .head(n).collect()', []),\n    ('lazy-profile', 'error', '\\\\.profile\\\\(\\\\s*\\\\)',\n     'LazyFrame.profile() was removed (misleading on the streaming engine); time the collect() instead', []),\n    ('streaming-flag', 'error', '\\\\bstreaming\\\\s*=\\\\s*(True|False)',\n     \"collect(streaming=...) was removed -> collect(engine='streaming' | 'in-memory')\", []),\n    ('csv-dtypes', 'error', '(read|scan)_csv\\\\([^)]*\\\\bdtypes\\\\s*=',\n     'read_csv(dtypes=...) -> schema_overrides=... (a list must now cover every column)', []),\n    ('min-periods', 'error', '\\\\bmin_periods\\\\s*=',\n     'rolling_*(min_periods=...) -> min_samples=...', []),\n    ('with-context', 'error', '\\\\.with_context\\\\(',\n     \"LazyFrame.with_context() was removed -> pl.concat([...], how='horizontal')\", []),\n    ('threadpool-size', 'error', 'pl\\\\.threadpool_size\\\\(',\n     'pl.threadpool_size() -> pl.thread_pool_size()', []),\n    ('expr-where', 'error', 'pl\\\\.col\\\\([^)]*\\\\)\\\\.where\\\\(',\n     'Expr.where() was removed -> .filter()', []),\n    ('apply', 'error', '\\\\.apply\\\\(',\n     'apply() was removed in Polars 1.0 -> map_elements() (slow, last resort) or a native expression', []),\n    ('map-dict', 'error', '\\\\.map_dict\\\\(',\n     'map_dict() was removed -> replace() or replace_strict()', []),\n    ('replace-default', 'error', '\\\\.replace\\\\([^)]*\\\\b(default|return_dtype)\\\\s*=',\n     'replace(default=..., return_dtype=...) -> replace_strict(...)', []),\n    ('missing-columns', 'error', '\\\\ballow_missing_columns\\\\s*=',\n     \"allow_missing_columns= -> missing_columns='insert' or 'raise'\", []),\n    ('read-rechunk', 'error', '(read|scan)_\\\\w+\\\\([^)]*\\\\brechunk\\\\s*=',\n     'read_*/scan_*(rechunk=...) was removed -> call .rechunk() on the result', []),\n    ('memory-map', 'error', '\\\\bmemory_map\\\\s*=',\n     'read_ipc/scan_ipc(memory_map=...) was removed', []),\n    ('hash-seeds', 'error', '\\\\bseed_[123]\\\\s*=|\\\\.hash\\\\(\\\\s*\\\\w+\\\\s*,\\\\s*\\\\w+',\n     'hash(seed, seed_1, seed_2, seed_3) -> hash(seed) (one seed only)', []),\n    ('categorical-ordering', 'error', 'pl\\\\.Categorical\\\\(\\\\s*(ordering\\\\s*=|[\\\\\"\\'](physical|lexical)[\\\\\"\\'])',\n     'pl.Categorical(ordering=...) was removed; categoricals are always lexical now', []),\n    ('to-struct-args', 'error', '\\\\.to_struct\\\\(\\\\s*\\\\)|\\\\b(upper_bound|n_field_strategy)\\\\s*=',\n     \"list.to_struct() needs the field names: list.to_struct(['a', 'b', 'c'])\", []),\n    ('concat-strict-false', 'error', 'how\\\\s*=\\\\s*[\\\\\"\\']horizontal[\\\\\"\\'][^)]*strict\\\\s*=\\\\s*False',\n     \"concat(how='horizontal', strict=False) now raises -> how='horizontal_extend' to pad\", []),\n    ('flatten', 'error', '\\\\.flatten\\\\(\\\\s*\\\\)',\n     'Expr.flatten() was removed -> .list.explode(keep_nulls=False, empty_as_null=False)', []),\n    ('top-k-descending', 'error', '(top|bottom)_k\\\\([^)]*\\\\bdescending\\\\s*=',\n     'top_k/bottom_k(descending=...) -> reverse=...', []),\n    ('pivot-columns', 'error', '\\\\.pivot\\\\([^)]*\\\\bcolumns\\\\s*=',\n     'pivot(columns=...) -> pivot(on=...)', []),\n    ('string-to-temporal-cast', 'warn', '\\\\.cast\\\\(\\\\s*pl\\\\.(Date|Datetime|Time)\\\\b',\n     'casting a String column to Date/Datetime/Time now raises -> str.to_date() / str.to_datetime() / str.to_time()', []),\n    ('concat-horizontal', 'warn', 'how\\\\s*=\\\\s*[\\\\\"\\']horizontal[\\\\\"\\'](?![^)]*_extend)',\n     \"horizontal concat now raises on different heights; use how='horizontal_extend' only if padding with nulls is intended\", []),\n    ('explode-empty', 'warn', '\\\\.explode\\\\((?![^)]*empty_as_null)',\n     'explode(): empty lists now give zero rows, not one null row; pass empty_as_null=True if the old count matters', []),\n    ('join-order', 'warn', '\\\\.join\\\\((?![^)]*maintain_order)(?=[^)]*\\\\bon\\\\s*=)',\n     \"lazy joins run on the streaming engine and don't keep row order; pass maintain_order='left' or sort if order matters\", []),\n    ('datetime-name', 'warn', 'pl\\\\.(datetime|repeat)\\\\((?!.*\\\\.alias\\\\()',\n     \"pl.datetime()/pl.repeat() are now named after their first argument; add .alias('...')\", []),\n    ('bytesio-seek', 'warn', 'BytesIO\\\\(\\\\s*\\\\)',\n     'reading a BytesIO no longer rewinds it; call buf.seek(0) between write and read', []),\n    ('cut-qcut', 'warn', '\\\\.q?cut\\\\(',\n     'cut()/qcut() are deprecated -> bin_intervals() / bin_quantiles() (labels required, pass right_closed=True)', []),\n    ('column-1', 'warn', '[\\\\\"\\']column_1[\\\\\"\\']',\n     'headerless CSV columns now start at column_0, not column_1', []),\n]\nFILES = ['*.py', '*.ipynb']\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\"polars2_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/polars-2-guard/references/rename-table.md",
      "role": "reference",
      "description": "Polars 1 to 2 table of removed names and silent changes.",
      "skill": "polars-2-guard",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/polars-2-guard/references/rename-table.md",
        "codex": ".agents/skills/polars-2-guard/references/rename-table.md",
        "cursor": ".agents/skills/polars-2-guard/references/rename-table.md",
        "gemini-cli": ".agents/skills/polars-2-guard/references/rename-table.md",
        "antigravity-cli": ".agents/skills/polars-2-guard/references/rename-table.md",
        "copilot": ".agents/skills/polars-2-guard/references/rename-table.md"
      },
      "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/references/rename-table.md",
      "raw_latest_url": "https://setupbay.com/raw/polars-2-upgrade-guard/latest/skills/polars-2-guard/references/rename-table.md",
      "bytes": 3373,
      "lang": "markdown",
      "content": "# Polars 1 → Polars 2 table\n\nFrom the Polars 2.0 upgrade guide (https://docs.pola.rs/releases/upgrade/2/). Checked against polars 2.1.0.\n\n## Removed: raises in Polars 2\n\n| Polars 1.x (or older) | Polars 2 |\n|---|---|\n| `df.melt(id_vars=\"a\", value_vars=[\"b\"])` | `df.unpivot(index=\"a\", on=[\"b\"])` |\n| `df.with_row_count()` (column `row_nr`) | `df.with_row_index()` (column `index`) |\n| `df.group_by(\"k\").count()` | `df.group_by(\"k\").len()` |\n| `df.groupby(\"k\")` | `df.group_by(\"k\")` |\n| `join(how=\"outer\")` / `\"outer_coalesce\"` | `join(how=\"full\")` / `how=\"full\", coalesce=True` |\n| `join(join_nulls=True)` | `join(nulls_equal=True)` |\n| `pl.col(\"s\").str.concat(\"-\")` | `pl.col(\"s\").str.join(\"-\")` (default delimiter is now `\"\"`) |\n| `lf.fetch(5)` | `lf.head(5).collect()` |\n| `lf.profile()` | none; time `collect()` instead |\n| `collect(streaming=True)` | `collect(engine=\"streaming\")` |\n| `read_csv(dtypes={...})` | `read_csv(schema_overrides={...})` |\n| `rolling_mean(3, min_periods=1)` | `rolling_mean(3, min_samples=1)` |\n| `lf.with_context(other)` | `pl.concat([lf, other], how=\"horizontal\")` |\n| `pl.threadpool_size()` | `pl.thread_pool_size()` |\n| `pl.col(\"a\").where(cond)` | `pl.col(\"a\").filter(cond)` |\n| `.apply(f)` (removed in 1.0) | a native expression; `map_elements(f)` as a last resort |\n| `.map_dict(d)` | `.replace(d)` or `.replace_strict(d)` |\n| `.replace(d, default=x)` | `.replace_strict(d, default=x)` |\n| `read_parquet(allow_missing_columns=True)` | `read_parquet(missing_columns=\"insert\")` |\n| `read_*(rechunk=True)` | `read_*(...).rechunk()` |\n| `read_ipc(memory_map=True)` | `read_ipc(...)` |\n| `hash(1, 2, 3, 4)` | `hash(1)` |\n| `pl.Categorical(ordering=\"lexical\")` | `pl.Categorical()` (always lexical) |\n| `list.to_struct(upper_bound=3)` | `list.to_struct([\"a\", \"b\", \"c\"])` |\n| `concat(how=\"horizontal\", strict=False)` | `concat(how=\"horizontal_extend\")` |\n| `Expr.flatten()` | `list.explode(keep_nulls=False, empty_as_null=False)` |\n| `top_k(k, by=..., descending=True)` | `top_k(k, by=..., reverse=True)` |\n| `pivot(columns=\"c\")` | `pivot(on=\"c\")` |\n| `pl.Series([\"2026-10-01\"]).cast(pl.Date)` | `pl.Series([\"2026-10-01\"]).str.to_date()` |\n\n## Changed silently: same code, different answer\n\n| Change in 2.0 | What to do |\n|---|---|\n| Lazy `collect()` runs on the streaming engine; joins, `group_by`, `unpivot` don't keep row order | `.sort(...)`, or `join(..., maintain_order=\"left\")` |\n| `explode()` turns an empty list into zero rows (was one null row) | `explode(..., empty_as_null=True)` for the old count |\n| `concat(how=\"horizontal\")` raises on different heights (used to pad) | pad on purpose with `how=\"horizontal_extend\"` |\n| `pl.datetime(...)` / `pl.repeat(...)` named after their first argument | `.alias(\"datetime\")` |\n| Reading a `BytesIO` starts at its current position | `buf.seek(0)` after writing |\n| Signed int + `UInt64` gives `Int128`, not `Float64` | cast if a float was expected |\n| Headerless CSV columns are `column_0, column_1, ...` | update names that started at `column_1` |\n| Map columns from Arrow/Parquet load as `pl.Map` | `.map.entries()` for the old list-of-structs shape |\n| `cut()` / `qcut()` deprecated | `bin_intervals(...)` / `bin_quantiles(...)`, `right_closed=True`, labels required |\n\nTo restore the in-memory engine for one query: `lf.collect(engine=\"in-memory\")`. Ask before doing it project-wide.\n"
    },
    {
      "path": "skills/polars-2-guard/references/polars1-sample.py.txt",
      "role": "reference",
      "description": "A Polars 1 report script for checking the guard.",
      "skill": "polars-2-guard",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/polars-2-guard/references/polars1-sample.py.txt",
        "codex": ".agents/skills/polars-2-guard/references/polars1-sample.py.txt",
        "cursor": ".agents/skills/polars-2-guard/references/polars1-sample.py.txt",
        "gemini-cli": ".agents/skills/polars-2-guard/references/polars1-sample.py.txt",
        "antigravity-cli": ".agents/skills/polars-2-guard/references/polars1-sample.py.txt",
        "copilot": ".agents/skills/polars-2-guard/references/polars1-sample.py.txt"
      },
      "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/references/polars1-sample.py.txt",
      "raw_latest_url": "https://setupbay.com/raw/polars-2-upgrade-guard/latest/skills/polars-2-guard/references/polars1-sample.py.txt",
      "bytes": 1198,
      "lang": "markdown",
      "content": "# A Polars 1.x report script, used to check the guard. Every marked line fails or changes in Polars 2.\nimport io\n\nimport polars as pl\n\nsales = pl.read_csv(\"sales.csv\", dtypes={\"store\": pl.Utf8})                  # csv-dtypes\nsales = sales.with_row_count()                                               # with-row-count\nlong = sales.melt(id_vars=\"store\", value_vars=[\"q1\", \"q2\"])                  # melt\nper_store = sales.group_by(\"store\").count()                                  # groupby-count\njoined = sales.join(per_store, on=\"store\", how=\"outer\")                      # join-outer\nlabels = sales.select(pl.col(\"store\").str.concat(\", \"))                      # str-concat\nsmooth = sales.select(pl.col(\"q1\").rolling_mean(3, min_periods=1))           # min-periods\nbig = sales.lazy().filter(pl.col(\"q1\") > 10).collect(streaming=True)         # streaming-flag\ndays = sales.with_columns(pl.col(\"day\").cast(pl.Date))                       # string-to-temporal-cast\nnames = sales.with_columns(pl.col(\"store\").map_dict({\"A\": \"Leeds\"}))         # map-dict\nbuf = io.BytesIO()                                                           # bytesio-seek\nsales.write_parquet(buf)\nagain = pl.read_parquet(buf)\n"
    }
  ],
  "requires": {
    "tools": [
      "python3",
      "uv"
    ],
    "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": true,
    "scripts": [
      "polars2_guard.py"
    ],
    "notes": "The scanner only reads .py and .ipynb files and prints findings; it never edits files and makes no network calls. Running the project with uv may download the pinned packages."
  },
  "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/polars-2-upgrade-guard/",
  "api_url": "https://setupbay.com/api/loadouts/polars-2-upgrade-guard.json",
  "verification": {
    "level": "verified-install",
    "status": "verified",
    "label": "Verified install",
    "verified": true,
    "checked_at": "2026-10-29T12:14:43Z",
    "commit": "e07c98dd5f9c1f64dd9ecf995b8af3311f15b8da",
    "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 yes (listing says yes)"
        ]
      },
      {
        "id": "references",
        "name": "References",
        "status": "pass",
        "summary": "3 references between files resolve for every agent",
        "details": []
      },
      {
        "id": "size",
        "name": "Size limits",
        "status": "pass",
        "summary": "8 files, 19.6 KB total; largest skills/polars-2-guard/scripts/polars2_guard.py 8.9 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": "polars2_guard.py passes a Polars 2 report (src, tests) and flags every Polars 1 line in the skill's sample: 9 removed names as errors, 3 silent changes as warnings. The pinned polars 2.1.0 then runs the report and its tests (row order kept with maintain_order, empty lists kept with empty_as_null) an",
        "details": [
          "Applied 12 loadout files at every agent's paths",
          "✓ scanner: the Polars 2 report is clean: bash -c python3 .agents/skills/polars-2-guard/scripts/polars2_guard.py src tests; echo \"exit=$?\"",
          "✓ scanner: the Polars 1 sample is flagged line by line: bash -c mkdir -p /tmp/polars1-sample && cp .claude/skills/polars-2-guard/references/polars1-sample.py.txt /tmp/polars1-sample/report1.py && python3 .claude/skills/polars-2-guard/scripts/polars2_guard.py /tmp/polars1-sample; echo \"exit=$?\"",
          "✓ polars 2.1.0 runs the report: uv run --python 3.12 python src/report.py",
          "✓ pytest on polars 2.1.0: uv run --python 3.12 pytest -q -p no:cacheprovider",
          "✓ polars 2.1.0 refuses the sample's removed calls: uv run --python 3.12 python legacy_probe.py",
          "Covers: The scanner's removed-name rules against real Polars 1 code, and its silent-change warnings (join order, string-to-date casts, BytesIO rewinds)",
          "Covers: No false positives on a Polars 2 report that uses unpivot, with_row_index, group_by().len(), how='full' with maintain_order, str.join and empty_as_null",
          "Covers: Polars 2.1.0 itself as the second check: the report and its tests run, and every removed call in the sample is refused",
          "Covers: Not covered: the row order the streaming engine happens to produce (it is not guaranteed, so the fixture sorts or keeps order explicitly)"
        ]
      }
    ]
  },
  "install": {
    "claude-code": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "CLAUDE.md",
          "target": "CLAUDE.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/CLAUDE.md",
          "role": "adapter"
        },
        {
          "path": "skills/polars-2-guard/SKILL.md",
          "target": ".claude/skills/polars-2-guard/SKILL.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/polars-2-guard/scripts/polars2_guard.py",
          "target": ".claude/skills/polars-2-guard/scripts/polars2_guard.py",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/scripts/polars2_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/polars-2-guard/references/rename-table.md",
          "target": ".claude/skills/polars-2-guard/references/rename-table.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/references/rename-table.md",
          "role": "reference"
        },
        {
          "path": "skills/polars-2-guard/references/polars1-sample.py.txt",
          "target": ".claude/skills/polars-2-guard/references/polars1-sample.py.txt",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/references/polars1-sample.py.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/polars-2-upgrade-guard/claude-code.zip"
    },
    "codex": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/polars-2-guard/SKILL.md",
          "target": ".agents/skills/polars-2-guard/SKILL.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/polars-2-guard/scripts/polars2_guard.py",
          "target": ".agents/skills/polars-2-guard/scripts/polars2_guard.py",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/scripts/polars2_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/polars-2-guard/references/rename-table.md",
          "target": ".agents/skills/polars-2-guard/references/rename-table.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/references/rename-table.md",
          "role": "reference"
        },
        {
          "path": "skills/polars-2-guard/references/polars1-sample.py.txt",
          "target": ".agents/skills/polars-2-guard/references/polars1-sample.py.txt",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/references/polars1-sample.py.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/polars-2-upgrade-guard/codex.zip"
    },
    "cursor": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": ".cursor/rules/polars-2-only.mdc",
          "target": ".cursor/rules/polars-2-only.mdc",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/.cursor/rules/polars-2-only.mdc",
          "role": "rules"
        },
        {
          "path": "skills/polars-2-guard/SKILL.md",
          "target": ".agents/skills/polars-2-guard/SKILL.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/polars-2-guard/scripts/polars2_guard.py",
          "target": ".agents/skills/polars-2-guard/scripts/polars2_guard.py",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/scripts/polars2_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/polars-2-guard/references/rename-table.md",
          "target": ".agents/skills/polars-2-guard/references/rename-table.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/references/rename-table.md",
          "role": "reference"
        },
        {
          "path": "skills/polars-2-guard/references/polars1-sample.py.txt",
          "target": ".agents/skills/polars-2-guard/references/polars1-sample.py.txt",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/references/polars1-sample.py.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/polars-2-upgrade-guard/cursor.zip"
    },
    "gemini-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "GEMINI.md",
          "target": "GEMINI.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/GEMINI.md",
          "role": "adapter"
        },
        {
          "path": "skills/polars-2-guard/SKILL.md",
          "target": ".agents/skills/polars-2-guard/SKILL.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/polars-2-guard/scripts/polars2_guard.py",
          "target": ".agents/skills/polars-2-guard/scripts/polars2_guard.py",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/scripts/polars2_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/polars-2-guard/references/rename-table.md",
          "target": ".agents/skills/polars-2-guard/references/rename-table.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/references/rename-table.md",
          "role": "reference"
        },
        {
          "path": "skills/polars-2-guard/references/polars1-sample.py.txt",
          "target": ".agents/skills/polars-2-guard/references/polars1-sample.py.txt",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/references/polars1-sample.py.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/polars-2-upgrade-guard/gemini-cli.zip"
    },
    "antigravity-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/polars-2-guard/SKILL.md",
          "target": ".agents/skills/polars-2-guard/SKILL.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/polars-2-guard/scripts/polars2_guard.py",
          "target": ".agents/skills/polars-2-guard/scripts/polars2_guard.py",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/scripts/polars2_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/polars-2-guard/references/rename-table.md",
          "target": ".agents/skills/polars-2-guard/references/rename-table.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/references/rename-table.md",
          "role": "reference"
        },
        {
          "path": "skills/polars-2-guard/references/polars1-sample.py.txt",
          "target": ".agents/skills/polars-2-guard/references/polars1-sample.py.txt",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/references/polars1-sample.py.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/polars-2-upgrade-guard/antigravity-cli.zip"
    },
    "copilot": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/polars-2-guard/SKILL.md",
          "target": ".agents/skills/polars-2-guard/SKILL.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/polars-2-guard/scripts/polars2_guard.py",
          "target": ".agents/skills/polars-2-guard/scripts/polars2_guard.py",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/scripts/polars2_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/polars-2-guard/references/rename-table.md",
          "target": ".agents/skills/polars-2-guard/references/rename-table.md",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/references/rename-table.md",
          "role": "reference"
        },
        {
          "path": "skills/polars-2-guard/references/polars1-sample.py.txt",
          "target": ".agents/skills/polars-2-guard/references/polars1-sample.py.txt",
          "raw_url": "https://setupbay.com/raw/polars-2-upgrade-guard/1.0.0/skills/polars-2-guard/references/polars1-sample.py.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/polars-2-upgrade-guard/copilot.zip"
    }
  },
  "source_zip_url": "https://setupbay.com/download/polars-2-upgrade-guard/source.zip",
  "claude_code_plugin": {
    "name": "polars-2-upgrade-guard",
    "marketplace_url": "https://setupbay.com/marketplace.json",
    "install": [
      "/plugin marketplace add https://setupbay.com/marketplace.json",
      "/plugin install polars-2-upgrade-guard@setupbay"
    ],
    "zip_url": "https://setupbay.com/plugins/polars-2-upgrade-guard.zip",
    "sha256": "8d0c925a26b1e5cb8c9404c93adf7044c0008b2bf95d967c6031a05ca972b994",
    "carries": [
      "skills"
    ],
    "note": "Plugins do not carry project rules (AGENTS.md, CLAUDE.md); install those from install.claude-code."
  }
}