legacy-modernization — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited legacy-modernization (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Legacy code isn't bad code — it's code that solved a real problem when it was written. Modernization is the process of adapting it to current requirements without breaking what works.
Core principle: Never rewrite from scratch. Strangle it incrementally. Each step must leave the system in a working state.
Iron Law:
THE SYSTEM MUST WORK AFTER EVERY CHANGE
There is no "temporarily broken while we modernize"Before any changes, understand what you have:
# Size and complexity
find src/ -name "*.ts" -o -name "*.js" | \
xargs wc -l | sort -rn | head -20
# Dependency graph
npx madge --circular src/
# Test coverage (what's protected)
npx jest --coverage --coverageReporters text-summary
# Outdated packages
npm outdated
# Known patterns to modernize
grep -rn "require\(" src/ --include="*.ts" | wc -l # CommonJS in TypeScript
grep -rn "callback\|\.then\|\.catch" src/ --include="*.ts" | wc -l # Promises vs async
grep -rn ": any" src/ --include="*.ts" | wc -l # Untyped codeRate each area for modernization risk:
## Modernization Risk Register
| Module | Lines | Test Coverage | External Deps | Criticality | Risk |
|--------|-------|---------------|---------------|-------------|------|
| rules.service.ts | 120 | 85% | None | High | LOW |
| mcp.service.ts | 450 | 45% | MCP SDK | High | HIGH |
| main.ts | 60 | 0% | NestJS | Medium | MEDIUM |
Risk = (Criticality × (100 - Coverage%)) / 100## Modernization Items
| ID | Pattern | Count | Files | Effort |
|----|---------|-------|-------|--------|
| M-001 | `any` type → explicit types | 23 | 8 files | 2h |
| M-002 | Callbacks → async/await | 12 | 4 files | 4h |
| M-003 | CommonJS → ESM imports | 45 | 15 files | 1h |
| M-004 | NestJS 9 → NestJS 10 | 1 | package.json | 8h |Build new behavior alongside old behavior, gradually replacing old with new.
// Phase 1: New implementation behind feature flag
async getRules(): Promise<Rule[]> {
if (process.env.USE_NEW_RULES_ENGINE === 'true') {
return this.newRulesEngine.getRules(); // New implementation
}
return this.legacyGetRules(); // Old implementation still works
}
// Phase 2: Enable in staging, verify
// Phase 3: Enable in production (10% → 50% → 100%)
// Phase 4: Remove old implementation
async getRules(): Promise<Rule[]> {
return this.newRulesEngine.getRules(); // Old code removed
}// Step 1: Introduce interface
interface RulesEngine {
getRules(): Promise<Rule[]>;
searchRules(query: string): Promise<Rule[]>;
}
// Step 2: Wrap legacy code
class LegacyRulesEngine implements RulesEngine {
// Existing code, now behind interface
}
// Step 3: New implementation
class NewRulesEngine implements RulesEngine {
// Modern implementation
}
// Step 4: Switch (one line change)
const rulesEngine: RulesEngine = new NewRulesEngine();
// Was: new LegacyRulesEngine()// Before (JavaScript)
function getUser(id) {
return fetch('/api/users/' + id)
.then(function(response) {
return response.json();
})
.then(function(data) {
return data.user;
});
}// After (TypeScript + async/await)
async function getUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
const data: { user: User } = await response.json();
return data.user;
}// Before (callbacks)
function readRule(path, callback) {
fs.readFile(path, 'utf8', function(err, content) {
if (err) return callback(err);
callback(null, parseRule(content));
});
}// After (async/await)
async function readRule(path: string): Promise<Rule> {
const content = await fs.promises.readFile(path, 'utf-8');
return parseRule(content);
}any Types// Before
function processRule(rule: any): any {
return { name: rule.name, content: rule.content };
}
// After (step 1: define interface)
interface RuleInput {
name: string;
content: string;
[key: string]: unknown; // allow extra properties
}
interface ProcessedRule {
name: string;
content: string;
}
// After (step 2: type the function)
function processRule(rule: RuleInput): ProcessedRule {
return { name: rule.name, content: rule.content };
}For EACH modernization item:
1. WRITE TESTS (if coverage < 80%)
- Tests must cover current behavior
- These tests protect against regression
2. MODERNIZE one file at a time
- Apply pattern change
- Keep behavior identical
3. RUN ALL TESTS
- All existing tests must still pass
- No "we'll fix the tests later"
4. COMMIT
- One commit per file or per pattern type
- Message: "refactor: migrate X to Y in Z"
5. VERIFY in staging before next item## NestJS 9 → 10 Migration Plan
### Preparation
- [ ] Read migration guide: https://docs.nestjs.com/migration-guide
- [ ] Test suite at 80%+ coverage
- [ ] Feature branch created
### Steps
1. [ ] Update peer dependencies first
2. [ ] Update NestJS packages
3. [ ] Fix breaking API changes (see migration guide)
4. [ ] Run test suite
5. [ ] Fix type errors
6. [ ] Test in staging
7. [ ] Deploy to production
### Rollback
- git revert to previous package.json
- npm install
- Deploy previous version| Risk | Mitigation |
|---|---|
| Breaking working functionality | Write tests before modernizing |
| Long migration blocks features | Use strangler fig pattern |
| Merge conflicts | Small, frequent commits |
| Regression in edge cases | Test coverage for edge cases first |
| Team unfamiliar with new patterns | Document new patterns + pair programming |
| Thought | Reality |
|---|---|
| "Let's rewrite it from scratch" | Rewrites take 3× longer and miss edge cases |
| "We'll fix tests later" | Tests are the safety net — fix them now |
| "One big refactoring PR" | Small PRs are safer and reviewable |
| "The new code is obviously correct" | Verify with tests, not confidence |
| "We can modernize while adding features" | Keep modernization commits separate |
Migration Patterns:
──────────────────────────────
Strangler Fig → New code wraps old, gradual replacement
Branch by Abstraction → Interface first, then implementations
Parallel Run → Old and new run simultaneously, compare results
Expand-Contract → Database: add new → migrate → remove old
Priority Order:
──────────────────────────────
1. Highest risk (low coverage, high criticality) → test first
2. Quick wins (high impact, low effort)
3. Architectural changes (last, requires stability)~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.