Skip to content

About

No description, website, or topics provided.

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

65 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

starter

License: Apache-2.0 Effect: 4.0 CI

🏛️ starter is an opinionated monorepo template for serious TypeScript with Effect and AI coding agents. 🔒 One architecture, zero knobs, and mechanical gates that reject the slop agents produce when left unconstrained. 🚀 Built for engineers accountable for codebases where AI writes the commits.


💡 Why

AI coding agents produce TypeScript that compiles cleanly and passes shallow unit tests while quietly violating foundational architecture: ambient side effects inside decision logic, unchecked type assertions (as Type), and mock-heavy test suites that mask runtime breakage.

starter establishes an uncompromising substrate. Invariants are not aspirational guidelines or doc comments; they are enforced mechanically by linter rules, complexity ceilings, mutation test floors, and continuous integration gates.

Concern The Naive AI-Assisted Default The Endgame Architecture (starter)
Domain Logic ❌ Interleaved I/O, clocks, random generators, and mutations ✅ Pure functions returning tagged Decision or Refusal unions
Branching ❌ Sprawling nested if/else and procedural loops ✅ Cyclomatic complexity 1 via exhaustive pattern matching (Match)
Boundary Data ❌ Unchecked casts (as unknown as Type, @ts-ignore) ✅ Strict Schema.decode transforming raw bytes into branded types
Test Verification ❌ Mock-heavy tests pinning internal implementation ✅ 100% mutation kill floor (Stryker) and property-based tests
Configuration ❌ Dozens of toggles that let agents bypass strictness ✅ Zero knobs — one proven opinionated toolchain end to end
Package Entries ❌ Star exports (export *) hiding dependency graphs ✅ Explicit named re-exports enumerated one line per symbol
Refactoring ❌ Patching around rotten legacy modules ✅ Delete-first rebuild with published observable pinning

📐 Architecture

Every external interaction in a starter project follows the I/O Sandwich:

read (impure) ──► decode (pure) ──► decide (pure) ──► shape (pure) ──► write (impure)
  1. 📥 read — Gathers raw input from ports and external systems.
  2. 🔍 decode — Validates unvalidated input into branded domain types using Schema.
  3. 🧠 decide — Executes domain logic with cyclomatic complexity 1 (zero I/O, zero ambient state).
  4. 📦 shape — Builds pure output documents and domain events from the decision.
  5. 📤 write — Persists changes, emits domain events, or returns responses.

🧰 Toolchain

starter wires a modern, fast, and type-safe toolchain across the workspace:

Tool Role & Configuration
⚡ pnpm Workspaces Strict workspace dependency management with catalog versioning (pnpm-workspace.yaml)
🏎️ Turbo High-performance task pipeline with cached builds, tests, and lint runs
🛡️ Effect 4 The standard functional effect system
🔍 oxlint Rust-based linter enforcing strict TypeScript rules and the house presets
🎨 dprint Fast, deterministic code and markdown formatting (dprint.json)
🧪 Vitest Fast unit and integration test runner with TypeScript support
🔬 Stryker Mutation testing ensuring tests fail when bugs are introduced
📝 Changesets Automated versioning and changelogs, released as git tags via shared tooling
🪝 Husky & Commitlint Git hooks enforcing conventional commit standards
🌳 Worktrunk Scripts Deno-powered git worktree lifecycle hooks for isolated agent work

📁 Workspaces

The repository is structured into two workspace roots defined in pnpm-workspace.yaml:

.
├── packages/           # Reusable libraries, engines, and domain cores
├── apps/               # The Worker and its end-to-end journeys
├── repos/              # Vendored subtrees (constitution, worktrunk-scripts)
└── docs/               # Solutions, tooling decisions, and plans
  • apps/site — The one Cloudflare Worker: a TanStack Start site that calls its Worker through effect/rpc (one RpcGroup served at /api/rpc, a typed RpcClient in the page) over one D1 database, defined and deployed with Alchemy. pnpm dev emulates the database locally; the health procedure probes the database and answers ok only when the pure decision check-health.workflow.ts finds the probe answered, refusing with DatabaseUnreachable otherwise.
  • apps/site-e2e — The end-to-end journeys that run against pnpm dev (pnpm journeys).

🚀 Getting Started

1. Create a Repository from Template

Click the Use this template button on GitHub, or create a repository via the GitHub CLI:

gh repo create my-effect-project --template systemfsoftware/starter --public
cd my-effect-project

2. Enter the Dev Shell and Install

