Skip to main content

Architecture

cogs is one pnpm/Turborepo monorepo serving two audiences from the same source tree: Claude Code skill authors/consumers, and @cogs/* npm package consumers. Here's how the pieces fit together.

Top-level layout

PathWhat it isIn the pnpm workspace?
packages/*24 @cogs/* packages (auth, email, config, react-query, react-hook-form helpers, etc.) — 22 publishable to npm, plus 2 private (send-service, supabase-sync)Yes — pnpm-workspace.yaml globs packages/*
skills/*Canonical source for the 5 Claude Code skills, git-trackedNo (not an npm package tree)
packages/skill-*Generated mirror of skills/*, one npm package per skillYes, but gitignored content — never hand-edited
infra/Terraform for AWS SES, Cloudflare DNS/Turnstile, remote stateNo — applied directly with terraform, never via pnpm
docs/Session dossiers, ADRs, runbooks, published-skills.jsonN/A (plain markdown/JSON)
bin/Release tooling scripts (see below)N/A
website/This documentation site (Docusaurus)No — has its own pnpm-workspace.yaml, isolated on purpose

Skills: canonical source vs. generated mirror

The 5 Claude Code skills are authored once, in exactly one place:

skills/kubb-react-query/
skills/nuqs-table-url-state/
skills/nuqs-url-state/
skills/shadcn-data-table-rhf/
skills/shadcn-react-hook-form-forms/

Each has a SKILL.md (the skill content Claude Code loads) and a metadata.json (version, provenance abstract). These are the files to read or edit.

bin/sync-skill-content.mjs mirrors each skills/<name>/ into a matching packages/skill-<name>/ directory so that directory can be published to npm as its own package (@cogs/skill-<name>) — for distribution channels that expect an installable package rather than a Claude Code plugin. That mirror directory is gitignored and rebuilt from skills/<name>/ on every sync; it is never the place to make an edit; it will be silently overwritten.

The 5 skills are also distributed as a single Claude Code plugin (.claude-plugin/marketplace.json, plugin name cogs), so a Claude Code user gets all 5 in one install rather than five separate npm installs. See Getting Started.

Release pipeline

Publishing a skill package or an @cogs/* library package follows the same root release script:

pnpm build # turbo run build across all packages
node bin/generate-skill-package-json.mjs --check
node bin/sync-skill-content.mjs --all # skills/* -> packages/skill-*
node bin/validate-skill-package.mjs --all
changeset publish

Ongoing releases run this in CI via GitHub Actions, publishing to the public npm @cogs scope using npm trusted publishing (OIDC) — no long-lived npm token is stored in CI.

Infra boundary

infra/ holds the Terraform for the auth/email plane this repo also ships (AWS SES identities, Cloudflare DNS, Turnstile widgets). It's deliberately outside the pnpm workspace and applied directly with terraform, gated by a manual-approval GitHub Actions environment for apply. It's scoped narrowly: infra/variables.tf validates that every domain it manages ends in .workloom.app — other domains (including this docs site's own catesworks.dev subdomain) are intentionally out of scope here, to avoid two separate Terraform states fighting over the same DNS zone.

Why two build systems for one repo

packages/* and website/ are built completely independently:

  • packages/* build through Turborepo (turbo.json), sharing tsconfig.base.json, Biome, and Vitest config.
  • website/ builds through Docusaurus's own toolchain, with its own pnpm-workspace.yaml so pnpm install inside website/ never touches — or is touched by — the root workspace's lockfile.

This mirrors the infra/ pattern: a subtree with a fundamentally different toolchain gets a hard boundary instead of being forced into the shared build graph.