One page for coding agents (Claude Code, Codex, Cursor, …). Humans: see CONTRIBUTING.md.
Cloudhal is an agent-native site platform. Sites are typed graphs (Site → Page → Section; ContentEntry; Snapshot). Agents change sites through an MCP server guarded by a policy engine; every change is snapshotted, diffable, previewable and reversible. TypeScript first; Rust mirrors the contracts and will take over the core later.
pnpm install # once
pnpm check # biome lint, size check, generated-file checks, typecheck, vitest (CI equivalent)
pnpm lint:fix # auto-format
pnpm size:check # size ratchet (docs/engineering/code-structure.md); size:report prints the tables
cargo test --workspace # Rust fixture tests (needs `pnpm --filter @cloudhal/core-contracts bundle` first; `pnpm gen` does it)
cargo clippy --workspace --all-targets -- -D warnings
pnpm gen # JSON Schema → TypeScript + Rust (requires cargo-typify 0.8.0)
pnpm drift # gen + fail if generated files changed
pnpm changeset # add a release note for a published package
pnpm demo:archive # regenerate the starter demo archive (RFC-0039; --check runs in pnpm check)
pnpm design:lint [files…] # Kumo design rules for apps/studio + packages/ui blocks (also a Claude Code hook)
pnpm design:shots -- <routes> # headless screenshots at 1440/390 px; fails on sideways scroll / console errors
pnpm kumo ls|doc <Name>|blocks|add <Block>|ai # Kumo CLI: component list, API docs, blocks, usage guide
pnpm i18n:export / pnpm i18n:import [--locale xx] # translations as nested ICU JSON in .i18n/ (ADR-0034); only en is hand-edited
node scripts/release-bundle.ts build|verify … # RFC-0046 release bundle: deterministic tarball + manifest + unsigned statement; verify checks a downloaded onepackages/core-contracts/schemas/*.schema.json— the only hand-edited contract source.common.schema.jsonholds shared$defs.packages/core-contracts/src/generated/,crates/cloudhal-schema/src/generated/— generated; never edit.packages/core-contracts/fixtures/{valid,invalid}/<schema>/— golden fixtures used by both languages.packages/release-verify— RFC-0046 §11a's published verifier of release bundles (Worker-safe: WebCrypto Ed25519,DecompressionStream,DigestStreamelse@noble/hashes):verifyRelease(§2's six steps, the two-pass tarball,entry-path), stable error codes,PINNED_KEYS(kept equal to the key doc),missingScopes;./attestationis the Node-onlygh attestation verifycheck. Its validators for the two release roots are generated by its owngen:ts(after core-contracts');scripts/release-bundle/verify.tscalls it for signed bundles.packages/storage—SqlDriver/BlobStore+ adapters (sqlite,d1,do-sqlite,fs,r2;SqlBlobStore,LayeredBlobStore,FetcherBlobStoreat the root) + migrations (migrations/*.ts, plain SQL). Conformance suites insrc/testing.ts.packages/core-ts—SiteGraphStore(CAS onrev, snapshots, diff, publish, rollback). Behavioural suite insrc/testing.tsruns on sqlite and Miniflare D1../localopens the offline store;seedStarterSiteseeds a demo../surfacesis RFC-0020's generative UI: the catalog (primitives + block types), eval-free surface checks, bindings, repairs, JSON Patch (patchPage),catalogPrompt;./interopconverts A2UI v0.9 and json-render to and from surfaces;./designis RFC-0021's DESIGN.md lint and guidance../fieldsis RFC-0041's typed custom fields (definition and value checks,resolveFields,formatFieldValue, the agents' JSON Schema, the conversion table);src/fields-operations/runs rename/retype/localize/remove.search.tsis RFC-0019's FTS5 site search (index written with the publish batch; articles, pages, terms and authors, RFC-0029);search-provider.tsis RFC-0029'sSearchProvider(fts5,memory,withFallback), set withSiteGraphStoreOptions.search;search-ai.tsis the experimental Cloudflare AI Search provider (hybridProvider,searchProviderFromEnv,cloudhal.jsonsearch, docs/guides/search.md).packages/schedules— RFC-0008: time model (onlytemporal-polyfill/implementation,nowinjected, neverTemporal.Now), RRULE subset, release model (rl1:), automation/run state, taint and budgets,runDue;./sqlisSqlScheduleStore,./enginethe Temporal-free part core-ts and the site Worker use. Worker-safe; conformance suites insrc/testing.tson sqlite and Miniflare D1.packages/astro-renderer—GraphSource(live/snapshot),cloudhal()integration (virtual config + dev route-cache watcher),.astrosection components (Surface.astrofor RFC-0020 surfaces;./surfacesrenders them,./kithas the table of contents and reading time;./fieldsandEntryFields.astrorender typed field values, RFC-0041)../status-pageis RFC-0043's status pages (statusPage(state, input) → Response, served by the Site Worker entry before Astro and bycloudhal devat/__cloudhal/status/<state>; the optionalstatusPagetheme hook; the catalog insrc/status-strings/where onlyen.tsis hand-edited and the other languages come frompnpm i18n:import). Routes live in starters, not here.packages/cli— thecloudhalCLI (init,dev,studio,mcp,deploy,push,pull,export,import, …; every command is insrc/cli/help.ts, whichcloudhal --helpprints and the docs site's CLI reference is generated from); TS bin run by Node 24 type stripping.src/cli.tsparses flags and dispatches throughCOMMANDS, built fromsrc/cli/commands-{local,remote,content,ops,site}.ts(a new command is one handler plus one line insrc/cli/help.ts).src/archive/is RFC-0039's.cloudhalcontainer (tar, gzip, manifest, limits), read and written bysrc/commands/export.tsandimport*.ts(archive-bundle.ts,archive-reassemble.ts); the starter demo archivetest/fixtures/site-archives/starter-blog.cloudhalis written bypnpm demo:archive, andpnpm checkfails when it is stale. E2E test spawns the bin (Astro disables dev middleware underVITEST). Cloudflare tests run both Workers on workerd via wrangler'screateTestHarness; test files run serially (shared starter caches).src/commands/deploy/release/is RFC-0046 §11b'sdeploy --release: the signed key and yank lists (trust.ts, the yank memory in~/.cloudhal/release-trust.json), the release lookup and download, the theme check against the CLI's starter (theme-check.ts; recheck its build-input list on every Astro or Vite major), the build keys ofsrc/config-build-keys.ts, extraction and theno_bundlewrangler configs; tests use@cloudhal/release-verify's synthetic bundle (test/fixtures/release-deploy.ts), never the network.packages/cloudflare— the admin Worker (createAdminWorker);./playgroundis RFC-0036's playground Worker (createPlaygroundWorker, thePlaygroundSiteDurable Object, signed session cookies). Generated entries must import@cloudhal/cloudflare/workerd-preludefirst (ADR-0012).packages/agent-runtime/packages/mcp-server— policy, skills, audit; MCP tools and transports (ADR-0011).@cloudhal/mcp-server/renderis RFC-0021'srender_pagecore: a small DevTools client, the in-page checks and limits; the CLI drives local Chrome with it (render-chrome.ts), the admin Worker Browser Rendering (browser-render.ts).starters/blog— the theme boundary:src/pages/[...path].astro,src/layouts,src/theme.ts.packages/api— Hono app (createApi), Better Auth (src/auth.ts), error mapping; shared suitesrc/testing.tson sqlite + D1. Build chat isbuild-turn.ts, its model evalbuild-eval.ts;design-pass.tsis RFC-0021 21c (Build chat looks at its render, the eval's vision-judged score).packages/ui—export * from "@cloudflare/kumo"+ blocks;styles.cssis the only Tailwind entry for admin UIs. Heavy kits have lazy subpaths:./charts(ECharts),./editor(the Tiptap markdown body editor, RFC-0014).packages/messages— RFC-0032 / ADR-0032: the ICU MessageFormat 1 subset of Studio's catalogs (plural,select,selectordinal,number)../compile(build-time only: the Vite plugin./vite, the type generator, tests;bundle-budget.tskeeps it out of every chunk) and./format(Worker-safe runtime, cachedIntl.PluralRules/NumberFormat), which is also Studio'sIntllayer:formatNumber,formatDate,formatRelative,formatListand cachednumberFormat/dateTimeFormat/relativeTimeFormat/displayNames/collator— screens and blocks never buildIntlformatters themselves (linti18n-format). Catalogs compile to data, never code.patches/—pnpm patchfiles (patchedDependenciesinpnpm-workspace.yaml).@[email protected]is generated bynode scripts/kumo-patch.ts: the hand-written changes inpatches/kumo/*.patch(tooltip-timestamp.patchaddsTimeseriesChart'stooltipTimestampFormat, DESIGN.md §2v "Kumo locale") plus every physical utility in Kumo'sdistmade logical (ADR-0033 A1). Never edit it; on a Kumo upgrade rename it and thepatchedDependenciesentry, re-run the script,pnpm install, re-check the gaps indocs/design/kumo-upstream-notes.md.packages/ui/test/kumo-rtl.test.tsxfails when the installed Kumo is not patched.[email protected]is hand-written against Miniflare'sdist/src/index.js: it backports Miniflare 5's sync binding-proxy fix (workers-sdk#15552) that the D1 harness needs and closes that proxy pool's idle sockets after 1 s (keepAliveTimeout/keepAliveMaxTimeout1000, workers-sdk#16445 A) (#190). On a Miniflare bump the first signal ispnpm install --frozen-lockfilefailing on the now-unused patch; then follow the upgrade recipe in ADR-0012 (addendum 2026-11-10: drop the patch if the release carries #15552, else re-apply both changes withpnpm patch/pnpm patch-commitand rename the entry).packages/storage/test/miniflare-patch.test.tsfails when the installed Miniflare lacks the fix.apps/studio— SPA; routes insrc/router.tsx, screens insrc/routes, typed API clientsrc/lib/api.ts(a barrel oversrc/lib/api/<area>.ts), i18n insrc/i18n(messages are ICU MF1, compiled at build time, with parameter types generated intosrc/i18n/generated/params.tsbypnpm gen— never edit it; languages registered inlocales.ts; add keys toen.tsand every<locale>.ts, or to a lazy namespacesrc/i18n/lazy/<ns>.<locale>.ts(the list and how to add one:src/i18n/namespaces.ts; DESIGN.md §1); only the active language of the core catalogs loads,tr.tsthroughloadLocale, and each other language's lazy-namespace loaders come with it from the generatedsrc/i18n/loaders/<locale>.ts; Arabic and Persian format with Western digits through the registry'sintltag).scripts/release-bundle.ts+scripts/release-bundle/— RFC-0046's builder and verifier (read thedeploy --dry-runoutput, manifest, sorted fixed-header tar, statement, limits, secret scan);scripts/publish-ci.tspublishes through the plannerscripts/release-publish-plan.ts(tag → draft → assets → npm → re-check → publish, resumable, dry run). Workflows:release.yml(bundle→bundle-sign→publish) and the reusablebundle-build.yml, whose path consumers pin ingh attestation verify --signer-workflow(never rename it). Tests inpackages/cli/test/release-*.test.ts.docs/adr/decisions (with sources) ·docs/rfcs/proposals ·docs/design/DESIGN.mdthe binding admin design system (Kumo)..agents/skills/— skills for you:contract-change,add-capability,write-rfc,studio-ui,translate, and Cloudflare's officialkumo-design(installed fromcloudflare/kumo, tracked inskills-lock.json; update withnpx skills update). Any admin-UI change loadskumo-design+studio-uiand followsdocs/design/DESIGN.md.
- Never edit generated files. Change the schema, run
pnpm gen, commit both. - Schema subset (so TS and Rust generators agree): discriminated unions =
oneOfwith inline variants and a one-value enum discriminator,kind: {"type":"string","enum":["x"]}unless the domain says otherwise (field definitions usetype, ADR-0043); nodefault; no$dynamicRef/unevaluated*/dependentSchemas; cross-file$refonly tocommon.schema.json#/$defs/*or another root schema's top level;additionalProperties: falseon every object except declared maps. - Every root type has
schemaVersion(enum[1]); mutable nodes haverev. New fields are optional unless an RFC says otherwise. - No interactive DB transactions anywhere (D1 has none); only atomic
batch(). Composite writes guard later statements on the earlier CAS (match on the new contenthash, notrev + 1). - Worker-safe imports: code reachable from a Worker never imports Node-only modules at top level (
better-sqlite3,node:fshelpers,@cloudhal/core-contracts/bundle,@cloudhal/api/auth/node). Use the package subpaths. - Runtime validation uses the precompiled validators (
assertValid/isValid), nevernew Ajv()(Workers forbid eval). - Admin UIs import UI primitives from
@cloudhal/uionly (@cloudflare/kumo/@base-ui/reactin apps are lint errors) and are composed from Kumo components and blocks: every screen is aPageLayoutwith a registered template, sections areSectionCards, no hand-made cards, headers, containers or raw HTML controls. Look APIs up withpnpm kumo doc <Name>before writing markup; if Kumo lacks something, add a block topackages/ui/src/blocks, never a one-off in a screen.pnpm design:lintmust pass (it also runs after every agent edit via.claude/settings.json). - Skills (
SKILL.md) use only the six spec fields; Cloudhal data goes inmetadataascloudhal.*string keys. - Secrets never in code, fixtures or docs. Test values live in
.env.example. - UI strings go through
useT()with keys inen.ts(only English is edited by hand; every other<locale>.tsis generated bypnpm i18n:import, ADR-0034) and every language's<locale>.ts(or every file of a lazy namespace, read withuseT("<ns>"); languages are registered inapps/studio/src/i18n/locales.ts); no hard-coded words inapps/studio/srcorpackages/ui/src— visible text,aria-label/title/placeholder/alt, toasts, banners, label tables and block defaults (blocks take every word as a prop, no English fallback; API errors throughapiErrorText). Enforced by thei18n,i18n-plural,i18n-concatandi18n-formatrules (andrtl-logicalfor physical CSS utilities, ADR-0033) ofpnpm design:lint(apps/studio/scripts/i18n-lint.ts, run inpnpm checkbytest/design-rules.test.tsand on every agent edit); legitimate literals go in itsALLOWEDlist or carry ani18n-ignore: <reason>comment. - Conventional commits,
git commit -s(DCO). Changeset for published-package changes. - Size ratchet (docs/engineering/code-structure.md): files ≤ 800 lines (soft 400;
.astro500, tests 1500; i18n dictionaries are data and exempt — the Studio bundle budget guards them), functions ≤ 150 lines, depth ≤ 5, ≤ 4 positional params (use an options object beyond 3); one responsibility per module, dispatch tables instead of giant switches.pnpm size:check(inpnpm check) fails on a new violation or when a file inscripts/size-baseline.jsongrows — put new code in a new module next to it; after shrinking one, runnode scripts/size-check.ts --update. - Regexes over external input (request paths and bodies, agent or editor markdown, uploads, fetched feeds) must be linear (#168, #174): no unanchored or end-anchored quantified runs (
/-+$/,/\s+$/,/\/+$/), no lazy scans that rescan to the end when the closer is missing (/<x>[\s\S]*?<\/x>/,/<[^>]*>/), no adjacent quantifiers over overlapping classes (\s+[^:]+,\s*(.*)). UsetrimChar/trimEndChar(@cloudhal/core-ts/names),trimEnd(),indexOf/lastIndexOfscanners, and check lengths before patterns. A rewrite gets an equivalence test against the old regex and a 100k-character adversarial case.
Node 24 · pnpm 12 · TypeScript 7.0 · Biome 2.6 · Vitest 5 · Turborepo 2.11 · Rust 2024 edition · json-schema-to-typescript 16.0.0 · cargo-typify 0.8.0 · ajv 8.20 · jsonschema 0.60 · better-sqlite3 13 · miniflare 4.20260730 (last stable 4.x, patched for #190; its sharp overridden to 0.35.5; see ADR-0012) · wrangler 4.158 · @astrojs/cloudflare 14.4 · Astro 7.4 · Tailwind 4.3 · marked 18 · Hono 4.13 · Better Auth 1.7.12 · Kumo 2.14 · React 19.3 · TanStack Router 1.170 / Query 5.104 · Vite 8.3.
Read docs/adr/README.md and the relevant ADR; each ADR links its primary sources. If the change alters a contract or a public interface, write an RFC first (.agents/skills/write-rfc).
Turborepo configuration, task behavior, and CLI commands can vary between installed versions and may differ from your training data. Resolve the turbo package from this file's directory or relevant workspace; in monorepos, it may not be visible from the repository root. For example, run node -p "require.resolve('turbo/package.json')" from a workspace that depends on turbo.
Read docs/README.md inside that installed package first, then read the relevant pages from its docs/ directory before changing Turborepo configuration or commands. Heed deprecation notices. These bundled docs match the installed package version and are available without network access.
This block is written and re-added by turbo before repository-scoped commands when an AI agent is detected. In the Turborepo source repository, its template is defined in crates/turborepo-cli/src/cli/agent_guidance.rs. Removing the managed block while updates are enabled means a later qualifying invocation will add it again. Set "agentGuidance": true in the root turbo.json or turbo.jsonc to opt in; this does not remove an existing block. Keep the block committed with your work to avoid an uncommitted change on the next agent invocation.