How Continue's Edit Aggregation Works for Multi-File Changes

Continue's edit aggregation system clusters rapid keystrokes into meaningful diffs using the EditAggregator class, ensuring that edits across multiple files are isolated into separate clusters before being combined into a unified context for the LLM.

Continue is an open-source AI code assistant (continuedev/continue) that tracks user edits in real-time to provide context-aware suggestions. The edit aggregation mechanism transforms streams of individual keystrokes into logical change clusters, handling multi-file scenarios by finalizing clusters when users switch between files.

The Core Architecture of Edit Aggregation

The EditAggregator class in core/nextEdit/context/aggregateEdits.ts serves as the central engine for transforming raw text edits into structured diffs.

File-Specific State Management

The aggregator maintains isolated state for each file using a Map<string, FileState> structure stored in EditAggregator.fileStates. Each FileState tracks:

  • Current file content
  • A queue of pending edit tasks (processingQueue)
  • Active clusters of edits for that specific file

This isolation ensures that edit history from one file never contaminates another during the clustering process.

Cluster Formation and Thresholds

A cluster represents a group of consecutive small edits forming a single logical change. Defined by the ClusterState interface, each cluster records:

  • The before snapshot of the file (beforeState)
  • Line ranges affected by the edits
  • Timestamps and cursor positions
  • The complete list of individual edits

The EditClusterConfig interface defines configurable thresholds that determine cluster boundaries:

  • deltaT: Time gap (in seconds) before forcing a new cluster
  • deltaL: Maximum line distance between edits to remain in the same cluster
  • maxEdits: Maximum number of edits allowed per cluster
  • maxDuration: Total time limit for a cluster's lifespan

Multi-File Edit Aggregation Pipeline

When processing edits across multiple files, the aggregator follows a strict pipeline to maintain file isolation while enabling unified context generation.

File Switch Detection

The critical mechanism for multi-file support resides in the processEdit method (lines 16-22 of aggregateEdits.ts). When an edit arrives for a different file than the previous one, the aggregator immediately finalizes all pending clusters for the previous file:

// core/nextEdit/context/aggregateEdits.ts
if (this.lastProcessedFilePath && this.lastProcessedFilePath !== filePath) {
  await this.finalizeClustersForFile(this.lastProcessedFilePath);
}
this.lastProcessedFilePath = filePath;

This ensures that file A's edits never mix with file B's edits within the same cluster, preserving the integrity of per-file diffs.

Edit Processing Workflow

  1. Edit Arrival: Each small edit (RangeInFileWithNextEditInfo) enters via processEdit
  2. Queue Management: Edits wrap into tasks and enter the file-specific processingQueue, processed in batches of up to five (_processQueue)
  3. Cluster Assignment: _processEditInternal calls findSuitableCluster to determine if the edit fits within an existing cluster based on proximity, time gaps, and edit counts
  4. Cluster Expansion: If suitable, the cluster updates its line range and timestamps; otherwise, a new cluster initializes with the file's pre-edit content
  5. Structural Boundaries: Edits inserting newlines or spanning multiple lines trigger immediate finalization of overlapping clusters to prevent mixing unrelated logical changes

Diff Generation and Finalization

When finalizeCluster executes, it generates a unified diff using createDiff and passes it to EditAggregator.onComparisonFinalized. The system filters out whitespace-only diffs or excessively large changes. The NextEditProvider sets this callback to receive finalized diffs for prompt construction.

Integrating with NextEditProvider

The NextEditProvider class (core/nextEdit/NextEditProvider.ts) consumes aggregated diffs to build LLM prompts. It merges finalized diffs with in-progress editing context:

// core/nextEdit/NextEditProvider.ts (excerpt)
const inProgressDiff = EditAggregator.getInstance().getInProgressDiff(
  helper.filepath,
);
if (inProgressDiff) {
  combinedDiffContext.push(inProgressDiff);
}

