{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://skillcatalog.dev/schemas/skillcatalog-local-yml.schema.json",
  "title": "SkillCatalog Local Project Manifest",
  "description": "The optional .skillcatalog/skillcatalog.local.yml file, a checkout-local sibling of .skillcatalog/skillcatalog.yml. SkillCatalog adds it to the checkout's .gitignore. Holds a user's personal, machine-local additions and removals for a project-backed profile. Never the profile's identity authority (that stays in the committed manifest), so profile is optional here. All top-level fields are optional. A legacy root-level skillcatalog.local.yml is readable but must be moved into .skillcatalog/ before any mutation.",
  "type": "object",
  "required": [],
  "additionalProperties": false,
  "properties": {
    "profile": {
      "type": "object",
      "required": [
        "id",
        "display_name"
      ],
      "additionalProperties": false,
      "description": "Optional profile identity. Advisory only; the committed .skillcatalog/skillcatalog.yml remains the identity authority.",
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,127}$",
          "not": {
            "const": "home"
          },
          "description": "Stable profile slug. Must not be 'home' (reserved for the global home profile)."
        },
        "display_name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 500,
          "description": "Human-readable profile name."
        }
      }
    },
    "catalogs": {
      "type": [
        "object",
        "null"
      ],
      "description": "Optional catalog references keyed by the intrinsic catalog id from catalog.yaml. An entry in THIS file must reference a catalog declared in THIS file's catalogs block. A declaration in the committed manifest does not satisfy an entry here. Declaring the same catalog id as the committed manifest with a different source is a compose error; a byte-identical source is allowed.",
      "propertyNames": {
        "minLength": 2,
        "maxLength": 64,
        "pattern": "^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,127}$"
      },
      "additionalProperties": {
        "type": "object",
        "required": [
          "source"
        ],
        "additionalProperties": false,
        "properties": {
          "source": {
            "type": "string",
            "minLength": 1,
            "anyOf": [
              {
                "pattern": "^git@[^\\s?#]*$"
              },
              {
                "pattern": "^[Ss][Ss][Hh]://(?![^\\s?#/]*:[^\\s?#/]*@)[^\\s?#]*$"
              },
              {
                "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://[^/?#\\s@]+(?:/[^\\s?#]*)?$"
              }
            ],
            "description": "Remote Git catalog source. Accepts git@host:path, ssh:// (scheme case-insensitive; user, port, IPv6 literal and colon-path forms all allowed, but no password in the userinfo), and HTTP(S) with a non-empty authority and no userinfo. Query strings, fragments, embedded whitespace, local filesystem paths and file:// URLs are rejected."
          }
        }
      }
    },
    "entries": {
      "type": "array",
      "description": "Optional ordered list of personal, machine-local skills, stacks, or bundles to add to the delivered profile. Merged before the committed team entries; a local entry shadows an identically-keyed team entry.",
      "items": {
        "type": "object",
        "required": [
          "catalog_id",
          "kind",
          "slug"
        ],
        "additionalProperties": false,
        "properties": {
          "catalog_id": {
            "type": "string",
            "minLength": 2,
            "maxLength": 64,
            "pattern": "^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,127}$",
            "description": "Intrinsic catalog id this entry resolves against."
          },
          "kind": {
            "type": "string",
            "enum": [
              "skill",
              "stack",
              "bundle"
            ],
            "description": "Type of catalog entry."
          },
          "slug": {
            "type": "string",
            "pattern": "^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,127}$",
            "description": "Slug of the entry in the referenced catalog."
          }
        }
      },
      "uniqueItems": true
    },
    "excludes": {
      "type": "array",
      "description": "Optional list of committed team entries to drop from delivery locally. Uses the same (catalog_id, kind, slug) shape as an entry and does not need a matching local catalog declaration. Excludes only ever remove team entries, never local additions; an exclude that matches no team entry is a harmless no-op.",
      "items": {
        "type": "object",
        "required": [
          "catalog_id",
          "kind",
          "slug"
        ],
        "additionalProperties": false,
        "properties": {
          "catalog_id": {
            "type": "string",
            "minLength": 2,
            "maxLength": 64,
            "pattern": "^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,127}$",
            "description": "Intrinsic catalog id this exclude resolves against."
          },
          "kind": {
            "type": "string",
            "enum": [
              "skill",
              "stack",
              "bundle"
            ],
            "description": "Type of catalog entry to exclude."
          },
          "slug": {
            "type": "string",
            "pattern": "^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,127}$",
            "description": "Slug of the team entry to exclude from delivery."
          }
        }
      },
      "uniqueItems": true
    }
  },
  "allOf": [
    {
      "if": {
        "properties": {
          "entries": {
            "type": "array",
            "minItems": 1
          }
        },
        "required": [
          "entries"
        ]
      },
      "then": {
        "required": [
          "catalogs"
        ],
        "properties": {
          "catalogs": {
            "type": "object",
            "minProperties": 1
          }
        }
      }
    }
  ]
}
