Skip to content

Contributing a battery

The contract on one page: the folder shape, the gate every battery must pass, and the commands that are the whole review.

5 min read

PagesContributing a battery

A plugin is a folder. You can add a battery, a design or a preset without touching a line of generator code. The whole review is a few commands you run on your own machine.

This page gets you oriented. The full documents live in the repo:

  • CONTRIBUTING.md: the full contract, three reference batteries to copy, and a step-by-step walkthrough.
  • docs/architecture/CONTRACT.md: manifests, frontmatter formats, tokens, slots, merge rules, determinism.
  • packages/core/src/types.ts: the pinned types. Import them. Never redefine them.

The folder

registry/<stacks|batteries|designs>/<id>/
  manifest.yaml            required
  template/                copied into the generated repo
  agent/
    rules/*.md             path-scoped conventions
    skills/*.md            slash commands
    agents/*.md            subagents
    hooks/*.md             hook definition plus its inline script
    solutions/*.md         long-form docs, published at /cookbook/<id>/<doc>
  design.yaml              designs only: the token set
  DESIGN.md                designs only

template/ paths are relative to the generated repo's root: template/src/lib/email/postmark.ts lands at src/lib/email/postmark.ts.

Start by copying the reference battery closest to yours. There are three, and between them they cover every mechanism the registry has: Stripe (the common case), Better Auth (slot injection) and Neon (a hook).

The gate: no config, no battery

A battery isn't accepted until it ships:

  1. Rules: at least one, scoped to the paths your battery owns.
  2. At least one skill: a real slash command for work people repeat.
  3. At least five solution docs: the mistakes, in advance.

This isn't bureaucracy. People pick this generator over a paid kit because the agent editing src/lib/billing/** already knows to verify the webhook signature. A folder of files without that knowledge is what we compete against, not what we build.

Write solution docs for a stranger. Every one is published as a public page in the cookbook. So no "as discussed above", no repo-internal references, no "we" about your team. Good ones answer what someone types into a search box: "idempotent Stripe webhooks", "Neon connection pooling on Vercel". Bad ones restate the vendor's quickstart.

The gate covers three of the six parts of the agentic layer. Agents, hooks and MCP servers are optional. Ship them when your battery needs them: Neon ships a hook and an agent, PostHog ships an agent, and 9 batteries ship an MCP server.

Two rules that trip people up

Never ship a merger-owned file in template/. Two plugins writing the same path is a validation error. These eight are built by the mergers, and you add to them through manifest.yaml instead (contributes.dependencies, .scripts, .env, .mcp, .gitignore, .files):

package.json · tsconfig.json · .mcp.json · .claude/settings.json · CLAUDE.md · README.md · .env.example · .gitignore

Plug into the app through slots, never by editing its pages. A page in the signed-in app gets its sidebar entry from the app-nav slot and its dashboard card from dashboard-cards. A landing section goes in landing-sections, and a vendor that receives user data adds itself to the privacy policy through legal-processors. The full slot table is in registry/README.md.

Never hard-code a package manager or a colour. Use {{pm}}, {{pmx}}, {{pmDlx}}, {{pmRun}} and {{pmAdd}} in every template file, script value and markdown body. {{pmx}} runs a binary the repo installed. {{pmDlx}} fetches a one-off tool. Use design tokens for anything visual. The template lint fails on both. See the token contract.

The commands

There is no CI here and none in generated repos. That's a choice. These run on your machine, and they are the whole review. Please actually run them.

This repo pins Bun (packageManager: bun@1.3.5) and its scripts call bun directly, so unlike a generated repo, these are Bun only:

shell

bun run validate

Manifest schema, path collisions, slots, the template lint and the battery gate. Fast enough to run after every edit.

shell

bun run combos --battery=<id>

Generates your battery alone and in every pair it can join, in memory. Then it checks the output: packages nothing installs, imports that point nowhere, tokens and slots left unfilled, env vars nothing declares. Takes seconds. Add --tier=coupled for the full product of the categories that share modules.

shell

bun run matrix --battery <id> --pairs --build --add-tested

Runs your battery alone, in every pair it can join, and in any preset or curated combo that has it. For each one: install, typecheck, lint, unit tests, next build with no env set, then boot the app and load every static route. Pairs that pass go into registry/tested.yaml. Leave out --pairs and no pair runs, so nothing new gets certified.

If your battery touches sign-in, billing, the database or the admin pages, run the lanes too:

shell

bun run matrix --battery <id> --browser

Each combo gets a fresh database on local Postgres, the repo's own migrations and seeds, a build with that env, and then the repo's own Playwright specs: sign-in, the app shell, checkout and the admin pages. --db alone stops before the browser. See how the generator works.

Never hand-edit tested.yaml. A pair in that file claims the combination was installed, checked, built and booted. The resolver believes it completely, so a line added by hand is how a stranger ends up with a repo that doesn't boot.

shell

bun run snapshot

Regenerates every preset and compares hashes. If a preset that doesn't include your battery changed, you touched something shared. Look hard at the diff before you explain it away.

bun run release-check runs validate, combos, snapshot and matrix --build in order.

Designs and presets

A design ships design.yaml, a light and a dark palette, its own five core components, three preview screens and a DESIGN.md written for an agent. Nirali reviews design pull requests. See designs.

A preset is registry/presets/<id>.json, matching the Preset type. Presets become pages, so description is two to three real paragraphs and highlights four to six concrete bullets. The matrix runs every preset as a whole.

Ownership

Every manifest has an owner: your GitHub handle. It says who decides whether a change to the plugin is right, and who the maintainers go to first when the vendor ships a breaking change. Handing a plugin over means changing owner in the same pull request. A stale owner is worse than none.

Nothing pings the owner automatically yet. If you open an issue about a plugin, tag its owner.

Ravi reviews core, the CLI and agent-layer quality. Expect the review to be mostly about the six layers, because that is where the product is.