Legacy behavior discovery
Reconstructs existing behavior and hidden dependencies so a legacy change has an observable, controlled starting point.
legacy-code-discovery
Team members using this skill
When is it useful?
Use it when an old module lacks trustworthy documentation or tests and seemingly small edits have surprising effects.
What does it help you do?
- Traces callers, outputs and hidden external side effects.
- Finds global state, initialization order and dynamic references.
- Identifies small seams for observing or replacing dependencies.
Try asking this
Map dependencies of this untested invoice class before refactoring it. Find every caller and observe a representative invoice without changing it. Record hidden time, database and notification dependencies before choosing the first extraction seam.
Detailed documentation
Read the skill’s original instructions, examples and checks in their authored language.
Show less
Legacy Code & Dependency Discovery
Before changing hard-to-modify code, map what the system actually does today — not what comments, outdated docs or naming suggest. Legacy code often relies on implicit execution order, shared mutable state, dynamic dispatch or undocumented external contracts.
1. Behavior & Entry-Point Mapping
Trace the affected area from its real entry points to its observable outputs:
- Entry surfaces: HTTP routes, RPC handlers, CLI commands, queue consumers, scheduled jobs, event listeners, lifecycle hooks, UI event handlers and public library exports.
- Call graph: trace both incoming callers (who invokes this unit and with what shapes) and outgoing callees (what services, repositories, SDKs or globals this unit touches).
- Observable outputs: return values, thrown exceptions or error codes, HTTP status and headers, database writes, emitted events, queued jobs, cache mutations, filesystem changes and telemetry.
2. Hidden Coupling & Implicit Dependencies
Look past explicit function arguments for hidden dependencies that break when code moves:
- Shared mutable state: module-level singletons, static properties, request-context globals, in-memory caches and mutated input parameters.
- Temporal coupling: initialization steps that must run before a method works, two-phase setup, reliance on transaction scope opened by a caller, or middleware ordering.
- Dynamic and reflective access:
- Dynamic property indexing (
obj[key]),eval, string-based container resolution, dependency injection tokens or service locators. - Framework conventions: magic method names, decorators, annotations, auto-discovered directory paths, ORM relation names, serializer field reflection and job/event class names persisted in databases or queues.
- Dynamic property indexing (
- Configuration & environment reads: direct
process.env/getenv/ config reads buried deep inside domain logic that behave differently across environments.
3. Identifying Seams
Follow Michael Feathers's seam model: a seam is a place where you can alter behavior or observe execution without editing the risky code in place, and every seam has an enabling point where you choose which behavior runs.
- Object / interface seams: pass a collaborator, adapter or clock/ID generator through constructor or parameter injection instead of instantiating it inline.
- Preprocessing / configuration seams: environment config, feature flags or router/container bindings that swap implementations at the boundary.
- Link / module seams: module exports or wrapper functions that allow intercepting calls at a module boundary before decomposing internals.
- ** Sprout & Wrap points**: when a function is too entangled to edit directly, identify where a new pure helper can be sprouted or the existing function can be wrapped to isolate new or extracted logic.
4. Risk & Test-Coverage Audit
Before proposing a refactoring plan, classify the safety net around the target code:
- Existing tests: run the current test suite around the target module. Check whether tests assert observable behavior or merely lock down private mocks.
- Uncovered branches: identify error paths, fallback branches, null/empty edge cases and boundary conditions with zero test coverage.
- External blast radius: note whether serialized payloads, database columns, public URLs, webhook formats or exported types cross process or version boundaries.
- Stop condition: if discovering dependencies reveals an undocumented architectural constraint or ambiguous business rule, record the concrete evidence and align with the architect or product analyst before restructuring.
Included files 1
SKILL.md