Installs, builds, tests and git hooks run their dependency code inside a deny-by-default sandbox, never directly on your machine. The Nix dev shell pins the toolchain and carries the sandbox (prerequisites):

nix develop # or: direnv allow
pnpm bootstrap

3. Build and Verify

pnpm build
pnpm check:ci

4. Run It Locally

pnpm dev       # the whole app at http://localhost:1337, Alchemy's local emulation, no cloud
pnpm journeys  # the end-to-end journeys in a real browser against pnpm dev

The site ships one example feature, a guestbook at /guestbook: a pure decision (sign-guestbook.workflow.ts, built with Workflow.make) trims a name and a message and refuses them with typed errors; the RPC procedures sign and list write and read entries in D1, with the decision's tagged refusals as sign's error schema; and the page shows the entries or the refusal. Everything it owns lives in apps/site/src/features/guestbook and apps/site-e2e/tests/features/guestbook.

To remove it:

  1. Delete apps/site/src/features/guestbook.
  2. Delete apps/site-e2e/tests/features/guestbook.
  3. Delete the route file apps/site/src/routes/guestbook.tsx.
  4. apps/site/src/api/site-rpcs.ts: drop the GuestbookRpcs import and make SiteRpcs just HealthRpcs.
  5. apps/site/src/api/site-rpc-server.ts: drop the GuestbookHandlers import and its Layer.provide(GuestbookHandlers) line.
  6. apps/site/alchemy.run.ts: drop the migrations option from Cloudflare.D1.Database('Database', …).

Removal leaves a deployed D1 as it is: the guestbook_entries table and its 0001_create_guestbook_entries.sql row in __alchemy_migrations stay; drop them from the D1 console in the Cloudflare dashboard with DROP TABLE guestbook_entries; DELETE FROM __alchemy_migrations WHERE name = '0001_create_guestbook_entries.sql';.

5. Make the Gates Block Merges

A copy gets the template's files, not its repository settings, so nothing stops a pull request with failing checks from merging until main has a ruleset that requires them. Install the "gates" ruleset from .github/rulesets/gates.json once, with your own GitHub credentials:

gh api -X POST repos/{owner}/{repo}/rulesets --input .github/rulesets/gates.json

Until it is installed, CI's rules check fails on every pull request and every push to main, and the production deploy waits on it.

6. Deploy

Your copy deploys to your own Cloudflare account; the template holds no credentials. Set CLOUDFLARE_API_TOKEN (a token that can edit Workers and D1) and CLOUDFLARE_ACCOUNT_ID, and optionally SITE_DOMAIN (a hostname in a zone on that account) to serve production there instead of on workers.dev:

pnpm run deploy                         # stage prod
ALCHEMY_STAGE=pr-12 pnpm run deploy     # a preview stage
ALCHEMY_STAGE=pr-12 pnpm run destroy

pnpm run deploy, not pnpm deploy: the bare form is pnpm's own built-in command.

In your copy, with the two secrets set as repository secrets (and SITE_DOMAIN as a repository variable), every same-repo pull request gets a preview with its URL in a comment, and main deploys production once the release gate passes. The template itself never deploys.


🚦 Verification Gates

All changes must satisfy local and continuous integration verification gates:

# Format code and markdown
pnpm format:check

# Typecheck workspace packages
pnpm typecheck

# Run linter across packages
pnpm lint

# Run unit and integration tests
pnpm test
# Run full CI suite locally
pnpm check:ci

❓ Frequently Asked Questions

Why does starter require Effect 4 instead of Effect 3?

Effect 4 brings the schema transformations and typed services the template's decisions and boundaries are written with. starter targets the future of Effect rather than supporting legacy patterns.

Why are there no configuration options or preset levels?

Every configuration toggle provides a route for AI agents to downgrade verification standards and reintroduce slop. Zero knobs guarantees that all packages created from this template adhere to identical architectural standards.

How does mutation testing work in this template?

Stryker introduces deliberate syntax and logic mutations into your code and runs your test suite against each mutant. If your tests still pass when code behavior changes, the mutant survives and the gate fails. Domain decisions require a 100% kill score. Mutation runs only in the release gate on pushes to main, never locally or on pull requests.

How do I migrate an existing codebase to this architecture?

Follow the strangler pattern: pin the published observable behavior of a module, delete the legacy file completely, and rebuild it from a blank page using pure I/O sandwiches. Never patch around a flawed core.


🤝 Contributing

Development setup, workflows, and PR guidelines are documented in CONTRIBUTING.md.


📄 License

Licensed under the Apache-2.0 License.

About

No description, website, or topics provided.

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages