feat(coding-agent): added automatic deduplication of context files by content

- Added automatic deduplication of identical context files by content, keeping the closest (lowest depth) copy when duplicates are discovered.
- Implemented dedupeExactContextFiles() function to filter duplicate context entries in both explicit and discovered file lists.
- Added 3 test cases covering deduplication of explicit context entries, discovered context entries, and preservation of distinct entries.
This commit is contained in:
can1357
2026-03-18 23:00:14 +01:00
parent 20cdf31b9f
commit 22a876bb8f
5 changed files with 94 additions and 8 deletions
@@ -5,7 +5,11 @@ import * as path from "node:path";
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk";
import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager";
import { loadSystemPromptFiles } from "@oh-my-pi/pi-coding-agent/system-prompt";
import {
buildSystemPrompt,
loadProjectContextFiles,
loadSystemPromptFiles,
} from "@oh-my-pi/pi-coding-agent/system-prompt";
function escapeRegExp(text: string): string {
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -77,4 +81,62 @@ describe("SYSTEM.md prompt assembly", () => {
await expect(loadSystemPromptFiles({ cwd: projectDir })).resolves.toBe("Project SYSTEM prompt");
});
it("drops identical explicit context entries even when file names differ", async () => {
const farPath = path.join(tempDir, "far", "AGENTS.md");
const nearPath = path.join(tempDir, "near", "CLAUDE.md");
const sharedContent = "Shared context instructions";
const prompt = await buildSystemPrompt({
cwd: tempDir,
customPrompt: "Base prompt",
contextFiles: [
{ path: farPath, content: sharedContent, depth: 2 },
{ path: nearPath, content: sharedContent, depth: 0 },
],
skills: [],
rules: [],
toolNames: [],
});
const matches = prompt.match(new RegExp(escapeRegExp(sharedContent), "g")) ?? [];
expect(matches).toHaveLength(1);
expect(prompt).not.toContain(`<file path="${farPath}">`);
expect(prompt).toContain(`<file path="${nearPath}">`);
});
it("drops identical discovered context entries and keeps the closest copy", async () => {
const projectDir = path.join(tempDir, "project");
const appDir = path.join(projectDir, "packages", "app");
const sharedContent = "Shared context instructions";
fs.mkdirSync(appDir, { recursive: true });
fs.writeFileSync(path.join(projectDir, "AGENTS.md"), sharedContent);
fs.writeFileSync(path.join(appDir, "AGENTS.md"), sharedContent);
const contextFiles = await loadProjectContextFiles({ cwd: appDir });
const discoveredFiles = contextFiles.filter(file => file.path.startsWith(projectDir));
expect(discoveredFiles).toHaveLength(1);
expect(discoveredFiles[0]?.path).toBe(path.join(appDir, "AGENTS.md"));
});
it("keeps distinct context entries when their contents differ", async () => {
const farPath = path.join(tempDir, "far", "AGENTS.md");
const nearPath = path.join(tempDir, "near", "CLAUDE.md");
const prompt = await buildSystemPrompt({
cwd: tempDir,
customPrompt: "Base prompt",
contextFiles: [
{ path: farPath, content: "Root context instructions", depth: 2 },
{ path: nearPath, content: "Near context instructions", depth: 0 },
],
skills: [],
rules: [],
toolNames: [],
});
expect(prompt).toContain("Root context instructions");
expect(prompt).toContain("Near context instructions");
});
});