Setting up a coding agent for a JavaScript project
Node projects have the loosest defaults of any ecosystem here, which means the setup work matters more.
JavaScript gives an agent the least resistance of any language on this network. There is no compiler, the runtime accepts almost anything, and a wrong program usually runs until it does not. That makes the setup below more important here than anywhere else — you are building the feedback the language declines to provide.
1. Turn on type checking, even without TypeScript#
The single highest-value change, and it does not require renaming a file.
{
"compilerOptions": {
"checkJs": true,
"strict": true,
"noEmit": true,
"target": "ES2023",
"module": "nodenext",
"moduleResolution": "nodenext"
},
"include": ["src/**/*.js"]
}npx tsc --noEmitYou now get real type errors on plain JavaScript, driven by JSDoc annotations and inference. More importantly, it unlocks the type-aware ESLint rules — including no-floating-promises, which catches the most common bug in generated JavaScript. See the failure modes.
Expect errors on existing code. Add // @ts-nocheck at the top of the worst files, fix inwards, and delete the pragmas as you go.
2. The lint config that catches real bugs#
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"],
},
},
];The type-checked preset is the part that matters. Without type information, ESLint cannot tell a promise from an object, and the async rules — where the real bugs are — do not work.
3. Four named commands#
{
"scripts": {
"typecheck": "tsc --noEmit",
"lint": "eslint . --fix",
"test": "vitest run",
"check": "npm run typecheck && npm run lint && npm run test"
}
}npm run check is the contract. Make sure it genuinely fails on a broken tree before you rely on it.
4. A fast test loop#
export default {
test: {
include: ["src/**/*.test.js"],
exclude: ["**/*.integration.test.js"], // default run stays offline
pool: "threads",
},
};The threshold to aim for is five seconds. Below it, the agent runs tests after every edit and converges. Above thirty, it stops running them and starts telling you the code "should work".
5. Permissions#
{
"permissions": {
"allow": [
"Bash(npm run test:*)", "Bash(npm run lint)", "Bash(npm run check)",
"Bash(npx tsc:*)", "Bash(git status)", "Bash(git diff:*)"
],
"ask": ["Bash(npm install:*)", "Bash(pnpm add:*)", "Bash(git push:*)"],
"deny": ["Read(./.env)", "Read(./.env.*)", "Bash(curl:*)", "Bash(rm -rf:*)"]
}
}Package installation belongs behind a confirmation. npm is the largest slopsquatting target of any registry, and models invent plausible package names — a two-second glance at the name before it installs is the cheapest control you have.
6. AGENTS.md, focused on what the language will not enforce#
Node 22, ESM only, pnpm. jsconfig.json has checkJs: true — keep it passing.
## Commands
- Check: `pnpm check` (tsc --noEmit, eslint, vitest run). Must pass.
- One test: `pnpm vitest run src/thing.test.js`
## Conventions
- await every promise. Floating promises are errors.
- Promise.all for independent work. A sequential loop needs a comment saying why.
- ?? for defaults, not || — 0 and "" are valid values.
- Do not mutate arguments, props or state. structuredClone for deep copies.
- Every fetch in a React effect aborts on cleanup.
## Use the platform, not a package
fetch (not axios), crypto.randomUUID (not uuid), node --env-file (not dotenv),
Intl/Temporal (not moment), fs.rm (not rimraf), Object.groupBy (not lodash).
## Landmines
- src/legacy/ is CommonJS and is loaded by the worker. Do not convert it.That last section — "use the platform" — is worth including in every JavaScript project. Models write the 2018 dependency by default because that is what the training data contains, and the dependencies accumulate silently.
The full version of this file, with the reasoning for each line, is in writing an AGENTS.md for JavaScript.
What this costs#
Agent sessions are billed, and the bill is driven by context size more than by how much you ask for. The levers — prompt caching, pruning unused MCP servers, starting a fresh session when the task changes — are in what tokens actually cost.
The checklist#
[ ] jsconfig.json with checkJs, and tsc --noEmit passing
[ ] type-aware ESLint with no-floating-promises
[ ] pnpm check fails on a broken tree
[ ] default test run under 5 seconds, offline
[ ] install commands behind a confirmation
[ ] AGENTS.md with the "use the platform" listCommon questions#
Is checkJs really worth it on an existing codebase?#
Yes, and you do not have to fix everything at once. // @ts-nocheck on the worst files gets you a passing build immediately, and every file you remove the pragma from is a file the agent now gets real type feedback on. The unlock for the async lint rules alone justifies it.
ESLint or Biome?#
Biome is much faster and covers formatting plus a good rule set. But the rules that catch the expensive bugs here need type information, and today that means ESLint. A reasonable setup is Biome for formatting and fast rules, ESLint for the type-aware ones.
Should the agent be allowed to install packages?#
Behind a confirmation, always. npm has the largest supply-chain attack surface of any registry and models produce plausible-but-nonexistent package names at a measurable rate. Reading the name before it installs takes two seconds.
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.
AGENTS.md now — no email needed.
Disclosure: some links on this page are affiliate links. If you buy something through one, we earn a commission at no extra cost to you. We only list tools we would tell a friend to use, and we say so when we have not used something ourselves. This is how the site stays free and ad-light.