For a library published to npm where the public API surface is the product. It assumes TypeScript, a bundler producing ESM and CJS output, changesets for versioning, and contributions from outside the core team. Use it when backward compatibility and release hygiene matter more than shipping speed.
# AGENTS.md
This is a TypeScript library published to npm. The public API is the product, so compatibility comes before convenience.
## Project
Stack: TypeScript (strict), tsup for bundling, Vitest, Changesets, GitHub Actions, npm.
Layout:
- `src/index.ts` the only public entry point, everything exported here is API
- `src/internal/` implementation detail, never exported from index
- `src/types.ts` public types
- `test/` tests, imported through the package name so they exercise the real entry
- `dist/` build output, generated
- `docs/` usage documentation, `README.md` quick start
- `.changeset/` pending release notes
Supported runtimes: Node 20 and above, plus modern browsers. Output is ESM and CJS with type definitions.
## Commands
```bash
pnpm install
pnpm build # tsup, emits dist/ with esm, cjs, and .d.ts
pnpm dev # build in watch mode
pnpm test # vitest run
pnpm test --watch
pnpm test test/parse.test.ts
pnpm typecheck # tsc --noEmit
pnpm lint
pnpm check:exports # verify the export map resolves in esm and cjs
pnpm size # report bundle size against the budget
pnpm changeset # record a release note for a user visible change
npm pack --dry-run # inspect exactly what gets published
```
Run `pnpm build && pnpm test && pnpm typecheck` before returning work.
## Code style
- No side effects at import time. Importing the package must not touch globals, read the environment, or start a timer.
- Zero runtime dependencies unless there is no reasonable alternative. Prefer inlining twenty lines over adding a package.
- Everything exported from `src/index.ts` is public API. If it is not meant to be used, it does not belong there.
- Public functions have explicit return types. Do not rely on inference for anything that crosses the boundary.
- No default export. Named exports keep tree shaking and refactoring predictable.
- Errors are typed subclasses with a stable `name`, and messages say what was expected and what was received.
- No Node built ins in code paths that must run in a browser. Platform specific code goes behind a separate export condition.
- Every public function and type carries a TSDoc comment with at least one example.
- Options are passed as a single object, so parameters can be added without a breaking change.
## Boundaries
Do not touch without explicit instruction:
- `version` in `package.json` and `CHANGELOG.md`. Both are written by Changesets.
- `exports`, `main`, `module`, `types`, and `files` fields in `package.json`.
- `dist/` and any build output.
- `pnpm-lock.yaml`, `.github/workflows/`, `.npmrc`.
Needs human review and an explicit decision: removing or renaming anything exported, changing a function signature, changing default behavior, adding a runtime dependency, and raising the minimum supported Node version.
If a task appears to require a breaking change, describe the break and the migration path and stop.
## Testing
- `pnpm test` runs Vitest against the built package where possible, so the export map is exercised.
- Every exported function has tests for the documented behavior, the error cases, and the edge inputs named in its TSDoc.
- Type level tests live in `test/types.test-d.ts` using `expectTypeOf`. A type regression must fail the suite.
- Internal helpers are tested through the public API, not directly, so refactors do not break the suite.
- Bug reports become a failing test first, referencing the issue number in the test name.
- Do not lower a test to make it pass. If behavior must change, that is a release decision.
## Publishing
- Every user visible change requires a changeset: `pnpm changeset`, then pick patch, minor, or major.
- Semver is strict. A patch fixes behavior that was already documented, a minor adds API, a major removes or changes it.
- Deprecate before removing: mark with `@deprecated`, keep it working for at least one minor release, and document the replacement.
- Releases happen from `main` through CI. Do not run `npm publish` locally.
- Before a major, the migration guide in `docs/` must be updated in the same PR.
- `npm pack --dry-run` should contain `dist/`, `README.md`, and `LICENSE`, and nothing else.
## Git workflow
- Branch from `main`: `feat/short-description`, `fix/short-description`, `docs/short-description`.
- Conventional commits: `feat(parser): support trailing commas`.
- A PR with a user visible change and no changeset is incomplete.
- PR description states the API impact: none, additive, or breaking.
- Keep PRs focused. Refactors and behavior changes go in separate PRs so a regression can be bisected.
- Never push to `main`.
In a library the distinction between public API and internals is the most important fact in the repository, and it is not obvious from the directory listing. Stating where the entry point is and what is exported keeps an agent from widening the surface accidentally. Build outputs should be identified as generated.
Library work involves building, type checking, and verifying the published artifact, which is more than a typical app. Listing the pack and size check commands makes it possible for the agent to catch a broken export map. Test commands with watch mode keep iteration cheap.
Library style rules are about consumers: no side effects on import, no unnecessary dependencies, and types that survive being consumed from another project. These are concrete and checkable. Error messages and naming matter more here than in application code.
Version numbers, the changelog, and the export map are release artifacts that must not be hand edited, since tooling owns them. Any breaking change is a human decision because it affects every downstream user. Mark dependency additions as review worthy since they become the consumer problem.
A library needs its public API tested from the outside, the way a consumer would import it, not through internal paths. Type level tests catch regressions that runtime tests miss entirely. Stating the support matrix prevents accidental use of a newer runtime feature.
Release mistakes in a library are public and hard to reverse, so the process needs to be written down rather than remembered. Changesets, semver rules, and the deprecation path are the parts an agent should follow exactly. This section also tells the agent when it must stop and hand off.
Outside contributors read the history and the PR template, so conventions here are documentation. Requiring a changeset with every user visible change keeps the changelog honest. Branch policy protects the release branch.
Claude Code looks for CLAUDE.md. Most other agents and editors read AGENTS.md. Rather than maintaining both and letting them drift, keep one real file and symlink the other:
ln -s AGENTS.md CLAUDE.md
git add AGENTS.md CLAUDE.md Git stores the symlink, so it survives cloning on macOS and Linux. On Windows it needs developer mode or a stub file that references the real one. The full comparison is in CLAUDE.md vs AGENTS.md.
Next.js SaaS
App Router SaaS with Supabase auth, Postgres, and Stripe billing.
Turborepo Monorepo
Multi-package workspace with shared UI, config, and typed contracts.
React Native / Expo
Expo Router app with typed navigation, native modules, and EAS builds.
SvelteKit App
SvelteKit app with server load functions, form actions, and Vitest.