How Code Evolution Lab turns a detected issue into concrete, runnable fixes.

This applies to web and API scans only. The CLI and GitHub Action detect issues and report them, but do not generate fixes.

What it does

Most linters tell you a line is wrong. Code Evolution Lab attempts to rewrite it, producing several candidate fixes derived from your actual code, each scored and ranked.

Generation runs in two phases. The heuristic phase applies transformation strategies to produce an initial set of solutions. Those are then deduplicated — near-identical candidates that differ only by variable naming are collapsed — ranked, and capped to your tier’s solutions-per-issue limit.

Some issues then go through a second phase, where those solutions become the starting population for evolution. Not every issue qualifies: evolution runs only for issues of type n_plus_1_query, nested_loops, nested_array_methods or await_in_loop, or for any issue of high or critical severity. Everything else stops after the heuristic phase. If evolution is disabled, times out, or fails, the heuristic solutions are what you get.

Solutions come from generators written in one of two styles, and it is worth knowing which you are looking at:

Transformation-based solutions record which of your original elements they preserved, and that record drives the risk rating. Template-based solutions are illustrative, so read them as guidance rather than as a patch.

Some generators are framework-aware. Detectors record a framework on the issue (estimatedImpact.metrics.framework), and generators branch on it — a missing listener cleanup is offered as a useEffect return in React, ngOnDestroy in Angular, and beforeUnmount in Vue. Where no framework is recorded, the framework-agnostic strategy is offered instead.

Generators

Each issue type is routed to a generator that knows how to fix that class of problem. There are eleven, one per detector category:

Generator Handles
N1SolutionGenerator N+1 queries
InefficientLoopSolutionGenerator 12 loop anti-patterns
MemoryLeakSolutionGenerator Event listener, timer, global, and closure leaks
LargePayloadSolutionGenerator Oversized API and query payloads
MissingIndexSolutionGenerator Missing and suboptimal database indexes
MissingCachingSolutionGenerator Repeated calls, missing memoization
ResourceLeaksSolutionGenerator Unclosed connections, streams, file handles
BlockingIoSolutionGenerator Synchronous file, crypto, process, and DB calls
DomManipulationSolutionGenerator Layout thrashing, unsafe innerHTML
BundleSizeSolutionGenerator Heavy imports, tree-shaking opportunities
RedosSolutionGenerator Catastrophic backtracking patterns

A generic generator handles anything unrouted. The routing map lives in backend/src/analyzer/code-analyzer.ts — an issue type absent from it will not reach a generator, so adding a new issue type means adding a routing entry.

How a solution is built

Every generator inherits the same pipeline from BaseSolutionGenerator:

  1. Analyse the original. The problematic code is parsed and reduced to a CodePattern describing what shape it is — a loop with calls, chained methods, repeated access, nested loops — along with the loop variable, the collection being iterated, and the repeated calls found inside. This applies to transformation-based generators; template-based ones route on issue type alone.

  2. Apply strategies. Each of the generator’s transformation strategies is applied to the original code. A strategy that throws, or that returns code failing validation, is skipped rather than failing the run.

  3. Apply generic transformations. A second pass runs the shared transformation candidates from code-transformer, adding any whose transformation type isn’t already represented. These are scored at 70 plus 5 per preserved element, capped at 90.

  4. Validate. Generated code must contain more than comments and must show some code-like content. Anything else is rejected silently.

  5. Rank and rate. Solutions are ordered, given a risk level and an estimated implementation time.

If the issue carries no original code, generation returns nothing — there is no template fallback.

Validation

Validation is deliberately shallow: it strips comments, requires something to remain, and requires that remainder to look like code rather than prose.

This catches the common failure — a strategy emitting an explanatory comment instead of a fix — but it does not verify that a solution is semantically equivalent to your original. Generated solutions are proposals, not verified refactors. Review and test them as you would any change.

Risk and time

Risk is derived from how much of your original code survived the transformation:

Risk Preserved elements
Low 5 or more
Medium 2 to 4
High Fewer than 2

Implementation time is estimated in minutes by the fitness calculator from the solution’s code and transformation type. Both are heuristics meant for prioritising among solutions, not commitments.

What a solution carries

interface Solution {
  id: string;
  issueId: string;
  rank: number;                  // 1 = best
  type: string;                  // Transformation strategy name
  code: string;
  fitnessScore: number;          // 0-100
  reasoning: string;
  description?: string;
  explanation?: string;
  generationMethod?: 'heuristic' | 'evolutionary';
  generationsUsed?: number;      // Evolutionary only
  implementationTime: number;    // Estimated minutes
  riskLevel: 'low' | 'medium' | 'high';
}

generationMethod tells you which phase produced it. Heuristic solutions carry the strategy’s own fitness value; evolved solutions carry a score computed by the fitness calculator.

Example transformations

These illustrate the kinds of rewrite generators produce. Actual output depends on your code, the detected pattern, and which strategies apply.

N+1 query — batch before the loop:

// Before
const orders = await Order.findAll();
for (const order of orders) {
  order.user = await User.findByPk(order.userId);
}

// After
const orders = await Order.findAll();
const users = await User.findAll({ where: { id: orders.map(o => o.userId) } });
const userMap = new Map(users.map(u => [u.id, u]));
for (const order of orders) {
  order.user = userMap.get(order.userId);
}

Sequential awaits in a loop:

// Before
const results = [];
for (const item of items) {
  results.push(await process(item));
}

// After
const results = await Promise.all(items.map(item => process(item)));

Note that the parallel version changes concurrency behaviour. If process hits a rate-limited API or a connection pool, a batched variant that caps concurrency is the safer fix — which is why generators offer several candidates rather than one.

Missing cleanup in a React effect:

// Before
useEffect(() => {
  const handler = () => {};
  window.addEventListener('resize', handler);
}, []);

// After
useEffect(() => {
  const handler = () => {};
  window.addEventListener('resize', handler);
  return () => window.removeEventListener('resize', handler);
}, []);

Writing a strategy

A strategy is an object with a name, a description, a fitness value, and an apply function receiving the original code, its analysed pattern, and the analysis context:

const myStrategy: TransformationStrategy = {
  name: 'my-custom-strategy',
  description: 'Applies a project-specific optimization',
  fitness: 80,
  apply: (originalCode, pattern, context) => ({
    success: true,
    code: transform(originalCode),
    description: 'Applied custom optimization',
    transformationType: 'my-custom-strategy',
    preservedElements: ['users', 'orderId'],
  }),
};

preservedElements is not cosmetic — it determines the solution’s risk rating, so list the identifiers and structures the transformation genuinely kept.

Pass strategies to generateTransformationBasedSolutions from your generator’s generateSolutions implementation.

Next steps