Legacy Package in a Modern Monorepo
Keeps legacy package exceptions local while preserving modern conventions across the monorepo.
Scenario
You maintain a modern pnpm monorepo.
Most packages use:
- TypeScript;
- ESM;
- Vitest;
- modern lint rules.
One package is an older CommonJS integration library used by production systems.
The package cannot be modernized casually because external consumers depend on its current behavior and module format.
You want agents working elsewhere in the monorepo to follow modern conventions while agents touching the legacy package understand its constraints.
Repository Structure
platform/
├── AGENTS.md
├── apps/
│ └── web/
├── packages/
│ ├── shared/
│ ├── ui/
│ └── legacy-integration/
│ ├── AGENTS.md
│ ├── src/
│ │ ├── client.js
│ │ └── transform.js
│ ├── tests/
│ └── package.json
├── pnpm-workspace.yaml
└── package.json
AGENTS.md
Root AGENTS.md:
# Monorepo Instructions
## Development
- Use `pnpm`.
- Use TypeScript for new code unless scoped instructions specify otherwise.
- Follow existing workspace boundaries.
## Modules
- Modern packages use ESM.
- Do not introduce new CommonJS packages.
## Dependencies
- Declare dependencies in the workspace that directly uses them.
- Reuse existing workspace packages when appropriate.
## Validation
- Run validation for affected workspaces.
- Validate consumers when shared package APIs change.
packages/legacy-integration/AGENTS.md:
# Legacy Integration Package Instructions
## Compatibility
- This package intentionally remains CommonJS.
- Do not convert it to ESM as part of unrelated work.
- Existing JavaScript files do not need to be migrated to TypeScript for targeted fixes.
- Preserve the current public module interface unless the task explicitly changes it.
## External Consumers
- Treat exported functions as compatibility-sensitive.
- Avoid changing argument shapes, return shapes, or error behavior without checking affected consumers.
- Do not remove exports merely because they appear unused inside this repository.
## Changes
- Prefer small, targeted modifications.
- Add regression coverage for bug fixes when practical.
- Avoid combining modernization with unrelated behavior changes.
## Validation
For changes to this package, run:
- `pnpm --filter legacy-integration lint`
- `pnpm --filter legacy-integration test`
If public behavior changes, validate known consumers before completion.
What This Does
This allows the repository to maintain modern defaults without pretending that every package has already reached the same state.
At the root:
TypeScript
ESM
modern package conventions
are the normal direction.
Inside:
packages/legacy-integration/
the instructions protect compatibility.
An agent fixing a bug there should not automatically transform:
const client = require("./client");
into:
import client from "./client";
as part of the same task.
What This Does NOT Do
The instructions do not declare the legacy architecture permanent.
A future task might explicitly be:
Migrate legacy-integration from CommonJS to ESM and TypeScript.
At that point, modernization is the task.
The difference is intentional scope.
The current instructions prevent modernization from happening accidentally during:
bug fix
dependency update
small feature
production incident
The file also does not assume an export is unused simply because no internal references exist.
External packages may consume it.
Why These Instructions Matter
Legacy packages inside modern repositories create misleading signals.
An agent sees:
95% TypeScript
ESM everywhere
modern test tooling
and reasonably concludes that an old JavaScript/CommonJS package should be cleaned up.
But that cleanup may break external consumers.
For example:
module.exports = {
createClient,
transformPayload
};
may be consumed by systems outside the repository.
Changing the module format can therefore have consequences that repository-local search cannot reveal.
The instruction:
- Preserve the current public module interface unless the task explicitly changes it.
makes compatibility an explicit concern.
Key Decisions
Modern defaults can coexist with legacy exceptions
The repository does not need to weaken its root standards simply because one package is different.
Scope the exception.
Public APIs require extra caution
Repository-local usage does not always reveal external usage.
This is especially important for:
libraries
SDKs
integration packages
published packages
shared internal packages
Separate modernization from behavioral work
Combining:
CommonJS → ESM
JavaScript → TypeScript
bug fix
API cleanup
into one change makes it difficult to determine what caused a regression.
Modernization is safer when intentional and separately validated.
Keep exceptions narrow
Do not copy legacy rules into other packages.
The exception exists only where it is needed.
Remove obsolete instructions after migration
If the package is eventually modernized, update or remove its scoped instructions.
AGENTS.md should describe the repository's current operating model, not preserve historical constraints forever.
When to Use This Pattern
Use this pattern when:
- a modern monorepo contains older packages;
- compatibility prevents immediate modernization;
- external consumers may depend on existing APIs;
- most repository conventions should not apply unchanged to the legacy area;
- modernization needs to be deliberate.
This pattern shows the real value of scoped instructions:
the repository can have strong defaults without forcing every part of the codebase to pretend it has identical constraints.