···11+### Formatter settings (from `biome.jsonc`)
22+33+- **Indent style:** Tabs (not spaces) for code files.
44+- **Indent width:** 2.
55+- **Line width:** 100 characters.
66+- **Trailing commas:** `all` — always include trailing commas.
77+- **Quote style:** Single quotes (`'`).
88+- **Semicolons:** Always.
99+- **Expand:** `auto` — Biome auto-decides when to expand arrays/objects.
1010+1111+---
1212+1313+## 2. Import Conventions
1414+1515+### Always use the `node:` protocol for Node.js builtins
1616+1717+Enforced as an **error** via Biome's `useNodejsImportProtocol` rule. Every import of a Node built-in must use the `node:` prefix:
1818+1919+```ts
2020+// ✅ Correct
2121+import fs from "node:fs";
2222+import path from "node:path";
2323+2424+// ❌ Wrong — will fail lint
2525+import fs from "fs";
2626+import path from "path";
2727+```
2828+2929+### Enforce `import type` for type-only imports
3030+3131+Enforced as an **error** via Biome's `useImportType` rule. If an import is only used as a type (not at runtime), it must use `import type`:
3232+3333+```ts
3434+// ✅ Correct
3535+import type { AstroConfig } from "../types/public/config.js";
3636+import { getFileInfo } from "../vite-plugin-utils/index.js";
3737+3838+// ❌ Wrong — pulls runtime code for a type-only import
3939+import { AstroConfig } from "../types/public/config.js";
4040+```
4141+4242+### Import organization
4343+4444+Biome's `organizeImports` assist is set to `"on"`, which auto-sorts and groups imports.
4545+4646+---
4747+4848+## 3. Restricted Use of Node.js APIs
4949+5050+This is a foundational architectural constraint. Astro code may run in non-Node environments like **Bun** or **Cloudflare Workers**, so Node.js API usage is strictly limited to specific areas of the codebase.
5151+5252+### Rules
5353+5454+- **Runtime-agnostic code** (code that shouldn't use Node.js APIs) should be placed inside folders or files called `runtime` (`runtime/` or `runtime.ts`).
5555+- **Vite plugin implementations** may use Node.js APIs, but if a Vite plugin returns a **virtual module**, that virtual module cannot use Node.js APIs.
5656+- Prefer **`URL` and file URL APIs** (`new URL(...)`, `import.meta.url`) over `node:path` string manipulation where possible. The codebase favors URL-based path handling for cross-runtime compatibility.
5757+5858+---
5959+6060+## 4. Function Signature Conventions
6161+6262+### Options bag pattern
6363+6464+Functions with more than two parameters are collapsed into an **options object**. This is a pervasive pattern throughout the codebase:
6565+6666+```ts
6767+// ✅ Correct — options bag with a typed interface
6868+interface CompileAstroOption {
6969+ compileProps: CompileProps;
7070+ astroFileToCompileMetadata: Map<string, CompileMetadata>;
7171+ logger: Logger;
7272+}
7373+7474+export async function compileAstro({
7575+ compileProps,
7676+ astroFileToCompileMetadata,
7777+ logger,
7878+}: CompileAstroOption): Promise<CompileAstroResult> { ... }
7979+```
8080+8181+```ts
8282+// ✅ Also correct — inline destructuring for simpler cases
8383+export async function generateHydrateScript(
8484+ scriptOptions: HydrateScriptOptions,
8585+ metadata: Required<AstroComponentMetadata>,
8686+): Promise<SSRElement> { ... }
8787+```
8888+8989+### Public APIs should default to `async`
9090+9191+Public-facing functions default to being `async`, even if they don't strictly need to be today. This future-proofs the API against needing to introduce async operations later without a breaking change.
9292+9393+---
9494+9595+## 5. Error Handling Convention
9696+9797+Astro has a **unified, structured error system**. All errors go through a central `AstroError` class and are defined in a single data file.
9898+9999+### Never throw generic `Error` — use `AstroError`
100100+101101+```ts
102102+// ❌ Wrong
103103+throw new Error("Something went wrong");
104104+105105+// ✅ Correct
106106+throw new AstroError({
107107+ ...AstroErrorData.NoMatchingImport,
108108+ message: AstroErrorData.NoMatchingImport.message(metadata.displayName),
109109+});
110110+```
111111+112112+### Error definitions live in `errors-data.ts`
113113+114114+All error data is centralized in `packages/astro/src/core/errors/errors-data.ts`. Each error follows a strict shape:
115115+116116+- **`name`** — A permanent, unique PascalCase identifier (never changed once published).
117117+- **`title`** — A brief user-facing summary.
118118+- **`message`** — Can be a static string or a function for dynamic context. Describes what happened and what action to take.
119119+- **`hint`** — Optional. Additional context, links to docs, or common causes.
120120+121121+### Error writing guidelines (from `errors/README.md`)
122122+123123+- Write from the **user's perspective**, not Astro internals ("You are missing..." not "Astro cannot find...").
124124+- **Avoid cutesy language** — no "Oops!" The developer may be frustrated.
125125+- Keep messages **skimmable** — short, clear sentences.
126126+- Don't prefix with "Error:" or "Hint:" — the UI already adds labels.
127127+- **Names are permanent** — never rename, so users can always search for them.
128128+- Dynamic messages use a function shape: `message: (param) => \`...\``.
129129+- Avoid complex logic inside error definitions.
130130+- Errors are **grouped by domain** (Content Collections, Images, Routing, etc.) and display on the error reference page in definition order.
131131+132132+---
133133+134134+## 6. Code Architecture Principles
135135+136136+### Decouple business logic from infrastructure
137137+138138+The CONTRIBUTING guide explicitly calls this out as a core principle:
139139+140140+- **Infrastructure:** Code that depends on external systems (DB calls, file system, randomness, etc.) or requires a special environment to run.
141141+- **Business logic (domain/core):** Pure logic that's easy to run from anywhere.
142142+143143+In practice this means: avoid side effects, make external dependencies explicit, and **pass more things as arguments** (dependency injection style).
144144+145145+### Test all implementations
146146+147147+If an infrastructure implementation is just a thin wrapper around an npm package, you may skip testing it and trust the package's own tests. But all business logic should be tested.
148148+149149+---
150150+151151+## 8. Code Cleanliness Rules
152152+153153+All enforced at the **error** level:
154154+155155+| Rule | Effect |
156156+| ---------------------------- | ----------------------------------------------------- |
157157+| `noUnusedVariables` | No unused variables (with `ignoreRestSiblings: true`) |
158158+| `noUnusedFunctionParameters` | No unused function parameters |
159159+| `noUnusedImports` | No unused imports |
160160+| `useNodejsImportProtocol` | Must use `node:` prefix for Node builtins |
161161+| `useImportType` | Must use `import type` for type-only imports |
162162+163163+- Node.js engine requirement: `>=22.12.0`.
164164+- Package manager: `pnpm` (enforced via `only-allow`).
165165+166166+---
167167+168168+## 12. Code Style
169169+170170+The observed patterns from the source code include:
171171+172172+- **Explicit return types** on exported functions.
173173+- **Interface-first design** — define an interface for options bags, then destructure in the function signature.
174174+- **JSDoc comments** on exported functions and complex internal functions, using `/** */` style. Avoid excessive or self-documenting comments.
175175+- **Explicit `.ts` extensions in import paths** — even for TypeScript files, imports use literal extensions.
176176+- **Avoid barrel files via `index.ts`** — packages organize re-exports through index files.
177177+- **`const` by default** — `let` only when reassignment is needed.
178178+179179+---
180180+181181+## Summary of Key Rules
182182+183183+| Convention | Details |
184184+| --------------- | ------------------------------------------------------------------------------------------ |
185185+| Formatter | Biome: tabs, single quotes, semicolons always, trailing commas always, 100-char line width |
186186+| Node builtins | Always `node:` prefix (error-level lint rule) |
187187+| Type imports | Always `import type` for type-only (error-level lint rule) |
188188+| Node.js APIs | Restricted to non-`runtime/` code; virtual modules must be runtime-agnostic |
189189+| Function params | >2 params → options bag with a typed interface |
190190+| Public APIs | Default to `async` |
191191+| Error handling | Always `AstroError` with centralized error data; never generic `Error` |
192192+| `console.log` | Warned; use `console.info`/`warn`/`error`/`debug` instead |
193193+| Unused code | Unused vars, params, and imports are errors |
194194+| Path handling | Prefer `URL` / file URLs over `node:path` string manipulation |
195195+| Testing | Vitest via `astro-scripts`; `bgproc` for dev servers; `agent-browser` for HMR |
196196+| Scripting | `node -e`, not Python |
197197+| Business logic | Decouple from infrastructure; make dependencies explicit via arguments |