AI-Native Updated 2026-09 8 min read View as Markdown

Writing an AGENTS.md for JavaScript

JavaScript needs a longer instructions file than the typed languages, and most of the extra length should be one list: what the platform now does that a package used to.

AGENTS.md is a Markdown file in your repository root that coding agents read before they start. Claude Code reads CLAUDE.md; most other tools read AGENTS.md. Write one and symlink the other:

shell
ln -s AGENTS.md CLAUDE.md

JavaScript is the language where this file does the most work, for two reasons. There is no compiler rejecting anything, and the training data spans fifteen years of a language that changed enormously in that time. So the model's defaults are drawn from an average of every JavaScript era at once, and your file is what pulls it to the current one.

The section other languages do not need#

The highest-value block in a JavaScript AGENTS.md is a list of things the platform now does that a package used to do. Generated code reaches for the 2018 dependency by default — not because it is wrong, but because there is vastly more of it in the training data — and those dependencies accumulate silently until you have a package.json full of things Node ships natively.

markdown
## Use the platform, not a package
fetch            not axios / node-fetch
crypto.randomUUID()  not uuid
node --env-file  not dotenv
structuredClone  not JSON.parse(JSON.stringify(x))
fs.rm / fs.mkdir with { recursive: true }   not rimraf / mkdirp
Intl.DateTimeFormat, Temporal   not moment
Object.groupBy, Array.at, toSorted, ?., ??   not lodash
util.styleText   not chalk (for simple cases)
fs.glob          not glob (for simple cases)
import.meta.dirname   not __dirname shims

Ten lines, and they prevent a category of drift that is otherwise invisible until a dependency audit. Every one of those is a real supply-chain surface you did not need.

What else belongs#

Commands. Unguessable, used every turn.

The async rules. This is where generated JavaScript actually breaks, and unlike the platform list above, some of it is enforceable — so enforce what you can and state the rest.

Module system and Node version. ESM or CJS, and which Node. Generated code mixes require and import in the same file if you do not say.

Boundaries. Which directory imports which.

Landmines.

The target

Under 100 lines, of which about fifteen are the platform list. Everything enforceable should be a lint rule instead — see below.

Enforce first, then write#

Even without TypeScript you can get type-aware linting, and it catches the bugs that matter most here.

jsconfig.json
{ "compilerOptions": { "checkJs": true, "strict": true, "noEmit": true },
  "include": ["src/**/*.js"] }
eslint.config.js
import js from "@eslint/js";
import ts from "typescript-eslint";

export default [
  js.configs.recommended,
  ...ts.configs.recommendedTypeChecked,
  {
    languageOptions: { parserOptions: { projectService: true } },
    rules: {
      "@typescript-eslint/no-floating-promises": "error",
      "@typescript-eslint/no-misused-promises": "error",
      "@typescript-eslint/await-thenable": "error",
      "require-atomic-updates": "error",
      "no-param-reassign": ["error", { props: true }],
      "no-await-in-loop": "warn",
      "eqeqeq": ["error", "smart"],
    },
  },
];

no-floating-promises alone removes the most common bug in generated JavaScript, and it is worth adding jsconfig.json purely to unlock it. Three lines of your instructions file become mandatory instead of advisory.

The template#

AGENTS.md
Node 22, ESM only (package.json has "type": "module"), pnpm.
jsconfig.json has checkJs: true — keep `pnpm typecheck` passing.

## Commands
- Check:    `pnpm check`  (tsc --noEmit, eslint, vitest run). Must pass.
- Test:     `pnpm vitest run`
- One test: `pnpm vitest run src/thing.test.js`
- Fix:      `pnpm eslint . --fix`

## Layout
- `src/core/`     pure logic. No fetch, no fs, no process.env.
- `src/adapters/` everything touching the outside world.
- `src/routes/`   http handlers. Parse, call core, respond.
- Tests beside the file: `thing.js` -> `thing.test.js`.

## Async (this is where things actually break)
- await every promise. No floating promises.
- Promise.all for independent work. A sequential await loop needs a comment
  saying why the sequencing is required.
- Promise.allSettled when partial success is acceptable.
- Never pass an async function to forEach — it does not await.
- Every fetch in a React effect aborts on cleanup (AbortController).
- Nothing synchronous and slow in a request handler: no readFileSync, no
  large JSON.parse, no crypto *Sync.

## Values
- `??` for defaults, never `||` — 0 and "" are valid values.
- `===` always.
- Do not mutate arguments, props or state. structuredClone for deep copies.
- Sort numbers with a comparator: toSorted((a, b) => a - b).

## Use the platform, not a package
fetch not axios · crypto.randomUUID() not uuid · node --env-file not dotenv
structuredClone not JSON round-tripping · fs.rm not rimraf
Intl/Temporal not moment · Object.groupBy / toSorted / ?. / ?? not lodash

New dependencies need a sentence in the PR description justifying them.

## Landmines
- `src/legacy/` is CommonJS and is loaded by the worker process. Do not
  convert it to ESM.
- `src/routes/webhook.js` verifies a signature over the RAW body. Do not
  add body parsing middleware in front of it.

That webhook line is a good example of what a landmine looks like: invisible in the code, catastrophic if changed, and something a model would "helpfully" break while tidying middleware.

Common questions#

Should I just migrate to TypeScript instead?#

If the codebase is small or new, probably. If it is large, checkJs plus JSDoc types gets you the type-aware lint rules and most of the editor benefit without a migration project — and you can convert file by file later if it proves worth it.

Why does generated JavaScript keep reaching for old packages?#

Because a decade of JavaScript in the training data uses them, and the replacements are recent. It is rarely harmful in itself; the cost is accumulation — every unnecessary dependency is a supply-chain surface and a future upgrade. The platform list is a cheap fix.

CommonJS or ESM?#

ESM for anything new, and say so explicitly in the file, because generated code will otherwise mix the two. If you have a CJS section that must stay, name it as a landmine rather than hoping the model infers the boundary.

Get the JavaScript agent pack

A battle-tested AGENTS.md, the review checklist, and the failure-mode cheat sheet for JavaScript. One email, then occasional updates when the tooling shifts. No course pitch.

Unsubscribe in one click. We never sell the list. Or just take the AGENTS.md now — no email needed.