{
  "schema": "snappy-card/1",
  "skill": "snappy-tool-design",
  "tier": "public",
  "origin_tier": "base",
  "purpose": "Use this before trusting or changing a tool surface. It reads the executable",
  "verbs": [
    {
      "name": "lint",
      "args": [
        "skill?"
      ],
      "effect": "read",
      "firstCall": "npx tsx ~/.claude/skills/snappy-tool-design/api.ts lint"
    },
    {
      "name": "example",
      "args": [
        "skill",
        "verb"
      ],
      "effect": "read",
      "firstCall": "npx tsx ~/.claude/skills/snappy-tool-design/api.ts example <skill> <verb>"
    },
    {
      "name": "probe",
      "args": [
        "skill?"
      ],
      "effect": "read",
      "firstCall": "npx tsx ~/.claude/skills/snappy-tool-design/api.ts probe"
    },
    {
      "name": "render",
      "args": [],
      "effect": "read",
      "firstCall": "npx tsx ~/.claude/skills/snappy-tool-design/api.ts render"
    },
    {
      "name": "loop",
      "args": [],
      "effect": "read",
      "firstCall": "npx tsx ~/.claude/skills/snappy-tool-design/api.ts loop"
    },
    {
      "name": "hosts",
      "args": [],
      "effect": "read",
      "firstCall": "npx tsx ~/.claude/skills/snappy-tool-design/api.ts hosts"
    },
    {
      "name": "specs",
      "args": [
        "skill?"
      ],
      "effect": "write-reversible",
      "firstCall": "npx tsx ~/.claude/skills/snappy-tool-design/api.ts specs"
    },
    {
      "name": "openapi",
      "args": [
        "skill"
      ],
      "effect": "read",
      "firstCall": "npx tsx ~/.claude/skills/snappy-tool-design/api.ts openapi <skill>"
    },
    {
      "name": "fix-loader",
      "args": [
        "skill"
      ],
      "effect": "write-reversible",
      "firstCall": "npx tsx ~/.claude/skills/snappy-tool-design/api.ts fix-loader <skill>"
    }
  ],
  "faces": [],
  "agents": [],
  "uses": [],
  "used_by": [],
  "checks": {
    "pass": 43,
    "fail": 6,
    "defer": 12,
    "line": "43 of 49 checks pass",
    "failing": [
      {
        "id": 8,
        "rule": "Skill body and reference graph stay within disclosure budgets",
        "says": "477 lines; ~7214 tokens; hosts-battery.md >100 lines without ToC"
      },
      {
        "id": 9,
        "rule": "Time-sensitive prose is isolated under Old patterns",
        "says": "1 time-sensitive instruction line(s) outside Old patterns"
      },
      {
        "id": 10,
        "rule": "One term names each concept",
        "says": "mixed synonym sets: endpoint/url/route/path"
      },
      {
        "id": 31,
        "rule": "Refusals have outcome, code, message, and fix",
        "says": "refusals schema-checked for outcome/code/message/fix"
      },
      {
        "id": 32,
        "rule": "Refusals name a literal from the valid domain",
        "says": "refusals checked against contract literals"
      },
      {
        "id": 40,
        "rule": "Metadata, skill body, and resources follow three-level budgets",
        "says": "metadata ~124 tokens; body ~7214 tokens"
      }
    ]
  },
  "coverage": null,
  "spec": null,
  "evals": {
    "scenarios": 4,
    "holdout": 2,
    "line": "4 eval scenarios, 2 held out"
  },
  "files": [
    "AGENTS.md",
    "HOSTS-LEDGER.md",
    "SKILL.md",
    "api.test.ts",
    "api.ts",
    "briefs/hosts-family-has-no-read.md",
    "briefs/hosts-first-call-refuses.md",
    "briefs/hosts-no-face-family.md",
    "briefs/hosts-not-driven-chatgpt.md",
    "briefs/hosts-not-driven-claude.md",
    "briefs/hosts-not-driven-codex.md",
    "briefs/hosts-not-driven-menubar.md",
    "briefs/hosts-not-driven-telegram.md",
    "briefs/hosts-road-names-no-face.md",
    "briefs/hosts-verb-folds-onto-no-shape.md",
    "briefs/r10-one-term-names-each-concept.md",
    "briefs/r11-each-task-presents-one-default-not-a-menu.md",
    "briefs/r12-description-contains-no-unauthorised-imperative.md",
    "briefs/r13-argument-without-a-description.md",
    "briefs/r15-near-miss-keys-refuse-with-the-correct-contract-key.md",
    "briefs/r16-runner-known-values-are-not-required-arguments.md",
    "briefs/r18-verb-without-an-effect-class.md",
    "briefs/r19-annotations-not-derived-from-class.md",
    "briefs/r20-read-and-write-are-separate-verbs.md",
    "briefs/r23-responses-obey-hard-and-soft-caps.md",
    "briefs/r24-limit-does-not-bound-the-answer.md",
    "briefs/r31-refusal-envelope-incomplete.md",
    "briefs/r32-refusals-name-a-literal-from-the-valid-domain.md",
    "briefs/r33-refusal-codes-form-one-tested-closed-table.md",
    "briefs/r35-requirements-are-declared-from-executable-credential-reads.md",
    "briefs/r39-verb-sets-consolidate-intent-rather-than-mirror-endpoints.md",
    "briefs/r4-near-neighbour-descriptions-cross-reference-each-other.md",
    "briefs/r40-metadata-skill-body-and-resources-follow-three-level-budgets.md",
    "briefs/r43-a-read-is-one-call-not-a-queued-job.md",
    "briefs/r45-long-work-is-a-separately-named-job-verb.md",
    "briefs/r5-description-is-valid-third-person-and-says-what-plus-when.md",
    "briefs/r54-ready-without-evals.md",
    "briefs/r55-eval-scenarios-are-multi-call-with-ground-truth.md",
    "briefs/r57-eval-manifest-contains-a-disjoint-holdout.md",
    "briefs/r60-loader-block-stale.md",
    "briefs/r7-promised-and-declared-verbs-have-no-set-difference.md",
    "briefs/r8-skill-body-and-reference-graph-stay-within-disclosure-budget.md",
    "briefs/r9-time-sensitive-prose-is-isolated-under-old-patterns.md",
    "browser-eval.test.ts",
    "browser-eval.ts",
    "corpus.ts",
    "evals.json",
    "hosts-bar.ts",
    "hosts-battery.md",
    "hosts-drive.ts",
    "hosts-run.ts",
    "hosts.test.ts",
    "hosts.ts",
    "loop.test.ts",
    "loop.ts",
    "probe.test.ts",
    "probe.ts",
    "r30-scope.test.ts",
    "refusals.test.ts",
    "render.ts",
    "report.ts",
    "skill-check-rung.md",
    "spec-corpus.test.ts",
    "spec-corpus.ts",
    "tool-design-hosts.json",
    "tool-design-probe.json",
    "tool-design-render.json",
    "tool-design-specs.json"
  ],
  "total_size": 858377,
  "checksum": "sha256:f95d9eea9a9bf8095eb5db1bbe38208d251478cc86870d701e208a4c827fe4ce",
  "built_at": "2026-09-09T22:11:15.791Z",
  "openapi": {
    "openapi": "3.1.0",
    "info": {
      "title": "snappy-tool-design",
      "version": "0.0.0",
      "description": "Contract-first ergonomics lint for AI-operated skills, unlike snappy-artifact-loop which manages implementation feedback. Use when auditing tool descriptions, verbs, arguments, effects, refusals, response bounds, progressive disclosure, latency, faces, the derived first call, or eval evidence against the sourced 61-rule research table. Triggers on: snappy-tool-design, tool design, lint a skill, tool ergonomics, verb table, derived first call, AGENTS.md drift, refusal shape, response bounds."
    },
    "x-snappy-transport": "cli",
    "servers": [
      {
        "url": "npx tsx ~/.claude/skills/snappy-tool-design/api.ts",
        "description": "The hand itself. Every path is a verb word, every query or body key an argument."
      }
    ],
    "paths": {
      "/lint": {
        "get": {
          "operationId": "snappy_tool_design_lint",
          "summary": "snappy-tool-design lint",
          "x-snappy-effect": "read",
          "x-snappy-annotations": {
            "readOnlyHint": true,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": false
          },
          "parameters": [
            {
              "name": "skill",
              "in": "query",
              "required": false,
              "schema": {
                "type": "string",
                "description": "Optional snappy-* directory name; omit with --all",
                "x-snappy-position": 0
              }
            },
            {
              "name": "all",
              "in": "query",
              "required": false,
              "schema": {
                "x-snappy-flag": "--all"
              }
            },
            {
              "name": "summary",
              "in": "query",
              "required": false,
              "schema": {
                "x-snappy-flag": "--summary"
              }
            },
            {
              "name": "limit",
              "in": "query",
              "required": false,
              "schema": {
                "type": "integer",
                "description": "Maximum skill rows printed in the collection table",
                "default": 100,
                "maximum": 500,
                "x-snappy-flag": "--limit"
              }
            }
          ],
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/example": {
        "get": {
          "operationId": "snappy_tool_design_example",
          "summary": "snappy-tool-design example",
          "x-snappy-effect": "read",
          "x-snappy-annotations": {
            "readOnlyHint": true,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": false
          },
          "parameters": [
            {
              "name": "skill",
              "in": "query",
              "required": true,
              "schema": {
                "type": "string",
                "description": "Snappy skill directory name",
                "x-snappy-position": 0
              }
            },
            {
              "name": "verb",
              "in": "query",
              "required": true,
              "schema": {
                "type": "string",
                "description": "Exact verb key from HAND_CONTRACT.verbs",
                "x-snappy-position": 1
              }
            }
          ],
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/probe": {
        "get": {
          "operationId": "snappy_tool_design_probe",
          "summary": "snappy-tool-design probe",
          "x-snappy-effect": "read",
          "x-snappy-annotations": {
            "readOnlyHint": true,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "parameters": [
            {
              "name": "skill",
              "in": "query",
              "required": false,
              "schema": {
                "type": "string",
                "description": "One snappy-* hand to probe; omit for the whole collection",
                "x-snappy-position": 0
              }
            },
            {
              "name": "via",
              "in": "query",
              "required": false,
              "schema": {
                "type": "string",
                "description": "Which MCP road to drive",
                "enum": [
                  "mini",
                  "public"
                ],
                "default": "mini",
                "x-snappy-flag": "--via"
              }
            },
            {
              "name": "out",
              "in": "query",
              "required": false,
              "schema": {
                "type": "string",
                "description": "Where to write the probe corpus",
                "x-snappy-flag": "--out"
              }
            },
            {
              "name": "token-file",
              "in": "query",
              "required": false,
              "schema": {
                "x-snappy-flag": "--token-file"
              }
            }
          ],
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/render": {
        "get": {
          "operationId": "snappy_tool_design_render",
          "summary": "snappy-tool-design render",
          "x-snappy-effect": "read",
          "x-snappy-annotations": {
            "readOnlyHint": true,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "parameters": [
            {
              "name": "out",
              "in": "query",
              "required": false,
              "schema": {
                "type": "string",
                "description": "Where to write the render corpus",
                "x-snappy-flag": "--out"
              }
            },
            {
              "name": "host-dir",
              "in": "query",
              "required": false,
              "schema": {
                "x-snappy-flag": "--host-dir"
              }
            },
            {
              "name": "shot-dir",
              "in": "query",
              "required": false,
              "schema": {
                "x-snappy-flag": "--shot-dir"
              }
            },
            {
              "name": "kind",
              "in": "query",
              "required": false,
              "schema": {
                "type": "string",
                "description": "Comma-separated face kinds to draw instead of the whole bundle; needs --out so a partial run cannot overwrite the collection's render file",
                "x-snappy-flag": "--kind"
              }
            }
          ],
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/loop": {
        "get": {
          "operationId": "snappy_tool_design_loop",
          "summary": "snappy-tool-design loop",
          "x-snappy-effect": "read",
          "x-snappy-annotations": {
            "readOnlyHint": true,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "parameters": [
            {
              "name": "via",
              "in": "query",
              "required": false,
              "schema": {
                "x-snappy-flag": "--via"
              }
            },
            {
              "name": "out",
              "in": "query",
              "required": false,
              "schema": {
                "type": "string",
                "description": "Directory that receives LEDGER.md and briefs/",
                "x-snappy-flag": "--out"
              }
            },
            {
              "name": "skip-probe",
              "in": "query",
              "required": false,
              "schema": {
                "x-snappy-flag": "--skip-probe"
              }
            },
            {
              "name": "skip-render",
              "in": "query",
              "required": false,
              "schema": {
                "x-snappy-flag": "--skip-render"
              }
            }
          ],
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/hosts": {
        "get": {
          "operationId": "snappy_tool_design_hosts",
          "summary": "snappy-tool-design hosts",
          "x-snappy-effect": "read",
          "x-snappy-annotations": {
            "readOnlyHint": true,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": true
          },
          "parameters": [
            {
              "name": "out",
              "in": "query",
              "required": false,
              "schema": {
                "type": "string",
                "description": "Directory that receives tool-design-hosts.json, HOSTS-LEDGER.md and briefs/",
                "x-snappy-flag": "--out"
              }
            },
            {
              "name": "token-file",
              "in": "query",
              "required": false,
              "schema": {
                "x-snappy-flag": "--token-file"
              }
            },
            {
              "name": "url",
              "in": "query",
              "required": false,
              "schema": {
                "x-snappy-flag": "--url"
              }
            },
            {
              "name": "only",
              "in": "query",
              "required": false,
              "schema": {
                "type": "string",
                "description": "Comma-separated host ids to attempt; the rest record not-driven with a reason",
                "enum": [
                  "chatgpt",
                  "claude",
                  "codex",
                  "menubar",
                  "telegram"
                ],
                "x-snappy-flag": "--only"
              }
            },
            {
              "name": "codex-model",
              "in": "query",
              "required": false,
              "schema": {
                "x-snappy-flag": "--codex-model"
              }
            }
          ],
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/specs": {
        "post": {
          "operationId": "snappy_tool_design_specs",
          "summary": "snappy-tool-design specs",
          "x-snappy-effect": "write-reversible",
          "x-snappy-annotations": {
            "readOnlyHint": false,
            "destructiveHint": false,
            "idempotentHint": true,
            "openWorldHint": true
          },
          "requestBody": {
            "required": false,
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "skill": {
                      "type": "string",
                      "description": "One snappy-* hand whose pinned vendor spec is re-read; omit for every hand that pins one",
                      "x-snappy-position": 0
                    }
                  }
                }
              }
            }
          },
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/openapi": {
        "get": {
          "operationId": "snappy_tool_design_openapi",
          "summary": "snappy-tool-design openapi",
          "x-snappy-effect": "read",
          "x-snappy-annotations": {
            "readOnlyHint": true,
            "destructiveHint": false,
            "idempotentHint": false,
            "openWorldHint": false
          },
          "parameters": [
            {
              "name": "skill",
              "in": "query",
              "required": true,
              "schema": {
                "type": "string",
                "description": "Snappy skill whose HAND_CONTRACT is emitted as an OpenAPI 3.1 document",
                "x-snappy-position": 0
              }
            }
          ],
          "responses": {
            "200": {
              "description": "The verb's own answer, as printed by `--json`."
            }
          }
        }
      },
      "/fix-loader": {
        "post": {
          "operationId": "snappy_tool_design_fix_loader",
          "summary": "snappy-tool-design fix-loader",
          "x-snappy-effect": "write-reversible",
          "x-snappy-annotations": {
            "readOnlyHint": false,
            "destructiveHint": false,
            "idempotentHint": true,
            "openWorldHint": false
          },
          "requestBody": {
            "required": true,
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "skill": {
                      "type": "string",
                      "description": "Snappy skill whose AGENTS.md contract block is regenerated",
                      "x-snappy-position": 0
                    }
                  },
                  "required": [
                    "skill"
                  ]
                }
              }
            }
          },
          "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"
            }
          }
        }
      }
    }
  }
}