Large Legacy Application
Encourages incremental changes that respect undocumented behavior and require targeted regression coverage.
Scenario
You maintain a large application that has evolved for many years.
The repository contains:
- modern TypeScript modules;
- older JavaScript modules;
- well-tested features;
- poorly tested legacy areas;
- multiple architectural patterns;
- compatibility-sensitive public behavior.
A full rewrite is neither practical nor desirable.
The goal is to let agents make safe changes without assuming the entire repository follows the newest pattern.
Repository Structure
enterprise-app/
├── src/
│ ├── modern/
│ │ ├── features/
│ │ └── services/
│ ├── legacy/
│ │ ├── billing/
│ │ ├── reports/
│ │ └── integrations/
│ └── shared/
├── tests/
│ ├── modern/
│ └── legacy/
├── scripts/
├── package.json
└── AGENTS.md
AGENTS.md
# Project Instructions
## General Approach
- Inspect the local architecture before making changes.
- Follow the established pattern of the area being modified.
- Do not assume newer repository patterns apply automatically to legacy modules.
- Keep targeted changes focused.
## Modern Code
- Use TypeScript in modern modules.
- Follow the existing feature and service boundaries.
- Add or update tests when behavior changes.
## Legacy Code
- Preserve existing behavior outside the requested change.
- Do not modernize an entire module as part of an unrelated bug fix or feature.
- Existing JavaScript files do not need to be converted to TypeScript for targeted changes.
- Small refactors are acceptable when necessary to make the requested change safely.
## Compatibility
- Treat existing public APIs and integration behavior as compatibility-sensitive.
- Do not remove exports merely because repository-local search finds no consumers.
- Check known integration boundaries before changing request, response, or data formats.
## Testing
Before changing poorly tested legacy behavior:
- inspect existing coverage;
- identify the behavior being changed;
- add characterization or regression coverage when practical.
For reproducible bugs:
- add a regression test at the safest practical level;
- verify the fix against that test.
## Generated Code
- Do not manually edit generated files.
- Identify the source and regeneration workflow before changing generated output.
## Dependencies
- Avoid unrelated dependency upgrades during targeted maintenance.
- Do not replace established libraries solely to modernize style.
## Validation
Run validation appropriate to the area changed.
For broad or shared changes:
- run `pnpm lint`;
- run `pnpm typecheck` where supported;
- run relevant tests;
- run broader regression tests when the blast radius is uncertain.
Report important behavior that could not be verified automatically.
What This Does
This tells the agent to treat the repository as heterogeneous.
Instead of assuming:
repository has one architecture
the agent first asks:
Which area am I changing?
What patterns exist here?
What compatibility constraints exist?
How much test coverage protects this behavior?
For example:
src/modern/features/orders/
may use modern TypeScript feature architecture.
But:
src/legacy/billing/
may use CommonJS modules and direct database access.
A small billing bug does not automatically justify migrating the entire module.
What This Does NOT Do
The instructions do not defend bad architecture indefinitely.
Modernization can still happen.
The important distinction is between:
intentional modernization project
and:
unplanned modernization hidden inside another task
The file also does not say agents must copy poor legacy patterns into new modern areas.
Local context determines which conventions apply.
Why These Instructions Matter
Large repositories often contain historical layers.
An agent may see:
module.exports = calculateInvoice;
and conclude:
This should be TypeScript and ESM.
That may be directionally reasonable.
But converting it while fixing one invoice rounding bug introduces additional changes:
module system
type system
imports
exports
tests
build behavior
runtime compatibility
Now a small bug fix has a much larger blast radius.
Another risk is apparently unused code.
A repository search may show no callers for:
exportLegacyReport()
but an external integration may call that API.
Local search is evidence, not proof that a compatibility-sensitive interface is unused.
Key Decisions
Local architecture matters
Large repositories rarely have perfect consistency.
Inspect before imposing a pattern.
Keep maintenance proportional
A bug fix should normally remain a bug fix.
Characterize unknown behavior
Tests can document legacy behavior before changing it.
Modernize intentionally
Modernization is easier to review when it is its own explicit objective.
Be transparent about validation gaps
Legacy systems may contain areas that cannot be tested automatically.
Report those limitations instead of implying complete confidence.
When to Use This Pattern
Use this pattern when:
- the application has years of history;
- several architectural generations coexist;
- test coverage varies significantly;
- external compatibility matters;
- rewriting everything is unrealistic.
The central rule is:
understand the local system before improving it, and improve it without accidentally expanding the task.