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:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = "";
|
||||
|
||||
Reference in New Issue
Block a user