{
  "schema": "snappy-card/1",
  "skill": "snappy-blog",
  "tier": "public",
  "origin_tier": "base",
  "purpose": "You are operating as the blog channel for Snappy. This file is the operational contract. The full SKILL.md exists for reference but the rules below are load-bearing -- if you deviate, the post is wrong or the build fails on Vercel.",
  "verbs": [
    {
      "name": "list",
      "args": [],
      "effect": "read",
      "firstCall": "npx tsx ~/.claude/skills/snappy-blog/api.ts list"
    },
    {
      "name": "validate",
      "args": [
        "path"
      ],
      "effect": "read",
      "firstCall": "npx tsx ~/.claude/skills/snappy-blog/api.ts validate <path>"
    }
  ],
  "faces": [],
  "agents": [],
  "uses": [],
  "used_by": [
    "snappy-image"
  ],
  "checks": {
    "pass": 40,
    "fail": 7,
    "defer": 14,
    "line": "40 of 47 checks pass",
    "failing": [
      {
        "id": 4,
        "rule": "Near-neighbour descriptions cross-reference each other",
        "says": "near neighbours not named: snappy-client-feisst, snappy-runner, snappy-specwatch"
      },
      {
        "id": 10,
        "rule": "One term names each concept",
        "says": "mixed synonym sets: endpoint/url/route/path"
      },
      {
        "id": 11,
        "rule": "Each task presents one default, not a menu",
        "says": "3 line(s) present three-way alternative menus"
      },
      {
        "id": 40,
        "rule": "Metadata, skill body, and resources follow three-level budgets",
        "says": "metadata ~190 tokens; body ~4689 tokens"
      },
      {
        "id": 54,
        "rule": "At least three eval scenarios precede ready status",
        "says": "0 eval scenario(s); ready=true"
      },
      {
        "id": 55,
        "rule": "Eval scenarios are multi-call with ground truth",
        "says": "eval manifest missing"
      },
      {
        "id": 57,
        "rule": "Eval manifest contains a disjoint holdout",
        "says": "0 disjoint holdout scenario(s)"
      }
    ]
  },
  "coverage": null,
  "spec": null,
  "evals": null,
  "files": [
    "AGENTS.md",
    "SKILL.md",
    "api.ts",
    "contract.test.ts",
    "entities.json"
  ],
  "total_size": 45097,
  "checksum": "sha256:dd15f936f8954a89c4b3728c7b90a397d416cbac324a0f65375f52a3be887e41",
  "built_at": "2026-09-09T22:08:12.549Z",
  "openapi": {
    "openapi": "3.1.0",
    "info": {
      "title": "snappy-blog",
      "version": "0.0.0",
      "description": "Interview-driven blog post generation for the Snappy website (snappy.ai/blog). Runs the 4-question content interview, drafts opinion-first 800-1200 word MDX with the exact frontmatter format required by snappy-publish, runs the snappy-content anti-AI checklist, routes through writer/council approval gates, and hands the finished MDX to snappy-publish for git deployment. WRITING ONLY -- it does not deploy; snappy-publish owns git and Vercel. NOT for long-form aimed anywhere other than snappy.ai/blog (see article). Triggers on: write blog post, snappy blog, create blog post, new blog article, blog for snappy, write an article, blog draft, draft blog, opinion piece for snappy, mdx blog post, snappy.ai blog, blog post about, write a post for the website."
    },
    "x-snappy-transport": "cli",
    "servers": [
      {
        "url": "npx tsx ~/.claude/skills/snappy-blog/api.ts",
        "description": "The hand itself. Every path is a verb word, every query or body key an argument."
      }
    ],
    "paths": {
      "/list": {
        "get": {
          "operationId": "snappy_blog_list",
          "summary": "snappy-blog list",
          "x-snappy-effect": "read",
          "x-snappy-annotations": {
            "readOnlyHint": true,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": false
          },
          "parameters": [
            {
              "name": "limit",
              "in": "query",
              "required": false,
              "schema": {
                "type": "integer",
                "description": "How many posts to return, newest first. The count is the FLAG --limit, never a positional word; default 20, ceiling 200",
                "default": 20,
                "minimum": 1,
                "maximum": 200,
                "x-snappy-flag": "--limit"
              }
            }
          ],
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/validate": {
        "get": {
          "operationId": "snappy_blog_validate",
          "summary": "snappy-blog validate",
          "x-snappy-effect": "read",
          "x-snappy-annotations": {
            "readOnlyHint": true,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": false
          },
          "parameters": [
            {
              "name": "path",
              "in": "query",
              "required": true,
              "schema": {
                "type": "string",
                "description": "Path to the MDX post whose frontmatter is checked",
                "x-snappy-position": 0
              }
            }
          ],
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      }
    },
    "components": {
      "schemas": {
        "SnappyRefusal": {
          "type": "object",
          "required": [
            "outcome",
            "code",
            "message"
          ],
          "properties": {
            "outcome": {
              "type": "string",
              "const": "refused"
            },
            "code": {
              "type": "string",
              "description": "A row of the closed refusal table."
            },
            "message": {
              "type": "string"
            },
            "fix": {
              "type": "string"
            }
          }
        }
      }
    }
  }
}