Imported from voorhoede/head-start (
AGENTS.md). Install upstream withnpx skills add voorhoede/head-start. Copyright stays with the author.
AGENTS.md
Instructions for AI coding agents working in this repository. Human contributors should start with the README and docs/.
Project overview
Head Start is a starter kit by De Voorhoede for building content-driven websites (not web apps) on top of a headless stack:
- Framework: Astro (v5,
output: 'server'via Cloudflare adapter). - CMS: DatoCMS — content is fetched via GraphQL; schema is managed through migrations in
config/datocms/migrations/. - Hosting: Cloudflare Workers with static assets. Deployed via Workers Builds (
npm run cloudflare:build+wrangler deploy). Local preview useswrangler dev. - Philosophy: no default JS framework, no default styling, progressively enhanced, fully accessible, highly performant. See README › Philosophy before suggesting new dependencies.
The repo is a small monorepo: the root is the Astro app; config/datocms/ is an npm workspace for CMS-side tooling.
Required reading for agents
Skim these before making non-trivial changes:
docs/project-structure.md— where things live.docs/blocks-and-components.md— the Block vs. Component distinction is load-bearing.docs/cms-data-loading.md— GraphQL queries/fragments are colocated with pages/blocks.docs/decision-log/— read the relevant entry before challenging an architectural choice, and add a new entry when making one.
Agent skills
Project-specific skills live in .agents/skills/. Each skill is a SKILL.md file whose frontmatter description declares when to load it — agents discover them on demand. Browse the directory and load the relevant skill before working in that area.
MCP servers
MCP servers are configured in .mcp.json at the repo root and picked up automatically by Claude Code, Cursor, and other compatible agents. Read the file for the current list — each entry's command/url is self-documenting.
The hosted
datocmsserver authenticates via OAuth in your browser the first time you use it — no token in.mcp.json. For visual QA viachrome-devtools, runnpm run devfirst and point the agent at http://localhost:4323. If you ever add a stdio server that needs a secret, read it from your shell via${VAR}interpolation — never hardcode.
Environment
- Node.js version is pinned in .node-version (currently
v25). Use the matching version. - Package manager:
npm(see package.jsonworkspaces). Do not introducepnpm/yarnlockfiles. - Copy .env.example to
.envand fill it in. Most scripts requireDATOCMS_READONLY_API_TOKEN,DATOCMS_API_TOKEN, andHEAD_START_PREVIEW_SECRET. Never commit real values. - A
.dev.varsfile is auto-created bynpm run prep:cloudflare-envforwrangler.
Build, run, test
Run everything from the repo root:
| Command | Purpose |
|---|---|
npm install |
Install deps (also runs husky hooks install). |
npm run dev |
Start Astro dev server at http://localhost:4323 plus GraphQL/icon/translation watchers in parallel. |
npm run build |
Runs prep (clean, download CMS data, generate types, build icon sprite) then astro build. |
npm run preview |
Serve the built dist/ with wrangler dev (closest to production). |
npm run deploy |
Deploy to Cloudflare Workers with wrangler deploy. |
npm run lint |
Runs astro check + ESLint + html-validate over dist/. lint:html requires a build first. |
npm run test / npm run test:unit |
Vitest unit tests (*.test.ts). Depends on prep. |
npm run analyze |
Build with Sonda bundle analyzer (writes to reports/). |
npm run create[:block|:component|:page|:api] |
Scaffold new files via Plop; prefer this over hand-creating boilerplate. |
npm run cms:* |
DatoCMS environment & migration management (see docs/migrations.md). These hit the live CMS — use a non-primary environment. |
Notes:
astro checkandlint:eslintare the fast feedback loop;lint:htmlneedsnpm run buildfirst or it has nothing to validate.- Dev port
4323is intentional (see astro.config.ts), not the Astro default4321. - Tests currently cover a subset of the codebase; add a
*.test.tsnext to the code you change when practical.
Code style
Enforced by eslint.config.mjs — run npm run lint:eslint -- --fix before committing (a nano-staged hook does this on staged *.{astro,js,ts} files).
- 2-space indent, single quotes, semicolons, spaces inside
{ }. - Unused vars/args must be prefixed with
_. - TypeScript everywhere;
.astrocomponents for UI. No non-null assertion restriction, but prefer narrowing. - Filenames:
- Blocks and components:
PascalCase/directory with matchingPascalCase.astro, plus optional*.fragment.graphql,*.client.ts,*.test.ts,*.preview.txt|png. - Library/helpers/scripts:
kebab-case.ts. - Pages follow Astro file-system routing under
src/pages/[locale]/.
- Blocks and components:
- Don't edit generated files:
src/lib/datocms/types.ts,src/assets/icon-sprite.svg, anything under.astro/,dist/, orfunctions/(all ignored by ESLint). - Prefer Astro components and web standards over adding a UI framework. If a component genuinely needs interactivity, add a sibling
*.client.ts— see existing blocks for the pattern.
GraphQL & CMS
- Query files (
*.query.graphql) live next to the page that uses them; fragment files (*.fragment.graphql) live next to their block/component. - Types are generated by
graphql-codegenvianpm run prep:datocms-types(also runs automatically indev/prebuild). Re-run it after editing a.graphqlfile. - CMS schema changes go through migrations: scaffold with
npm run cms:migrations:generate, test against a fresh environment withcms:environments:create, thencms:environments:promote. Seedocs/migrations.md. - Adding a new Block: create the block in DatoCMS (model API key = snake_case of the component name), scaffold the frontend with
npm run create:block, add the fragment, and register it insrc/blocks/Blocks.astro.
Security considerations
- Treat everything in
.env/.dev.varsas a secret. Don't print tokens in logs, commit messages, or error output. DATOCMS_API_TOKENhas full CMS write access; scripts inscripts/use it. Avoid invoking them against the primary DatoCMS environment unless explicitly asked.HEAD_START_PREVIEW_SECRETgates preview mode; rotate it if leaked. Seedocs/preview-mode.mdand the preview-ssr-branch decision.- All user-facing output must remain XSS-safe — prefer Astro's default escaping and the structured-text renderer over
set:htmlunless content is already sanitised CMS output. - Don't disable ESLint rules, the a11y plugin, or
astro checkto get a build through. Fix the underlying issue.
Guardrails
Hard rules. If you're unsure whether an action is covered, stop and ask the user.
Destructive actions — ask first
Never run these without explicit, in-context confirmation from the user (a prior "yes" from a different task does not carry over):
rm -rf,find … -delete, or any recursive delete outsidenode_modules/,dist/,.astro/,reports/, orfunctions/(these are safe to wipe).git push --force/--force-with-lease,git reset --hardon a branch that has been pushed,git clean -fdx, rewriting or amending commits that are already on the remote, deleting branches or tags (local or remote).git checkout ./git restoreover uncommitted work you didn't create this session — it may be the user's in-progress changes.npm run cms:environments:destroy,cms:environments:promote, or anycms:*script targeting the primary DatoCMS environment. Always operate on a fresh non-primary environment unless the user explicitly names the primary one.- Anything that writes to DatoCMS production content, uploads, or access tokens (including
cms:upload-block-previewsagainst primary). - Rotating, printing, or committing secrets from
.env,.dev.vars, orwrangler— even redacted. - Touching Cloudflare Pages settings, deploy hooks, DNS, or repository secrets.
npm install <new-dep>for a runtime dependency, a UI framework, or a styling system — these cut against the project philosophy. Dev-only tooling with a clear justification is fine to propose, but confirm before installing.--no-verifyon commits, disabling ESLint/astro check, or editing generated files to bypass errors (see Code style).
Prefer reversible alternatives: work on a feature branch, use a scratch DatoCMS environment, stage changes before deleting, and keep commits small.
Loop & runaway prevention
If you find yourself repeating the same action, stop and re-plan instead of retrying harder:
- Three-strike rule. If the same command, edit, or search fails or produces the same result three times, stop. Summarise what you tried and ask the user — don't escalate (e.g. from
rmtorm -rf, or from a targeted fix to a rewrite) to force progress. - Don't fight the tooling. If
astro check, ESLint, orhtml-validatekeeps flagging the same issue, read the rule and fix the root cause. Do not add// eslint-disable,@ts-ignore, orset:htmljust to get green. - Don't re-run long tasks speculatively.
npm run build,npm run prep, andcms:*scripts are slow and hit the network/CMS. Run them once, read the output, then act — don't loop builds waiting for a different result. - Watchers are already running in
dev. Don't spawn parallelastro dev,graphql-codegen, orchokidarprocesses;npm run devcovers them viarun-p. - Bounded searches. Prefer
grep_search/file_searchwith specific patterns over repeatedsemantic_searchpasses once you have enough context to act. - Fail loudly, not silently. If a step genuinely can't proceed (missing env var, CMS unreachable, schema drift), surface it to the user with the exact error — don't work around it by mocking data or skipping the step.
- Stay on-task. Don't opportunistically refactor, reformat, or "tidy up" files you weren't asked to change; it expands the diff and makes review harder.
Commit & PR guidelines
- Keep PRs focused; discuss larger changes in an issue first (CONTRIBUTING).
- PRs use
.github/PULL_REQUEST_TEMPLATE.md— fill it in. - CI runs lint + HTML validation + tests and needs
DATOCMS_API_TOKEN/DATOCMS_READONLY_API_TOKENrepository secrets to be set. - Record meaningful architectural decisions in
docs/decision-log/using the existingYYYY-MM-DD-slug.mdpattern. - Update
CHANGELOG.mdfor user-visible changes.
Nuances & known rough edges
Flagging these so agents don't faithfully replicate them:
- Docs have typos.
docs/getting-started.mdcontains "Prequisites" (should be "Prerequisites") and "You're project is now deployed" (should be "Your"). README › Commands describeslintas "valide HTML output" (should be "validate"). Fix opportunistically when editing those files, but don't open a PR solely for typo churn. astro -- --helphint is misleading. The README suggestsastro -- --help; the working invocations arenpm run astro -- --helpornpx astro --help.- Getting-started seed gap. Issue #27 — the project still requires manual creation of SEO / Home / 404 content in DatoCMS after running migrations or the build will fail. Mention this when guiding a new setup.
docs/testing.mdis out of date (it says e2e is "planned"). Check the repo for whatever is actually wired up before asserting test coverage to the user.- Port 4323 vs 4321. Several third-party docs/snippets assume Astro's default
4321; Head Start uses4323. Don't "fix" this — it's intentional. lint:htmlsilently passes on an emptydist/. It only validates files produced byastro build, so order matters in CI and local runs.config/datocmsis a workspace. Runningnpm installat the root installs it; don'tcd config/datocms && npm installseparately unless you know why.
When in doubt
Prefer small, reversible changes. Read the nearest doc in docs/ and the relevant decision-log entry, then ask the user before making architectural shifts (introducing a UI framework, changing the CMS, adding a global styling system, etc.) — these cut against the project's stated philosophy.