This integration occurs in NextEditProvider._generatePrompts (lines 96-107), where the provider combines:

  • All finalized diffs from previous edits (this.diffContext)
  • The current in-progress diff from the active file (inProgressDiff)

The resulting combinedDiffContext presents the LLM with a complete, multi-file view of recent changes while maintaining clear boundaries between files.

Practical Implementation Examples

Configuring the EditAggregator

Instantiate the singleton with custom thresholds for specific aggregation behavior:

import { EditAggregator } from "core/nextEdit/context/aggregateEdits";

const aggregator = EditAggregator.getInstance({
  deltaT: 1.5,      // seconds before forcing new cluster
  deltaL: 3,        // line distance threshold
  maxEdits: 200,    // maximum edits per cluster
});

const edit = {
  filepath: "/project/src/util.ts",
  fileContents: "const newValue = 42;",
  fileContentsBefore: "const oldValue = 0;",
  range: { start: { line: 10, character: 0 }, end: { line: 10, character: 18 } },
  editText: "const newValue = 42;",
  beforeCursorPos: { line: 10, character: 0 },
  afterCursorPos: { line: 10, character: 20 },
};

await aggregator.processEdit(edit);

Retrieving In-Progress Diffs

Access current editing context before finalization:

const currentDiff = EditAggregator.getInstance().getInProgressDiff(
  "/project/src/main.ts"
);

Manual Finalization

Force immediate finalization of all pending clusters, useful during application shutdown or context switches:

await EditAggregator.getInstance().finalizeAllClusters();

Key Source Files

File Role
core/nextEdit/context/aggregateEdits.ts Core aggregation engine implementing EditAggregator, clusters, and diff creation
core/nextEdit/NextEditProvider.ts Consumes aggregated diffs and builds LLM prompts via _generatePrompts
core/nextEdit/context/processSmallEdit.ts Helper recording finalized diffs into the provider context
core/edit/searchAndReplace/* Validation utilities feeding edit objects to the aggregator
core/edit/streamDiffLines.ts Unified diff generation used by createDiff

Summary

  • EditAggregator (aggregateEdits.ts) clusters rapid keystrokes into logical diffs using configurable time, line, and count thresholds.
  • Multi-file isolation occurs through finalizeClustersForFile, which flushes pending clusters when users switch files.
  • Cluster state tracks before/after snapshots, line ranges, and edit metadata via the FileState map structure.
  • NextEditProvider merges finalized diffs with in-progress edits using getInProgressDiff before sending context to the LLM.
  • Structural edits (newlines, multi-line changes) trigger immediate cluster finalization to preserve logical boundaries.

Frequently Asked Questions

How does Continue prevent edit clusters from mixing between different files?

When EditAggregator.processEdit receives an edit for a new file path, it detects the change via lastProcessedFilePath comparison and immediately calls finalizeClustersForFile for the previous file. This forces all pending clusters from the previous file to generate diffs before processing begins on the new file, ensuring complete isolation between file contexts.

What triggers a cluster to finalize into a diff?

Clusters finalize when they exceed thresholds defined in EditClusterConfig: time gaps exceeding deltaT, line distances beyond deltaL, edit counts surpassing maxEdits, or total duration exceeding maxDuration. Additionally, structural edits spanning multiple lines or inserting newlines trigger immediate finalization of overlapping clusters to prevent mixing unrelated changes.

Can I access edits before the cluster finalizes?

Yes. The EditAggregator.getInProgressDiff method returns the current diff for an active file before finalization. NextEditProvider uses this in _generatePrompts to combine in-progress changes with already-finalized diffs, ensuring the LLM receives real-time context including unsaved modifications.

Where does the actual diff generation happen?

The finalizeCluster method inside EditAggregator calls createDiff to generate unified diffs. This utility leverages logic from core/edit/streamDiffLines.ts to produce the diff format, which is then passed to the onComparisonFinalized callback configured by NextEditProvider.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →