feat(FN-1772): add backend-aware memory instruction context API

- Add MemoryBackendContext interface for backend-aware memory integration
- Implement getMemoryInstructions() method with backend-specific logic in project-memory.ts
- Add QMD memory backend type detection and instruction context for QMD-backed projects
- Update memory-plugin-contract.md with backend-variant instruction requirements (section 3.8.7)
- Update settings reference for memoryBackendType with custom backend support
- Add comprehensive regression tests for backend-variant memory instruction behavior
- Add changeset for @gsxdsm/fusion package
This commit is contained in:
gsxdsm
2026-04-13 22:03:42 -07:00
parent ce518f3b61
commit f6ae0e2e82
9 changed files with 502 additions and 18 deletions

View File

@@ -2357,6 +2357,65 @@ describe("buildExecutionPrompt", () => {
expect(result).toContain("`.fusion/memory.md`");
});
});
describe("memoryBackendType setting", () => {
it("includes .fusion/memory.md for file backend", () => {
const task = createMockTaskDetail();
const result = buildExecutionPrompt(task, "/project", {
memoryEnabled: true,
memoryBackendType: "file",
} as any);
expect(result).toContain("## Project Memory");
// Check that the Project Memory section contains .fusion/memory.md
const memorySectionMatch = result.match(/## Project Memory\n([\s\S]*?)(?=\n## [^#]|$)/);
expect(memorySectionMatch).toBeTruthy();
expect(memorySectionMatch![1]).toContain(".fusion/memory.md");
});
it("includes read-only wording for readonly backend without write directives in memory section", () => {
const task = createMockTaskDetail();
const result = buildExecutionPrompt(task, "/project", {
memoryEnabled: true,
memoryBackendType: "readonly",
} as any);
expect(result).toContain("## Project Memory");
// Extract the Project Memory section
const memorySectionMatch = result.match(/## Project Memory\n([\s\S]*?)(?=\n## [^#]|$)/);
expect(memorySectionMatch).toBeTruthy();
const memorySection = memorySectionMatch![1];
// Should NOT contain write/update directives in the memory section
expect(memorySection).not.toMatch(/write.*memory|update.*memory/i);
// Should NOT contain the specific file path in the memory section
expect(memorySection).not.toContain(".fusion/memory.md");
});
it("does not include .fusion/memory.md in Project Memory section for qmd backend", () => {
const task = createMockTaskDetail();
const result = buildExecutionPrompt(task, "/project", {
memoryEnabled: true,
memoryBackendType: "qmd",
} as any);
expect(result).toContain("## Project Memory");
// Extract the Project Memory section
const memorySectionMatch = result.match(/## Project Memory\n([\s\S]*?)(?=\n## [^#]|$)/);
expect(memorySectionMatch).toBeTruthy();
const memorySection = memorySectionMatch![1];
// QMD should NOT unconditionally reference .fusion/memory.md in the memory section
expect(memorySection).not.toContain(".fusion/memory.md");
// Should instruct to consult project memory
expect(memorySection).toMatch(/consult.*project memory/i);
});
it("excludes memory section when memoryEnabled: false regardless of backend", () => {
const task = createMockTaskDetail();
const result = buildExecutionPrompt(task, "/project", {
memoryEnabled: false,
memoryBackendType: "file",
} as any);
expect(result).toContain("Execute this task.");
expect(result).not.toContain("## Project Memory");
});
});
});
// Import the summarizeToolArgs helper directly (not affected by mocks above)

View File

@@ -4049,11 +4049,12 @@ git log --oneline
}
// Build project memory section from settings
// When enabled, agents consult and update .fusion/memory.md for durable project learnings.
// When enabled, agents consult and update project memory for durable project learnings.
// Backend-aware: instructions branch based on memoryBackendType (file, readonly, qmd)
const memoryEnabled = settings?.memoryEnabled !== false;
let memorySection = "";
if (memoryEnabled && rootDir) {
memorySection = "\n" + buildExecutionMemoryInstructions(rootDir);
memorySection = "\n" + buildExecutionMemoryInstructions(rootDir, settings);
}
// Build steering comments section (last 10 comments only to avoid context bloat)

View File

@@ -317,6 +317,71 @@ describe("buildSpecificationPrompt", () => {
});
});
describe("memoryBackendType setting", () => {
it("includes .fusion/memory.md for file backend", () => {
const settings: Settings = {
maxConcurrent: 2,
maxWorktrees: 4,
pollIntervalMs: 10000,
groupOverlappingFiles: false,
autoMerge: true,
memoryEnabled: true,
memoryBackendType: "file",
};
const prompt = buildSpecificationPrompt(
baseTask,
".fusion/tasks/KB-001/PROMPT.md",
settings,
);
expect(prompt).toContain("## Project Memory");
expect(prompt).toContain(".fusion/memory.md");
});
it("includes read-only wording for readonly backend without write directives", () => {
const settings: Settings = {
maxConcurrent: 2,
maxWorktrees: 4,
pollIntervalMs: 10000,
groupOverlappingFiles: false,
autoMerge: true,
memoryEnabled: true,
memoryBackendType: "readonly",
};
const prompt = buildSpecificationPrompt(
baseTask,
".fusion/tasks/KB-001/PROMPT.md",
settings,
);
expect(prompt).toContain("## Project Memory");
// Should NOT contain write/update directives
expect(prompt).not.toMatch(/write.*memory|update.*memory/i);
// Should NOT contain the specific file path
expect(prompt).not.toContain(".fusion/memory.md");
});
it("does not include .fusion/memory.md for qmd backend", () => {
const settings: Settings = {
maxConcurrent: 2,
maxWorktrees: 4,
pollIntervalMs: 10000,
groupOverlappingFiles: false,
autoMerge: true,
memoryEnabled: true,
memoryBackendType: "qmd",
};
const prompt = buildSpecificationPrompt(
baseTask,
".fusion/tasks/KB-001/PROMPT.md",
settings,
);
expect(prompt).toContain("## Project Memory");
// QMD should NOT unconditionally reference .fusion/memory.md
expect(prompt).not.toContain(".fusion/memory.md");
// Should instruct to consult project memory
expect(prompt).toMatch(/consult.*project memory/i);
});
});
describe("user comments", () => {
it("includes user comments section when user comments exist", () => {
const taskWithComments: TaskDetail = {

View File

@@ -1410,11 +1410,12 @@ export function buildSpecificationPrompt(
}
// Build project memory section from settings.
// When enabled, agents consult .fusion/memory.md for durable project learnings.
// When enabled, agents consult project memory for durable project learnings.
// Backend-aware: instructions branch based on memoryBackendType (file, readonly, qmd)
const memoryEnabled = settings?.memoryEnabled !== false;
let memorySection = "";
if (memoryEnabled) {
memorySection = "\n\n" + buildTriageMemoryInstructions("");
memorySection = "\n\n" + buildTriageMemoryInstructions("", settings);
}
let attachmentsSection = "";