# SchemaStore PR — Patchwork Recipe Schema

**Status:** Submitted — [SchemaStore/schemastore#5608](https://github.com/SchemaStore/schemastore/pull/5608) (open, awaiting LGTM from @madskristensen / @hyperupcall as of 2026-04-28)
**Target repo:** https://github.com/SchemaStore/schemastore
**Catalog entry:** `src/api/json/catalog.json` (only — schema served externally)
**External schema URL:** `https://raw.githubusercontent.com/patchworkos/recipes/main/schema/recipe.v1.json`

**Note (2026-04-28):** Per maintainer feedback, the in-repo `src/schemas/json/patchwork-recipe.json` and `src/test/patchwork-recipe/` fixtures were dropped — when the catalog points at an external URL, SchemaStore does not need a mirrored copy. PR diff is now a single `catalog.json` entry.

---

## Pre-submission checklist

- [x] Add `x-taplo` file-patterns to generated schema (`schemaGenerator.ts` — shipped alpha.22)
- [x] Public schema URL resolves: `raw.githubusercontent.com/patchworkos/recipes/main/schema/recipe.v1.json` HTTP 200 (used as catalog `url` instead of needing `patchwork.sh` hosting)
- [x] SchemaStore CI passes — `validate`, `triage`, pre-commit, codeowners-merge all green on commit a85eab5
- [N/A] Test fixtures — dropped per maintainer guidance (external-URL catalog entries don't need them)
- [ ] Confirm `$schema` header in all `examples/recipes/*.yaml` matches the catalog URL exactly (only matters once PR merges and `https://www.schemastore.org/json/` redirect resolves)
- [ ] Decide long-term: keep hosting on `patchworkos/recipes` repo, or migrate to `Oolab-labs/patchwork-os/main/schemas/` (also live, HTTP 200) — currently both work, the former is canonical because catalog points there and `$id` matches

---

## PR title

```
Add Patchwork Recipe schema (patchwork-recipe.json)
```

## PR body

### What is Patchwork?

[Patchwork](https://patchwork.sh) is a recipe-based automation platform for developer workflows. Recipes are YAML files that chain tool calls across SaaS connectors (Jira, GitHub, Slack, Gmail, Linear, etc.) with support for parallel execution, conditional steps, template variables, and approval-gated writes.

### What this PR adds

A JSON Schema for Patchwork recipe files (`.patchwork.yaml` / `.yaml` files with a `name` + `steps` structure). The schema covers:

- Top-level recipe fields: `name`, `description`, `trigger`, `steps`, `maxConcurrency`, `maxDepth`, `apiVersion`
- Per-step fields: `id`, `tool`, `params`, `awaits`, `when`, `optional`, `risk`, `output`, `agent` (agent-step variant), `recipe` (nested-recipe-step variant)
- Trigger types: `manual`, `cron`, `webhook`, `chained`
- Chained-step metadata: `dependencies`, `condition`, `parallelGroups`, `maxDepth`
- Template expression syntax: `{{steps.X.data.field}}`, `{{env.VAR}}`

### Schema location

The live schema is served by the Patchwork bridge at:

```
https://patchwork.sh/schema/recipe.v1.json
```

### Intended editor experience

Any editor with `yaml-language-server` (VS Code YAML extension, IntelliJ, Neovim with yaml-ls, Helix) will provide:

- Autocomplete for all top-level recipe keys and step fields
- Hover docs for `risk`, `awaits`, `when`, `optional`, and tool-specific `params`
- Validation errors for unknown keys, wrong types, and required-field violations

Recipes generated by `patchwork recipe new` already emit:

```yaml
# yaml-language-server: $schema=https://patchwork.sh/schema/recipe.v1.json
name: my-recipe
```

---

## Schema changes — status

`x-taplo` file-patterns are now in the generated schema (shipped alpha.22). The catalog entry still needs to be added manually to the SchemaStore fork:

```json
// In catalog entry (SchemaStore fork, src/api/json/catalog.json):
```

```json
{
  "name": "Patchwork Recipe",
  "description": "Patchwork recipe YAML file for chained automation workflows",
  "fileMatch": ["*.patchwork.yaml", "*.patchwork.yml"],
  "url": "https://patchwork.sh/schema/recipe.v1.json"
}
```

> **Note:** We intentionally do NOT claim `*.yaml` or `*.yml` globally — that would be too broad and conflict with other schemas. Authors who use plain `.yaml` filenames should add the `# yaml-language-server: $schema=...` header comment instead.

---

## Test fixtures to add

SchemaStore requires at least one passing and one failing fixture.

**Positive fixture** (`src/test/patchwork-recipe/positive/basic.yaml`):
```yaml
# yaml-language-server: $schema=https://patchwork.sh/schema/recipe.v1.json
name: jira-to-linear-sync
description: Sync open Jira issues to Linear
steps:
  - id: fetch
    tool: jira.searchIssues
    params:
      jql: "status = Open"
  - id: create
    tool: linear.createIssue
    params:
      title: "{{steps.fetch.data.issues[0].summary}}"
    awaits: [fetch]
    risk: medium
```

**Negative fixture** (`src/test/patchwork-recipe/negative/unknown-field.yaml`):
```yaml
name: bad-recipe
steps:
  - id: step1
    tool: jira.searchIssues
    unknown_field: this_should_fail   # ← additionalProperties: false
```

---

## Files to add in the SchemaStore fork

```
src/schemas/json/patchwork-recipe.json   ← copy of generated recipe.v1.json + fileMatch
src/test/patchwork-recipe/
  positive/basic.yaml
  negative/unknown-field.yaml
```

Catalog entry appended (alphabetical by name) to `src/api/json/catalog.json`:
```json
{
  "name": "Patchwork Recipe",
  "description": "Patchwork recipe YAML file for chained automation workflows",
  "fileMatch": ["*.patchwork.yaml", "*.patchwork.yml"],
  "url": "https://patchwork.sh/schema/recipe.v1.json",
  "versions": {
    "v1": "https://patchwork.sh/schema/recipe.v1.json"
  }
}
```

---

## Remaining gate: public URL

The bridge serves schemas locally at `http://localhost:<port>/schemas/recipe.v1.json` (alpha.22). The public `https://patchwork.sh/schema/recipe.v1.json` URL needs to be wired up before submitting — either a static file host (S3/Cloudflare Pages) that mirrors the generated schema on release, or a redirect to the CDN-cached bridge schema endpoint.

Recommended path: on `npm publish`, run `scripts/publish-schemas.mjs` that uploads `generateSchemaSet()` output to `patchwork.sh/schema/`. This keeps the live URL and the npm package in sync automatically.
