{
  "id": "fastapi-pydantic-v2-guard",
  "type": "loadout",
  "title": "FastAPI guard against Pydantic v1 code",
  "summary": "Stop agents writing class Config, orm_mode, @validator, .dict() and pydantic.v1 into FastAPI apps on Pydantic v2, and catch Optional fields that became required.",
  "description": "Agents still write FastAPI code from the Pydantic v1 years: class Config with orm_mode, @validator, .dict(), parse_obj, Field(regex=). On Pydantic 2.14 most of it still runs with a PydanticDeprecatedSince20 warning, which nobody reads, so it piles up until v3 removes it. Some of it fails outright. Field(regex=) and from pydantic import BaseSettings break at import, and current FastAPI refuses pydantic.v1 models with PydanticV1NotSupportedError. One change fails quietly. Optional[str] with no default is a required field in v2, so clients that leave it out start getting 422s.\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 26 v1 constructs and deprecated FastAPI patterns as errors and Optional-without-default fields as warnings. Then the tests run with PydanticDeprecatedSince20 raised as an error, which catches v1 calls the scanner can't see. The verifier runs both against a FastAPI app with pinned fastapi 0.162.0, pydantic 2.14.1 and pydantic-settings 2.16.2, and against a v1 sample.\n",
  "highlights": [
    "Never/always table for Pydantic v2 in a .py-scoped Cursor rule",
    "Scanner flags 26 Pydantic v1 constructs and on_event, read-only",
    "Warns when Optional[X] has no default, which v2 makes required",
    "Tests run with PydanticDeprecatedSince20 as an error",
    "Migration table checked against pinned FastAPI and Pydantic versions"
  ],
  "version": "1.0.0",
  "license": "MIT",
  "author": {
    "name": "SetupBay",
    "url": "https://setupbay.com"
  },
  "credits": [
    {
      "title": "Pydantic docs: migration guide",
      "url": "https://docs.pydantic.dev/latest/migration/"
    },
    {
      "title": "FastAPI docs: lifespan events",
      "url": "https://fastapi.tiangolo.com/advanced/events/"
    },
    {
      "title": "pydantic-settings docs: settings management",
      "url": "https://docs.pydantic.dev/latest/concepts/pydantic_settings/"
    },
    {
      "title": "@Romilpatel1988: Upshift, an agent that upgrades code from Pydantic v1 to v2",
      "url": "https://x.com/Romilpatel1988/status/2109646729025105978"
    },
    {
      "title": "@RDJArc: scoped .cursor/rules files for FastAPI",
      "url": "https://x.com/RDJArc/status/2111064912952979818"
    }
  ],
  "categories": [
    "web-app"
  ],
  "stacks": [
    "fastapi",
    "pydantic",
    "python",
    "pytest"
  ],
  "files": [
    {
      "path": "AGENTS.md",
      "role": "instructions",
      "description": "Pydantic v2 and FastAPI 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/fastapi-pydantic-v2-guard/1.0.0/AGENTS.md",
      "raw_latest_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/latest/AGENTS.md",
      "bytes": 2235,
      "lang": "markdown",
      "content": "# AGENTS.md: FastAPI on Pydantic v2 (Pydantic v1 guard)\n\nThis is a **FastAPI** service on **Pydantic v2** (`pydantic>=2.14,<3`, `fastapi>=0.162`). Much of the FastAPI code\nyou remember was written for Pydantic v1. Some of it fails at import, some only warns, and one pattern changes\nwhether a field is required.\n\n## Version facts\n- Check the pins first: `uv pip show fastapi pydantic pydantic-settings`.\n- Current FastAPI refuses `pydantic.v1` models outright (`PydanticV1NotSupportedError`). There is no v1 fallback.\n- v1 methods such as `.dict()`, `@validator` and `class Config` still run on v2 but warn\n  `PydanticDeprecatedSince20`. They are removed in Pydantic v3. Treat them as errors here.\n- `Optional[int]` with no default is a **required** field in v2. It allows `None`, it doesn't make the field optional.\n- Settings live in `pydantic-settings`: `from pydantic_settings import BaseSettings, SettingsConfigDict`.\n- If you're not sure an API exists in v2, check the migration guide (https://docs.pydantic.dev/latest/migration/)\n  instead of guessing.\n\n## Rules\n- Write Pydantic v2 only. The `pydantic-v2-guard` skill has the full table. The common ones are:\n  `model_config = ConfigDict(from_attributes=True)` (not `class Config: orm_mode = True`),\n  `@field_validator` / `@model_validator` (not `@validator` / `@root_validator`), `model_dump()` /\n  `model_dump_json()` / `model_validate()` (not `dict()` / `json()` / `parse_obj()`), `Field(pattern=)` (not `regex=`),\n  `min_length` (not `min_items`), `Literal[...]` (not `const=`).\n- Give every optional field a default: `note: str | None = None`.\n- Separate request and response models. Set `response_model` (or a return annotation) so output is filtered.\n- Startup and shutdown go in a `lifespan` function, not `@app.on_event`.\n- Don't add `pydantic.v1` imports or downgrade Pydantic to make old code pass.\n\n## Before you say \"done\"\n1. Run the `pydantic-v2-guard` scanner on the files you touched. It must report `0 error(s)`. Read every `warn`.\n2. Run the tests with v1 deprecations as errors:\n   `uv run pytest -q -W error::pydantic.warnings.PydanticDeprecatedSince20`.\n3. Say which models changed shape (required/optional fields, aliases) so API clients can be checked.\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/fastapi-pydantic-v2-guard/1.0.0/CLAUDE.md",
      "raw_latest_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/latest/CLAUDE.md",
      "bytes": 387,
      "lang": "markdown",
      "content": "# CLAUDE.md\n\n@AGENTS.md\n\n## Claude Code notes\n\n- Skills from this loadout live in `.claude/skills/`: `pydantic-v2-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 \"FastAPI guard against Pydantic v1 code\" v1.0.0 — https://setupbay.com/loadouts/fastapi-pydantic-v2-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/fastapi-pydantic-v2-guard/1.0.0/GEMINI.md",
      "raw_latest_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/latest/GEMINI.md",
      "bytes": 388,
      "lang": "markdown",
      "content": "# GEMINI.md\n\n@./AGENTS.md\n\n## Gemini CLI notes\n\n- Skills from this loadout live in `.agents/skills/`: `pydantic-v2-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 \"FastAPI guard against Pydantic v1 code\" v1.0.0 — https://setupbay.com/loadouts/fastapi-pydantic-v2-guard/ -->\n"
    },
    {
      "path": ".cursor/rules/pydantic-v2-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/pydantic-v2-only.mdc",
        "gemini-cli": null,
        "antigravity-cli": null,
        "copilot": null
      },
      "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/.cursor/rules/pydantic-v2-only.mdc",
      "raw_latest_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/latest/.cursor/rules/pydantic-v2-only.mdc",
      "bytes": 1190,
      "lang": "markdown",
      "content": "---\ndescription: FastAPI with Pydantic v2 only; no Pydantic v1 models, config or methods\nglobs: \"**/*.py\"\nalwaysApply: false\n---\n\nThis FastAPI service uses Pydantic v2. FastAPI refuses `pydantic.v1` models.\n\n| Never (Pydantic v1) | Always (Pydantic v2) |\n|---|---|\n| `class Config: orm_mode = True` | `model_config = ConfigDict(from_attributes=True)` |\n| `@validator(\"x\")` | `@field_validator(\"x\")` + `@classmethod` |\n| `@root_validator` | `@model_validator(mode=\"after\")` |\n| `m.dict()`, `m.json()` | `m.model_dump()`, `m.model_dump_json()` |\n| `M.parse_obj(d)`, `M.from_orm(row)` | `M.model_validate(d)`, `M.model_validate(row)` |\n| `m.copy(update=...)` | `m.model_copy(update=...)` |\n| `Field(regex=...)`, `Field(min_items=1)` | `Field(pattern=...)`, `Field(min_length=1)` |\n| `Field(const=True)` | `Literal[\"value\"]` |\n| `from pydantic import BaseSettings` | `from pydantic_settings import BaseSettings` |\n| `note: Optional[str]` meaning \"may be left out\" | `note: str \\| None = None` |\n| `@app.on_event(\"startup\")` | `FastAPI(lifespan=lifespan)` |\n\n- After editing, run the `pydantic-v2-guard` skill's scanner, then pytest with `-W error::pydantic.warnings.PydanticDeprecatedSince20`.\n"
    },
    {
      "path": "skills/pydantic-v2-guard/SKILL.md",
      "role": "skill",
      "description": "Scan for Pydantic v1 code, then run the tests with v1 deprecations as errors.",
      "skill": "pydantic-v2-guard",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/pydantic-v2-guard/SKILL.md",
        "codex": ".agents/skills/pydantic-v2-guard/SKILL.md",
        "cursor": ".agents/skills/pydantic-v2-guard/SKILL.md",
        "gemini-cli": ".agents/skills/pydantic-v2-guard/SKILL.md",
        "antigravity-cli": ".agents/skills/pydantic-v2-guard/SKILL.md",
        "copilot": ".agents/skills/pydantic-v2-guard/SKILL.md"
      },
      "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/SKILL.md",
      "raw_latest_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/latest/skills/pydantic-v2-guard/SKILL.md",
      "bytes": 2972,
      "lang": "markdown",
      "content": "---\nname: pydantic-v2-guard\ndescription: Check a FastAPI project for Pydantic v1 models, config, validators and methods, and for Optional fields that became required, before saying a model or endpoint change is done. Use after writing or editing Pydantic models or FastAPI routes, when FastAPI raises PydanticV1NotSupportedError, Pydantic warns PydanticDeprecatedSince20, or when porting older FastAPI snippets.\n---\n\n# Pydantic v2 guard for FastAPI\n\nAgents still write Pydantic v1 into FastAPI services: `class Config`, `orm_mode`, `@validator`, `.dict()`,\n`parse_obj`. On Pydantic 2.14 most of that still runs with a `PydanticDeprecatedSince20` warning, so it looks fine\nuntil v3 removes it. Some of it doesn't run at all: `Field(regex=)` and `from pydantic import BaseSettings` fail at\nimport, and FastAPI 0.162 refuses `pydantic.v1` models. And `Optional[str]` with no default became a required field.\n\n## 1. Confirm the versions\n- `uv run python -c \"import pydantic, fastapi; print(pydantic.VERSION, fastapi.__version__)\"` should print `2.x` and\n  a current FastAPI. If Pydantic is 1.x, stop and ask before migrating anything.\n\n## 2. Scan\n```bash\npython3 .agents/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py app tests\n```\n(Use the path the skill was installed to: `.claude/skills/...` for Claude Code, `.agents/skills/...` for the others.)\n\n- Every `error` line is Pydantic v1 (or a deprecated FastAPI pattern). Fix it with the replacement the line gives.\n  `references/migration-table.md` has the table with before/after code.\n- `optional-no-default` warnings: decide whether the field may be left out. If yes, add `= None`. If clients must\n  send it (possibly as `null`), keep it required and add `# guard: ok` to the line. That silences warnings, never errors.\n- Run it again until it prints `0 error(s)`.\n\n## 3. Let Pydantic check it\n```bash\nuv run pytest -q -W error::pydantic.warnings.PydanticDeprecatedSince20\n```\n- That turns every remaining v1 call into a failing test, including ones inside helpers the scanner can't see.\n- For each changed model, add or keep one test that posts a payload missing each optional field, and one that checks\n  the response doesn't include fields the response model leaves out.\n\n## 4. Fixes that need care\n- **Validators**: `@field_validator` needs `@classmethod`. `pre=True` becomes `mode=\"before\"`. A `values` argument\n  becomes `info: ValidationInfo` with `info.data`.\n- **`from_orm`**: needs `from_attributes=True` in `model_config`. Then `Model.model_validate(orm_obj)`.\n- **Aliases**: `allow_population_by_field_name` becomes `populate_by_name=True`. Check `by_alias=True` where JSON\n  uses the alias.\n- **Settings**: `class Config: env_file = \".env\"` becomes `model_config = SettingsConfigDict(env_file=\".env\")`.\n\n## 5. Report\nSay how many v1 constructs you replaced, which fields changed between required and optional, and that the scanner and\nthe tests (with PydanticDeprecatedSince20 as an error) were clean.\n"
    },
    {
      "path": "skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
      "role": "reference",
      "description": "Read-only scanner for Pydantic v1 constructs and Optional fields without defaults.",
      "skill": "pydantic-v2-guard",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
        "codex": ".agents/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
        "cursor": ".agents/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
        "gemini-cli": ".agents/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
        "antigravity-cli": ".agents/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
        "copilot": ".agents/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py"
      },
      "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
      "raw_latest_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/latest/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
      "bytes": 7847,
      "lang": "markdown",
      "content": "#!/usr/bin/env python3\n\"\"\"Find Pydantic v1 code (and FastAPI patterns built on it) in a FastAPI project on Pydantic v2.\n\nUsage: python3 pydantic_v2_guard.py [PATH ...]        (default: the current folder)\n       python3 pydantic_v2_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    ('pydantic-v1-import', 'error', '\\\\bfrom\\\\s+pydantic\\\\.v1\\\\b|\\\\bimport\\\\s+pydantic\\\\.v1\\\\b|pydantic\\\\.v1\\\\.',\n     'pydantic.v1 models are refused by FastAPI (PydanticV1NotSupportedError) -> plain pydantic v2 models', []),\n    ('basesettings-import', 'error', 'from\\\\s+pydantic\\\\s+import\\\\s+[^#]*\\\\bBaseSettings\\\\b',\n     'BaseSettings moved to pydantic-settings -> from pydantic_settings import BaseSettings, SettingsConfigDict', []),\n    ('class-config', 'error', '^\\\\s+class\\\\s+Config\\\\s*(\\\\(|:)',\n     'inner class Config -> model_config = ConfigDict(...) (settings: SettingsConfigDict)', []),\n    ('orm-mode', 'error', '\\\\borm_mode\\\\b',\n     'orm_mode -> from_attributes=True in ConfigDict', []),\n    ('allow-population', 'error', '\\\\ballow_population_by_field_name\\\\b',\n     'allow_population_by_field_name -> populate_by_name=True (or validate_by_name)', []),\n    ('schema-extra', 'error', '\\\\bschema_extra\\\\b',\n     'schema_extra -> json_schema_extra', []),\n    ('anystr-config', 'error', '\\\\b(anystr_strip_whitespace|anystr_lower|max_anystr_length|min_anystr_length)\\\\b',\n     'anystr_* config -> str_strip_whitespace, str_to_lower, str_max_length, str_min_length', []),\n    ('validator', 'error', '@validator\\\\(|\\\\bimport\\\\s+[^#]*\\\\bvalidator\\\\b',\n     \"@validator -> @field_validator('x') with @classmethod (mode='before' for pre=True)\", []),\n    ('root-validator', 'error', '@root_validator\\\\b|\\\\broot_validator\\\\b',\n     \"@root_validator -> @model_validator(mode='before' | 'after')\", []),\n    ('each-item', 'error', '\\\\beach_item\\\\s*=',\n     'validator(each_item=True) -> annotate the item type: list[Annotated[int, AfterValidator(f)]]', []),\n    ('dict-method', 'error', '\\\\.dict\\\\(\\\\s*(\\\\)|[a-z_]+\\\\s*=)',\n     '.dict() -> .model_dump() (exclude_unset=, by_alias= still work)', []),\n    ('json-method', 'error', '\\\\.json\\\\(\\\\s*(exclude|include|by_alias|indent|models_as_dict)\\\\w*\\\\s*=',\n     'model.json(...) -> .model_dump_json(...) (a bare .json() is left alone: HTTP responses have one too)', []),\n    ('parse-obj', 'error', '\\\\.(parse_obj|parse_raw|parse_file)\\\\(',\n     'parse_obj/parse_raw -> model_validate() / model_validate_json()', []),\n    ('from-orm', 'error', '\\\\.from_orm\\\\(',\n     'from_orm() -> model_validate(obj) with from_attributes=True', []),\n    ('copy-update', 'error', '\\\\.copy\\\\(\\\\s*update\\\\s*=',\n     '.copy(update=...) -> .model_copy(update=...)', []),\n    ('schema-method', 'error', '\\\\.(schema|schema_json)\\\\(\\\\s*\\\\)',\n     '.schema() -> .model_json_schema()', []),\n    ('fields-attr', 'error', '\\\\.__fields__\\\\b|\\\\.__fields_set__\\\\b',\n     '__fields__ -> model_fields (on the class); __fields_set__ -> model_fields_set', []),\n    ('forward-refs', 'error', '\\\\.update_forward_refs\\\\(',\n     'update_forward_refs() -> model_rebuild()', []),\n    ('construct', 'error', '\\\\.construct\\\\(',\n     '.construct() -> .model_construct()', []),\n    ('field-regex', 'error', '\\\\bField\\\\([^)]*\\\\bregex\\\\s*=',\n     'Field(regex=...) is removed -> Field(pattern=...)', []),\n    ('field-const', 'error', '\\\\bField\\\\([^)]*\\\\bconst\\\\s*=',\n     'Field(const=...) is removed -> Literal[...]', []),\n    ('field-items', 'error', '\\\\b(min_items|max_items|unique_items)\\\\s*=',\n     'min_items/max_items -> min_length/max_length (unique_items -> a set type)', []),\n    ('allow-mutation', 'error', '\\\\ballow_mutation\\\\b',\n     'allow_mutation -> frozen=True (field or model)', []),\n    ('constr-regex', 'error', '\\\\bconstr\\\\([^)]*\\\\bregex\\\\s*=',\n     'constr(regex=...) -> Annotated[str, StringConstraints(pattern=...)]', []),\n    ('parse-obj-as', 'error', '\\\\bparse_obj_as\\\\(',\n     'parse_obj_as(T, data) -> TypeAdapter(T).validate_python(data)', []),\n    ('on-event', 'error', '\\\\.on_event\\\\(\\\\s*[\\\\\"\\'](startup|shutdown)[\\\\\"\\']',\n     '@app.on_event is deprecated -> lifespan=asynccontextmanager function', []),\n    ('optional-no-default', 'warn', '^\\\\s+\\\\w+\\\\s*:\\\\s*(Optional\\\\[[^\\\\]]+\\\\]|[\\\\w\\\\[\\\\], .]+\\\\|\\\\s*None)\\\\s*$',\n     'Optional[X] with no default is REQUIRED in v2 (it only allows None) -> add = None if it may be left out', []),\n    ('orm-response-dict', 'warn', 'response_model\\\\s*=\\\\s*dict\\\\b',\n     'response_model=dict skips output filtering -> a response model', []),\n]\nFILES = ['*.py']\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\"pydantic_v2_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/pydantic-v2-guard/references/migration-table.md",
      "role": "reference",
      "description": "Pydantic v1 to v2 table for FastAPI services.",
      "skill": "pydantic-v2-guard",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/pydantic-v2-guard/references/migration-table.md",
        "codex": ".agents/skills/pydantic-v2-guard/references/migration-table.md",
        "cursor": ".agents/skills/pydantic-v2-guard/references/migration-table.md",
        "gemini-cli": ".agents/skills/pydantic-v2-guard/references/migration-table.md",
        "antigravity-cli": ".agents/skills/pydantic-v2-guard/references/migration-table.md",
        "copilot": ".agents/skills/pydantic-v2-guard/references/migration-table.md"
      },
      "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/references/migration-table.md",
      "raw_latest_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/latest/skills/pydantic-v2-guard/references/migration-table.md",
      "bytes": 2578,
      "lang": "markdown",
      "content": "# Pydantic v1 → v2 table for FastAPI\n\nFrom the Pydantic migration guide (https://docs.pydantic.dev/latest/migration/) and FastAPI's docs. Checked against\npydantic 2.14.1, pydantic-settings 2.16.2 and fastapi 0.162.0.\n\n## Fails on v2 or in FastAPI\n\n| v1 | v2 | What happens with v1 code |\n|---|---|---|\n| `from pydantic.v1 import BaseModel` in a route | `from pydantic import BaseModel` | FastAPI raises `PydanticV1NotSupportedError` |\n| `from pydantic import BaseSettings` | `from pydantic_settings import BaseSettings` | `PydanticImportError` |\n| `Field(regex=\"^a\")` | `Field(pattern=\"^a\")` | `PydanticUserError: regex is removed` |\n| `Field(const=True)` | `Literal[\"value\"]` | error |\n| `constr(regex=...)` | `Annotated[str, StringConstraints(pattern=...)]` | error |\n| `note: Optional[str]` (no default) | `note: str \\| None = None` | field is now required, so 422 for clients that leave it out |\n\n## Deprecated (warns PydanticDeprecatedSince20, removed in v3)\n\n| v1 | v2 |\n|---|---|\n| `class Config: orm_mode = True` | `model_config = ConfigDict(from_attributes=True)` |\n| `allow_population_by_field_name = True` | `populate_by_name=True` |\n| `schema_extra = {...}` | `json_schema_extra={...}` |\n| `anystr_strip_whitespace = True` | `str_strip_whitespace=True` |\n| `allow_mutation = False` | `frozen=True` |\n| `@validator(\"x\", pre=True)` | `@field_validator(\"x\", mode=\"before\")` + `@classmethod` |\n| `@validator(\"x\", each_item=True)` | `list[Annotated[int, AfterValidator(check)]]` |\n| `@root_validator` | `@model_validator(mode=\"after\")` (instance method) |\n| `m.dict(exclude_unset=True)` | `m.model_dump(exclude_unset=True)` |\n| `m.json()` | `m.model_dump_json()` |\n| `M.parse_obj(d)` / `M.parse_raw(s)` | `M.model_validate(d)` / `M.model_validate_json(s)` |\n| `M.from_orm(row)` | `M.model_validate(row)` with `from_attributes=True` |\n| `m.copy(update={...})` | `m.model_copy(update={...})` |\n| `M.schema()` | `M.model_json_schema()` |\n| `M.__fields__` | `M.model_fields` |\n| `M.update_forward_refs()` | `M.model_rebuild()` |\n| `M.construct(...)` | `M.model_construct(...)` |\n| `Field(min_items=1, max_items=5)` | `Field(min_length=1, max_length=5)` |\n| `parse_obj_as(list[M], data)` | `TypeAdapter(list[M]).validate_python(data)` |\n\n## FastAPI patterns\n\n| Old | Current |\n|---|---|\n| `@app.on_event(\"startup\")` | `@asynccontextmanager async def lifespan(app): ...; yield` and `FastAPI(lifespan=lifespan)` |\n| `response_model=dict` | a response model, so output is filtered and documented |\n| `Depends()` default arguments | `Annotated[Session, Depends(get_session)]` |\n"
    },
    {
      "path": "skills/pydantic-v2-guard/references/v1-sample.py.txt",
      "role": "reference",
      "description": "A FastAPI module written for Pydantic v1, for checking the guard.",
      "skill": "pydantic-v2-guard",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/pydantic-v2-guard/references/v1-sample.py.txt",
        "codex": ".agents/skills/pydantic-v2-guard/references/v1-sample.py.txt",
        "cursor": ".agents/skills/pydantic-v2-guard/references/v1-sample.py.txt",
        "gemini-cli": ".agents/skills/pydantic-v2-guard/references/v1-sample.py.txt",
        "antigravity-cli": ".agents/skills/pydantic-v2-guard/references/v1-sample.py.txt",
        "copilot": ".agents/skills/pydantic-v2-guard/references/v1-sample.py.txt"
      },
      "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/references/v1-sample.py.txt",
      "raw_latest_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/latest/skills/pydantic-v2-guard/references/v1-sample.py.txt",
      "bytes": 1519,
      "lang": "markdown",
      "content": "# A FastAPI module written for Pydantic v1, used to check the guard.\nfrom typing import Optional\n\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel, BaseSettings, Field, root_validator, validator    # basesettings-import, validator, root-validator\n\napp = FastAPI()\n\n\nclass Settings(BaseSettings):\n    database_url: str\n\n    class Config:                                                                 # class-config\n        env_file = \".env\"\n\n\nclass ItemIn(BaseModel):\n    name: str = Field(..., regex=\"^[a-z-]+$\")                                     # field-regex\n    tags: list[str] = Field(default_factory=list, min_items=1)                    # field-items\n    note: Optional[str]                                                           # optional-no-default\n\n    @validator(\"name\")                                                            # validator\n    def lower(cls, v):\n        return v.lower()\n\n\nclass ItemOut(BaseModel):\n    id: int\n    name: str\n\n    class Config:                                                                 # class-config\n        orm_mode = True                                                           # orm-mode\n\n\n@app.on_event(\"startup\")                                                          # on-event\ndef connect():\n    pass\n\n\n@app.post(\"/items\")\ndef create(item: ItemIn):\n    row = save(item.dict())                                                       # dict-method\n    return ItemOut.from_orm(row)                                                  # from-orm\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": [
      "pydantic_v2_guard.py"
    ],
    "notes": "The scanner only reads .py files and prints findings; it never edits files and makes no network calls. Running the tests 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/fastapi-pydantic-v2-guard/",
  "api_url": "https://setupbay.com/api/loadouts/fastapi-pydantic-v2-guard.json",
  "verification": {
    "level": "verified-install",
    "status": "verified",
    "label": "Verified install",
    "verified": true,
    "checked_at": "2026-10-29T12:58:10Z",
    "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 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, 18.7 KB total; largest skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py 7.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": "pydantic_v2_guard.py passes a FastAPI 0.162.0 app on Pydantic 2.14.1 and flags every Pydantic v1 line in the skill's sample: 12 errors and 1 Optional-without-default warning. The app's tests pass with PydanticDeprecatedSince20 as an error, and the pinned FastAPI and Pydantic refuse v1 models in a ro",
        "details": [
          "Applied 12 loadout files at every agent's paths",
          "✓ scanner: the v2 app and tests are clean: bash -c python3 .agents/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py app tests; echo \"exit=$?\"",
          "✓ scanner: the v1 sample is flagged line by line: bash -c mkdir -p /tmp/pydantic-v1-sample && cp .claude/skills/pydantic-v2-guard/references/v1-sample.py.txt /tmp/pydantic-v1-sample/items_v1.py && python3 .claude/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py /tmp/pydantic-v1-sample; echo \"exit=$?\"",
          "✓ pytest with PydanticDeprecatedSince20 as an error: uv run --python 3.12 pytest -q -p no:cacheprovider -W error::pydantic.warnings.PydanticDeprecatedSince20",
          "✓ FastAPI 0.162.0 and Pydantic 2.14.1 on the sample's v1 code: uv run --python 3.12 python legacy_probe.py",
          "Covers: The scanner's v1 rules (Config, orm_mode, validators, dict/parse_obj/from_orm, Field(regex=), min_items, BaseSettings, on_event) against a real v1 FastAPI module",
          "Covers: No false positives on a v2 app with ConfigDict, field and model validators, from_attributes, model_copy, lifespan and pydantic-settings, or on response.json() in tests",
          "Covers: The pinned FastAPI and Pydantic as the second check: v1 models, BaseSettings and regex= are refused; deprecated v1 calls warn, and the tests fail on that warning",
          "Covers: Optional[X] without a default is required in v2 (ValidationError when left out)",
          "Covers: Not covered: real database sessions (the app uses an attribute object in place of an ORM row)"
        ]
      }
    ]
  },
  "install": {
    "claude-code": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "CLAUDE.md",
          "target": "CLAUDE.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/CLAUDE.md",
          "role": "adapter"
        },
        {
          "path": "skills/pydantic-v2-guard/SKILL.md",
          "target": ".claude/skills/pydantic-v2-guard/SKILL.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
          "target": ".claude/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/pydantic-v2-guard/references/migration-table.md",
          "target": ".claude/skills/pydantic-v2-guard/references/migration-table.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/references/migration-table.md",
          "role": "reference"
        },
        {
          "path": "skills/pydantic-v2-guard/references/v1-sample.py.txt",
          "target": ".claude/skills/pydantic-v2-guard/references/v1-sample.py.txt",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/references/v1-sample.py.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/fastapi-pydantic-v2-guard/claude-code.zip"
    },
    "codex": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/pydantic-v2-guard/SKILL.md",
          "target": ".agents/skills/pydantic-v2-guard/SKILL.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
          "target": ".agents/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/pydantic-v2-guard/references/migration-table.md",
          "target": ".agents/skills/pydantic-v2-guard/references/migration-table.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/references/migration-table.md",
          "role": "reference"
        },
        {
          "path": "skills/pydantic-v2-guard/references/v1-sample.py.txt",
          "target": ".agents/skills/pydantic-v2-guard/references/v1-sample.py.txt",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/references/v1-sample.py.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/fastapi-pydantic-v2-guard/codex.zip"
    },
    "cursor": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": ".cursor/rules/pydantic-v2-only.mdc",
          "target": ".cursor/rules/pydantic-v2-only.mdc",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/.cursor/rules/pydantic-v2-only.mdc",
          "role": "rules"
        },
        {
          "path": "skills/pydantic-v2-guard/SKILL.md",
          "target": ".agents/skills/pydantic-v2-guard/SKILL.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
          "target": ".agents/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/pydantic-v2-guard/references/migration-table.md",
          "target": ".agents/skills/pydantic-v2-guard/references/migration-table.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/references/migration-table.md",
          "role": "reference"
        },
        {
          "path": "skills/pydantic-v2-guard/references/v1-sample.py.txt",
          "target": ".agents/skills/pydantic-v2-guard/references/v1-sample.py.txt",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/references/v1-sample.py.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/fastapi-pydantic-v2-guard/cursor.zip"
    },
    "gemini-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "GEMINI.md",
          "target": "GEMINI.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/GEMINI.md",
          "role": "adapter"
        },
        {
          "path": "skills/pydantic-v2-guard/SKILL.md",
          "target": ".agents/skills/pydantic-v2-guard/SKILL.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
          "target": ".agents/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/pydantic-v2-guard/references/migration-table.md",
          "target": ".agents/skills/pydantic-v2-guard/references/migration-table.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/references/migration-table.md",
          "role": "reference"
        },
        {
          "path": "skills/pydantic-v2-guard/references/v1-sample.py.txt",
          "target": ".agents/skills/pydantic-v2-guard/references/v1-sample.py.txt",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/references/v1-sample.py.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/fastapi-pydantic-v2-guard/gemini-cli.zip"
    },
    "antigravity-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/pydantic-v2-guard/SKILL.md",
          "target": ".agents/skills/pydantic-v2-guard/SKILL.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
          "target": ".agents/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/pydantic-v2-guard/references/migration-table.md",
          "target": ".agents/skills/pydantic-v2-guard/references/migration-table.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/references/migration-table.md",
          "role": "reference"
        },
        {
          "path": "skills/pydantic-v2-guard/references/v1-sample.py.txt",
          "target": ".agents/skills/pydantic-v2-guard/references/v1-sample.py.txt",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/references/v1-sample.py.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/fastapi-pydantic-v2-guard/antigravity-cli.zip"
    },
    "copilot": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/pydantic-v2-guard/SKILL.md",
          "target": ".agents/skills/pydantic-v2-guard/SKILL.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
          "target": ".agents/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/scripts/pydantic_v2_guard.py",
          "role": "reference"
        },
        {
          "path": "skills/pydantic-v2-guard/references/migration-table.md",
          "target": ".agents/skills/pydantic-v2-guard/references/migration-table.md",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/references/migration-table.md",
          "role": "reference"
        },
        {
          "path": "skills/pydantic-v2-guard/references/v1-sample.py.txt",
          "target": ".agents/skills/pydantic-v2-guard/references/v1-sample.py.txt",
          "raw_url": "https://setupbay.com/raw/fastapi-pydantic-v2-guard/1.0.0/skills/pydantic-v2-guard/references/v1-sample.py.txt",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://setupbay.com/download/fastapi-pydantic-v2-guard/copilot.zip"
    }
  },
  "source_zip_url": "https://setupbay.com/download/fastapi-pydantic-v2-guard/source.zip",
  "claude_code_plugin": {
    "name": "fastapi-pydantic-v2-guard",
    "marketplace_url": "https://setupbay.com/marketplace.json",
    "install": [
      "/plugin marketplace add https://setupbay.com/marketplace.json",
      "/plugin install fastapi-pydantic-v2-guard@setupbay"
    ],
    "zip_url": "https://setupbay.com/plugins/fastapi-pydantic-v2-guard.zip",
    "sha256": "9d5354cb197830714dbc446d7736eb791bf51a678517f1b6a05670d0f3a0bd7b",
    "carries": [
      "skills"
    ],
    "note": "Plugins do not carry project rules (AGENTS.md, CLAUDE.md); install those from install.claude-code."
  }
}