"Can we do more with less code?"
Proposed implementation should target minimal code surface without sacrificing correctness, performance, and security. If you have an idea to refactor and making the code more reusable/maintainable/production-grade, propose it as an option, not as the default.
The product name is "anvil", with lowercase "a". Use this consistently in your response, code, comments, etc.
This repository is in v1, not blank-slate prototyping. Core v1 flows are implemented, and the repo now has worker-focused Vitest coverage, a narrow queue integration path, and a small Playwright frontend e2e suite. This repository is still under active development, so expect surrounding code, adjacent files, and in-flight changes to move while you work. Manual verification is still expected for volatile paths, especially around sandbox/container behavior and other platform-sensitive execution flows.
- Multiple agents may be implementing changes in parallel.
- Prefer using multiple agents when the work can be split into independent parallel tasks. Plan work to be parallelizable.
- If a task involves comprehending or modifying
src/worker, always useGPT-5.4withxhighreasoning effort orClaude Opus 4.6withmaxreasoning effort. - Keep edits scoped to the task at hand.
- Prefer focused, minimal changes over broad refactors or unrelated cleanup.
- Prefer clarity over cleverness. Favor explicit names and intermediate types over dense inline expressions. Small duplication is acceptable when it materially improves readability and maintenance.
- Avoid touching unrelated files unless a change is required for correctness.
- Do not assume automated tests are complete or authoritative for all behavior.
- When validation is needed, prefer the existing worker harness for worker/backend changes, use the frontend e2e suite for client flows, then leave volatile-path verification for the user.
- Keep shared client/server validation aligned when changing invite acceptance or password policy.
src/contracts/auth.tsis the shared source of truth for invite/password constants. src/contracts/is the shared client/server API surface: types and codecs that cross the HTTP boundary (API request/response shapes, branded IDs, public enums likeRunStatus,DispatchMode). If the frontend never imports it, it does not belong here.src/worker/contracts/holds worker-internal coordination types: DO RPC inputs/results, execution state machines (ProjectRunStatus,D1SyncStatus), dispatch/queue messages, trusted serialization helpers, and repo-config validation. These cross the Workers↔DO boundary but never reach the client.- Frontend code in
src/client/must use proper accessibility (a11y) practices: semantic HTML elements, ARIA attributes where needed, keyboard navigability, sufficient color contrast, and screen-reader-friendly labels.
- Do not start the dev server as part of routine agent work. Assume I already have it running in another terminal unless proven otherwise.
- Do not modify
drizzle/files directly: they are generated bynpm run db:generate. Modify schema insrc/worker/db/first, then generate the migration. - Prioritize lookup on
cloudflare-docsmcp for up-to-date Cloudflare developer docs (if available). Fallback to searching the web if you cannot find the relevant information on this mcp - For confirming the function's exported signature, it is often faster to read the module's
.d.tsinnode_modules/instead of searching the web. For documented/published behaviors, searching the web/mcp on the latest information/guideline is appropriate.
- Restrict the use of
chrome-devtoolsto browse the local dev server. - Before trying to use this mcp tool (if available), confirm with
curl http://127.0.0.1:9222/json/versionthat Chrome is running on Windows, not in WSL2. - Browser the local dev server for validation when you are working on frontend code in
src/client, or when I asked/implied you to check. If the intention is unclear, ask for confirmation/clarification.
- Keep cross-runtime boundaries to a single hop from the caller. Workers -> DO and Workers -> D1 are allowed, but do not chain boundaries transitively. In particular, Workers -> DO -> D1 is not allowed.
- DO -> DO is allowed (e.g. ProjectDO -> RunDO) if and only if it is single hop (not part of a transitive chain), and not reentrant.
- See "Transaction/Mutation/Saga" below
- Retain strict mutation boundaries between D1, Workers, and Durable Object: Durable Objects can hold a transaction against its object (e.g. Project, Run, etc). Do not attempt to propose a design that resembles a distributed transaction (e.g. Workers reads a row, decide on resolutions, then invoke Durable Object RPC to mutate)
- If there needs to be resolution, Durable Object can resolve that conflict in a transaction and return a tagged union for Workers to decide
- If there needs to be reconciliation with D1, do not query/modify D1 directly in Durable Object RPC- use
alarm()for reconciliation. A similar pattern already exists. - If you need to reach for
blockConcurrencyWhilein an Durable Object RPC, your design is probably wrong. Try again. - In general: Workers stays stateless, no mutations; Durable Objects are stateful, transactional.
- Durable Objects do not throw
HttpError. Use tagged union between Workers and Durable Objects to communicate outcome. - Reserve
throwin Durable Object for FUBAR
- Available commands:
npm test,npm run test:integration:queue,npm run test:integration:workflows,npm run test:e2e,npx tsc -p tests/tsconfig.json --noEmit tests/worker/remains the fast worker-first harness for routes, D1/DO invariants, dispatch edge cases, and shared worker utilities- For
tests/worker/setup that is not explicitly asserting alarm delivery or alarm persistence, prefer thetests/helpers/project-do.ts*WithoutAlarmhelpers so state-invariant tests do not race background reconciliation. - Do not persist real alarms in tests unless the test is specifically about alarm scheduling or alarm delivery; record or stub the write instead when the assertion only cares that an alarm would be armed.
- For route and ownership/atomicity tests that do not care about workflow dispatch, prefer projects with
dispatchMode: "queue"to avoid background Workflow work leaking into teardown. tests/e2e/is the live frontend Playwright suite for auth, route guards, profile flows, and basic project CRUD against a locally started apptests/integration/queue-runner-happy-path.test.tsis the narrow end-to-end queue integration check: it applies local D1 migrations, seeds a bootstrap invite, starts the local app, accepts the invite, logs in, creates a project, triggers queued runs, verifies a passing run's steps/logs, and checks that back-to-back runs serialize correctlytests/integration/workflows-runner-happy-path.test.tsis the narrow end-to-end Workflow-backed integration check: it applies local D1 migrations, seeds a bootstrap invite, starts the local app, accepts the invite, logs in, creates a Workflow-backed project, triggers a run, and verifies a passing run's steps/logs- Treat live run-execution coverage as expensive. Do not duplicate it across
tests/integration/queue-runner-happy-path.test.tsand Playwright run-trigger flows. - Prefer the queue integration test for queue-backed execution and the workflows integration test for Workflow-backed execution. Keep Playwright focused on browser/UI coverage unless there is a clear local-development reason to exercise real run execution there too.
- If the queue integration test, workflows integration test, and browser e2e coverage that triggers real runs need to be run on a local dev machine, run them sequentially, not in parallel.
- Cloudflare Workers Vitest still runs with containers disabled; container-related workerd exceptions and sourcemap noise can appear in
tests/worker/output without failing the file - Use the worker harness for fast feedback, use the Playwright suite for frontend validation, and treat the integration tests as targeted coverage for the local run path rather than exhaustive sandbox validation
- Watch out for queue/alarm side effects, long-running integration or e2e timeouts, and preserved temp-state logs when run-dispatch or browser coverage fails
- Before adding/changing behavior:
reference/anvil-spec.md,reference/anvil-spec-resolutions.md,reference/spec-drift.md,reference/frontend-spec.md - Frontend UI work:
src/client/main.tsx,src/client/app.tsx,src/client/pages/,src/client/components/,src/client/styles/ - Client-side auth and API calls:
src/client/auth/,src/client/lib/,src/client/hooks/,src/client/toast/ - Shared client/server contracts:
src/contracts/,src/lib/ - Worker-internal contracts (DO RPC, dispatch coordination, execution state):
src/worker/contracts/ - API and routing work:
src/worker/index.ts,src/worker/router.ts,src/worker/http.ts,src/worker/hono.ts,src/worker/api/public/,src/worker/api/private/ - Auth and session flow:
src/worker/auth/,src/worker/db/d1/sessions.ts,scripts/seed-bootstrap-invite.ts,src/client/auth/ - Security headers and CSP:
src/worker/router.ts,src/worker/security/,index.htmlThe current CSP allows Google Fonts viafonts.googleapis.comandfonts.gstatic.com. - Data model and persistence:
src/worker/db/d1/,src/worker/db/durable/,drizzle/d1/,drizzle/project-do/,drizzle/run-do/ - Run orchestration and sandboxing:
src/worker/durable/project-do/,src/worker/durable/run-do/,src/worker/dispatch/queue/,src/worker/dispatch/workflows/,src/worker/dispatch/shared/,src/worker/sandbox/ - Validation and codecs:
reference/en-garde.README.md,src/worker/validation.ts,src/lib/codec-errors.ts - Testing and tooling:
tests/worker/,tests/e2e/,tests/integration/,tests/helpers/,package.json,wrangler.jsonc,vite.config.ts,vitest.config.ts,vitest.integration.config.ts,tsconfig.json,tsconfig.scripts.json,tests/tsconfig.json,worker-configuration.d.ts
- "Alarms" under Durable Objects
- "Rules of Durable Objects"
- "Durable Object Stub"
- "Error handling" under Durable Objects
- "SQLite-backed Durable Object Storage" - specifically
.transactionsection - "Remote-procedure call (RPC)" under Workers - "application-defined classes (or objects with custom prototypes) cannot be passed over RPC"
- Exception:
util-en-gardedefined types are plain objects. Those can be safely passed over RPC.
- Exception: