{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://reuseforge.com/schemas/reuseforge.schema.json",
  "title": "ReuseForge publish manifest (reuseforge.json)",
  "description": "Describes what a project publishes to ReuseForge. Read by `rf publish`, validated by the server in publish_version (stored as ItemVersion.manifest). Unknown top-level keys are rejected except those starting with `x-`, so typos fail loudly.",
  "type": "object",
  "required": ["name", "version", "kind"],
  "additionalProperties": false,
  "patternProperties": {
    "^x-": {}
  },
  "properties": {
    "$schema": {
      "type": "string",
      "description": "Editor hint. Ignored by the platform."
    },
    "name": {
      "type": "string",
      "pattern": "^(?:[a-z0-9](?:[a-z0-9-]{0,37}[a-z0-9])?/)?[a-z0-9][a-z0-9._-]{0,63}$",
      "description": "`owner/slug`, or just `slug` to publish under the logged-in account. Same grammar as the ItemRef schema in the OpenAPI document.",
      "examples": ["acme/invoice-extractor", "invoice-extractor"]
    },
    "version": {
      "type": "string",
      "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z.-]+)?(\\+[0-9A-Za-z.-]+)?$",
      "description": "Semantic version 2.0.0. A `-` suffix makes it a prerelease, which `latest` skips. Versions are immutable once published.",
      "examples": ["1.4.0", "1.5.0-rc.1"]
    },
    "kind": {
      "type": "string",
      "enum": ["repo", "skill", "harness", "mcp_server", "model", "dataset", "prompt", "template"],
      "description": "Same values as the ItemKind schema in the OpenAPI document."
    },
    "title": {
      "type": "string",
      "minLength": 1,
      "maxLength": 120
    },
    "summary": {
      "type": "string",
      "maxLength": 280,
      "description": "One line shown in search results."
    },
    "readme": {
      "type": "string",
      "maxLength": 512,
      "description": "Path of the README file inside the project (default `README.md`). Its text becomes the item's readme.",
      "default": "README.md"
    },
    "license": {
      "type": "string",
      "maxLength": 64,
      "description": "SPDX license expression, for example `MIT` or `Apache-2.0 OR MIT`. Use `LicenseRef-Proprietary` for paid closed items.",
      "examples": ["MIT", "Apache-2.0", "LicenseRef-Proprietary"]
    },
    "homepage": {
      "type": "string",
      "maxLength": 300,
      "format": "uri"
    },
    "repository": {
      "type": "string",
      "maxLength": 300,
      "format": "uri",
      "description": "Source repository URL, stored as the item's source."
    },
    "pricing": {
      "type": "object",
      "additionalProperties": false,
      "description": "Informational for `rf publish`: the price lives on the item, not on the version. The CLI never changes a price implicitly; it warns when this differs from the item and `rf items update --price` is the explicit path.",
      "properties": {
        "model": {
          "type": "string",
          "enum": ["free", "fixed", "pwyw"],
          "default": "free"
        },
        "price_cents": {
          "type": "integer",
          "minimum": 0,
          "maximum": 100000,
          "description": "Integer US cents. Required when model is `fixed`."
        }
      },
      "if": {
        "properties": { "model": { "const": "fixed" } },
        "required": ["model"]
      },
      "then": {
        "required": ["price_cents"]
      }
    },
    "capabilities": {
      "type": "array",
      "maxItems": 12,
      "uniqueItems": true,
      "items": { "type": "string", "maxLength": 64 },
      "description": "Capability ids from the platform taxonomy, for example `pdf.extract`."
    },
    "tags": {
      "type": "array",
      "maxItems": 20,
      "uniqueItems": true,
      "items": { "type": "string", "maxLength": 40 }
    },
    "files": {
      "type": "array",
      "maxItems": 100,
      "items": { "type": "string", "minLength": 1, "maxLength": 256 },
      "description": "Include globs relative to the manifest directory (`*`, `**`, `?`). Default: everything not ignored."
    },
    "ignore": {
      "type": "array",
      "items": { "type": "string", "minLength": 1, "maxLength": 256 },
      "description": "Exclude globs, applied after `files`, on top of the built-in ignore list (.git, node_modules, .venv, __pycache__, .DS_Store, reuseforge.lock, .env*)."
    },
    "preview": {
      "type": "array",
      "items": { "type": "string", "minLength": 1, "maxLength": 256 },
      "description": "Globs of files readable without purchase (README, SKILL.md)."
    },
    "executable": {
      "type": "array",
      "maxItems": 100,
      "items": { "type": "string", "minLength": 1, "maxLength": 256 },
      "description": "Globs (`*`, `**`, `?`) of published files that get mode 0755 in the ZIP archive (`get_version_archive`); every other file is 0644. Setuid and setgid are never set."
    },
    "dependencies": {
      "type": "object",
      "description": "Other ReuseForge items this one needs, as a map of `owner/slug` to semver range.",
      "propertyNames": {
        "pattern": "^[a-z0-9](?:[a-z0-9-]{0,37}[a-z0-9])?/[a-z0-9][a-z0-9._-]{0,63}$"
      },
      "maxProperties": 50,
      "additionalProperties": {
        "type": "string",
        "maxLength": 64,
        "description": "Exact `1.2.3`, `^1.2.0`, `~1.2.0`, `>=1.2.0 <2.0.0` or `*`."
      },
      "examples": [{ "acme/pdf-text-layer": "^2.1.0" }]
    },
    "install": {
      "type": "object",
      "description": "Per-target overrides of the default install recipe (see DESIGN.md section 13). Keys are install targets.",
      "propertyNames": {
        "enum": ["generic", "claude-code", "cursor", "codex", "mcp-json"]
      },
      "additionalProperties": { "$ref": "#/$defs/installOverride" }
    },
    "mcp": {
      "$ref": "#/$defs/mcpServer"
    },
    "test": {
      "type": "object",
      "additionalProperties": false,
      "description": "What `request_verification` runs in the sandbox. The result becomes the item's attested tests_pass_rate.",
      "required": ["command"],
      "properties": {
        "command": {
          "type": "string",
          "minLength": 1,
          "maxLength": 500,
          "examples": ["pytest -q", "node --test"]
        },
        "timeout": {
          "type": "integer",
          "minimum": 1,
          "maximum": 900,
          "default": 120,
          "description": "Seconds."
        },
        "runtime": {
          "type": "string",
          "enum": ["python3.11", "python3.12", "node22", "bash"],
          "description": "Sandbox image."
        }
      }
    },
    "requires": {
      "type": "object",
      "additionalProperties": false,
      "description": "What the consumer's environment must provide. Informational, shown before install.",
      "properties": {
        "python": { "type": "string", "maxLength": 64 },
        "node": { "type": "string", "maxLength": 64 },
        "env": {
          "type": "array",
          "items": { "type": "string", "pattern": "^[A-Z][A-Z0-9_]*$" },
          "description": "Names (never values) of environment variables the item reads."
        },
        "tools": {
          "type": "array",
          "items": { "type": "string", "maxLength": 64 },
          "description": "Executables expected on PATH, for example `git`."
        }
      }
    },
    "targets": {
      "type": "array",
      "uniqueItems": true,
      "items": { "enum": ["generic", "claude-code", "cursor", "codex", "mcp-json"] },
      "description": "Install targets the author has tested. Default: all that apply to the kind."
    },
    "changelog": {
      "type": "string",
      "maxLength": 20000,
      "description": "Release notes of this version. Alternatively a path to a CHANGELOG file via `changelog_file`."
    },
    "changelog_file": {
      "type": "string",
      "maxLength": 512,
      "description": "Path of a markdown file whose text is used as the changelog when `changelog` is absent."
    }
  },
  "$defs": {
    "installOverride": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "dest": {
          "type": "string",
          "maxLength": 256,
          "description": "Destination directory relative to the project root (or `~/...`). Replaces the default for the kind."
        },
        "files": {
          "type": "array",
          "maxItems": 100,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["from", "to"],
            "properties": {
              "from": { "type": "string", "minLength": 1, "maxLength": 256, "description": "Path inside the published files." },
              "to": { "type": "string", "minLength": 1, "maxLength": 256, "description": "Destination path relative to `dest`." }
            }
          }
        },
        "config_patches": {
          "type": "array",
          "maxItems": 10,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["path", "format"],
            "properties": {
              "path": { "type": "string", "maxLength": 256 },
              "format": { "enum": ["json", "toml", "markdown_block"] },
              "merge": { "type": "object" },
              "block_id": { "type": "string", "maxLength": 64 },
              "content": { "type": "string", "maxLength": 20000 }
            }
          }
        },
        "commands": {
          "type": "array",
          "maxItems": 10,
          "description": "Display-only suggestions. Clients never run them without an explicit flag.",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["command", "description"],
            "properties": {
              "command": { "type": "string", "maxLength": 500 },
              "description": { "type": "string", "maxLength": 200 }
            }
          }
        }
      }
    },
    "mcpServer": {
      "type": "object",
      "additionalProperties": false,
      "description": "How to start or reach the MCP server this item ships (kind `mcp_server`). `env` and `headers` values must be `${VAR}` placeholders, never secrets.",
      "required": ["transport"],
      "properties": {
        "transport": { "enum": ["stdio", "http"] },
        "command": { "type": "string", "maxLength": 256 },
        "args": { "type": "array", "items": { "type": "string", "maxLength": 256 }, "maxItems": 50 },
        "env": {
          "type": "object",
          "additionalProperties": { "type": "string", "pattern": "^\\$\\{[A-Z][A-Z0-9_]*\\}$" }
        },
        "url": { "type": "string", "format": "uri", "maxLength": 300 },
        "headers": {
          "type": "object",
          "additionalProperties": { "type": "string", "pattern": "^(?:[A-Za-z]+ )?\\$\\{[A-Z][A-Z0-9_]*\\}$" }
        }
      },
      "allOf": [
        {
          "if": { "properties": { "transport": { "const": "stdio" } }, "required": ["transport"] },
          "then": { "required": ["command"] }
        },
        {
          "if": { "properties": { "transport": { "const": "http" } }, "required": ["transport"] },
          "then": { "required": ["url"] }
        }
      ]
    }
  },
  "examples": [
    {
      "name": "acme/invoice-extractor",
      "version": "1.4.0",
      "kind": "skill",
      "title": "Invoice extractor",
      "summary": "Extract line items and totals from invoice PDFs.",
      "license": "MIT",
      "capabilities": ["pdf.extract"],
      "tags": ["invoices", "pdf"],
      "files": ["SKILL.md", "scripts/**"],
      "ignore": ["**/*.pyc"],
      "preview": ["SKILL.md"],
      "dependencies": { "acme/pdf-text-layer": "^2.1.0" },
      "test": { "command": "pytest -q", "timeout": 120, "runtime": "python3.12" },
      "requires": { "python": ">=3.9", "env": ["OPENAI_API_KEY"] },
      "changelog": "Handles multi-page invoices."
    },
    {
      "name": "acme/pg-readonly-mcp",
      "version": "0.3.1",
      "kind": "mcp_server",
      "license": "Apache-2.0",
      "mcp": {
        "transport": "stdio",
        "command": "node",
        "args": ["server.js"],
        "env": { "DATABASE_URL": "${DATABASE_URL}" }
      }
    }
  ]
}
