fix(FN-2078): adopt canonical .fusion/memory paths
- Update core and engine prompt text to reference .fusion/memory/ with MEMORY.md and daily-note guidance - Allow worktree tool boundary access to .fusion/memory/ files while preserving legacy .fusion/memory.md compatibility - Revise memory compaction/insight extraction messaging and related type/docs comments to use canonical memory paths - Refresh dashboard and test expectations across core/engine/dashboard/docs for the new memory path wording
This commit is contained in:
@@ -154,7 +154,7 @@ describe("resolveAgentPrompt", () => {
|
||||
|
||||
it("built-in executor prompt mentions memory exception", () => {
|
||||
const result = resolveAgentPrompt("executor");
|
||||
expect(result).toContain(".fusion/memory.md");
|
||||
expect(result).toContain(".fusion/memory/");
|
||||
});
|
||||
|
||||
it("built-in executor prompt mentions attachments exception", () => {
|
||||
@@ -183,7 +183,7 @@ describe("resolveAgentPrompt", () => {
|
||||
};
|
||||
|
||||
const result = resolveAgentPrompt("executor", config);
|
||||
expect(result).toContain(".fusion/memory.md");
|
||||
expect(result).toContain(".fusion/memory/");
|
||||
});
|
||||
|
||||
it("senior-engineer prompt mentions attachments exception", () => {
|
||||
|
||||
@@ -108,7 +108,7 @@ model, read-only access) to independently assess your work.
|
||||
You are running in an **isolated git worktree**. This means:
|
||||
|
||||
- **All code changes must be made inside the current worktree directory.** Do not modify files outside the worktree — the worktree is your isolated execution environment.
|
||||
- **Exception — Project memory:** You MAY read and write to .fusion/memory.md at the project root to save durable project learnings (architecture patterns, conventions, pitfalls).
|
||||
- **Exception — Project memory:** You MAY read and write to files under .fusion/memory/ at the project root (MEMORY.md for durable learnings, YYYY-MM-DD.md for daily notes) to save durable project learnings.
|
||||
- **Exception — Task attachments:** You MAY read files under .fusion/tasks/{taskId}/attachments/ at the project root for context screenshots and documents attached to this task.
|
||||
- **Shell commands** run inside the worktree by default. Avoid using cd to navigate outside the worktree.
|
||||
|
||||
@@ -519,7 +519,7 @@ model, read-only access) to independently assess your work.
|
||||
You are running in an **isolated git worktree**. This means:
|
||||
|
||||
- **All code changes must be made inside the current worktree directory.** Do not modify files outside the worktree — the worktree is your isolated execution environment.
|
||||
- **Exception — Project memory:** You MAY read and write to .fusion/memory.md at the project root to save durable project learnings (architecture patterns, conventions, pitfalls).
|
||||
- **Exception — Project memory:** You MAY read and write to files under .fusion/memory/ at the project root (MEMORY.md for durable learnings, YYYY-MM-DD.md for daily notes) to save durable project learnings.
|
||||
- **Exception — Task attachments:** You MAY read files under .fusion/tasks/{taskId}/attachments/ at the project root for context screenshots and documents attached to this task.
|
||||
- **Shell commands** run inside the worktree by default. Avoid using cd to navigate outside the worktree.
|
||||
|
||||
|
||||
@@ -124,8 +124,8 @@ describe("memory-compaction", () => {
|
||||
it("should prompt to write compacted content to file", () => {
|
||||
const automation = createAutoSummarizeAutomation({});
|
||||
|
||||
expect(automation.steps![0].prompt).toContain(".fusion/memory.md");
|
||||
expect(automation.steps![0].prompt).toContain(".fusion/memory.md");
|
||||
expect(automation.steps![0].prompt).toContain(".fusion/memory/MEMORY.md");
|
||||
expect(automation.steps![0].prompt).toContain(".fusion/memory/MEMORY.md");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -254,7 +254,7 @@ export function createAutoSummarizeAutomation(
|
||||
|
||||
## Your Task
|
||||
|
||||
1. Read the working memory file at \`.fusion/memory.md\` using your file reading tools
|
||||
1. Read the working memory file at \`.fusion/memory/MEMORY.md\` using your file reading tools
|
||||
2. Check if the file size exceeds the threshold of ${threshold} characters
|
||||
3. If the file is BELOW the threshold: output JSON indicating no compaction needed:
|
||||
\`\`\`json
|
||||
@@ -263,7 +263,7 @@ export function createAutoSummarizeAutomation(
|
||||
4. If the file is AT OR ABOVE the threshold:
|
||||
a) Distill the memory to ONLY the most important insights
|
||||
b) Preserve at least 2 of these 3 core sections: Architecture, Conventions, Pitfalls
|
||||
c) Write the compacted content back to \`.fusion/memory.md\`
|
||||
c) Write the compacted content back to \`.fusion/memory/MEMORY.md\`
|
||||
d) Output JSON indicating compaction was done:
|
||||
\`\`\`json
|
||||
{"skipped": false, "originalSize": <size_before>, "newSize": <size_after>, "reduction": "<percentage>%"}
|
||||
@@ -287,7 +287,7 @@ export function createAutoSummarizeAutomation(
|
||||
**CRITICAL REQUIREMENTS:**
|
||||
- You MUST preserve at least 2 of these 3 core sections: Architecture, Conventions, Pitfalls
|
||||
- Output ONLY valid JSON — no markdown fences, no extra text
|
||||
- Use your file writing tools to update \`.fusion/memory.md\` with the compacted content`;
|
||||
- Use your file writing tools to update \`.fusion/memory/MEMORY.md\` with the compacted content`;
|
||||
|
||||
return {
|
||||
name: AUTO_SUMMARIZE_SCHEDULE_NAME,
|
||||
|
||||
@@ -796,7 +796,7 @@ export function createInsightExtractionAutomation(
|
||||
|
||||
## Instructions
|
||||
|
||||
1. Read the working memory file at \`.fusion/memory.md\` using your file reading tools
|
||||
1. Read the working memory file at \`.fusion/memory/MEMORY.md\` using your file reading tools
|
||||
2. Read the existing insights file at \`.fusion/memory-insights.md\` (it may not exist yet)
|
||||
3. Analyze the working memory content and identify:
|
||||
a) **New insights** that should be preserved in long-term memory
|
||||
@@ -835,7 +835,7 @@ After extracting insights, also produce a PRUNED version of working memory conta
|
||||
**CRITICAL REQUIREMENTS:**
|
||||
- You MUST preserve at least 2 of these 3 core sections: Architecture, Conventions, Pitfalls
|
||||
- If working memory doesn't have enough durable content to justify pruning, omit \`prunedMemory\` entirely (do not force a prune)
|
||||
- The \`prunedMemory\` field should be a complete, valid markdown file that can replace \`.fusion/memory.md\`
|
||||
- The \`prunedMemory\` field should be a complete, valid markdown file that can replace \`.fusion/memory/MEMORY.md\`
|
||||
|
||||
## Output Format
|
||||
|
||||
@@ -1149,7 +1149,7 @@ export async function generateMemoryAudit(
|
||||
id: "working-memory-exists",
|
||||
name: "Working memory file exists",
|
||||
passed: false,
|
||||
details: "File .fusion/memory.md does not exist",
|
||||
details: "File .fusion/memory/MEMORY.md does not exist",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1258,7 +1258,7 @@ export interface ProjectSettings {
|
||||
setupScript?: string;
|
||||
/** When true, enables periodic AI-powered extraction of insights from working memory
|
||||
* into a distilled long-term memory file. Creates an automation schedule that reads
|
||||
* `.fusion/memory.md`, identifies patterns/principles/pitfalls, and writes to
|
||||
* `.fusion/memory/MEMORY.md`, identifies patterns/principles/pitfalls, and writes to
|
||||
* `.fusion/memory-insights.md`. Default: false. */
|
||||
insightExtractionEnabled?: boolean;
|
||||
/** Cron expression for insight extraction schedule. Only used when
|
||||
@@ -1269,15 +1269,15 @@ export interface ProjectSettings {
|
||||
* Extraction only runs if BOTH this time has elapsed AND memory has grown
|
||||
* by more than MIN_INSIGHT_GROWTH_CHARS characters. Default: 86400000 (24h). */
|
||||
insightExtractionMinIntervalMs?: number;
|
||||
/** When enabled, agents will consult and update .fusion/memory.md with durable
|
||||
/** When enabled, agents will consult and update the .fusion/memory/ directory with durable
|
||||
* project learnings. When disabled, agents will not include memory instructions
|
||||
* in their prompts and will not read or write to .fusion/memory.md.
|
||||
* in their prompts and will not read or write to the .fusion/memory/ directory.
|
||||
* Default: true (enabled for backward compatibility). */
|
||||
memoryEnabled?: boolean;
|
||||
/** Memory backend type for pluggable memory storage.
|
||||
* Available built-in backends:
|
||||
* - "qmd": QMD (Quantized Memory Distillation) backend using the qmd CLI tool (default)
|
||||
* - "file": File-based backend storing memory in `.fusion/memory.md`
|
||||
* - "file": File-based backend storing memory in `.fusion/memory/MEMORY.md`
|
||||
* - "readonly": Read-only backend that returns empty memory (for external management)
|
||||
* - Any registered custom backend type
|
||||
* Default: "qmd" */
|
||||
|
||||
@@ -95,7 +95,7 @@ function countTotalInsights(categories: ParsedInsightCategory[]): number {
|
||||
function getBackendDisplayName(backend: string): string {
|
||||
switch (backend) {
|
||||
case "file":
|
||||
return "File (.fusion/memory.md)";
|
||||
return "File (.fusion/memory/MEMORY.md)";
|
||||
case "readonly":
|
||||
return "Read-Only";
|
||||
case "qmd":
|
||||
|
||||
@@ -2114,7 +2114,7 @@ describe("buildExecutionPrompt", () => {
|
||||
"# test",
|
||||
"## Context to Read First",
|
||||
"- `/home/user/project/web/app/page.tsx`",
|
||||
"- `/home/user/project/.fusion/memory.md`",
|
||||
"- `/home/user/project/.fusion/memory/MEMORY.md`",
|
||||
"## Steps",
|
||||
"### Step 0: Preflight",
|
||||
"- [ ] inspect `/home/user/project/web/app/layout.tsx`",
|
||||
@@ -2130,8 +2130,8 @@ describe("buildExecutionPrompt", () => {
|
||||
|
||||
expect(result).toContain("/home/user/project/.worktrees/happy-robin/web/app/page.tsx");
|
||||
expect(result).toContain("/home/user/project/.worktrees/happy-robin/web/app/layout.tsx");
|
||||
expect(result).toContain("/home/user/project/.fusion/memory.md");
|
||||
expect(result).not.toContain("/home/user/project/.worktrees/happy-robin/.fusion/memory.md");
|
||||
expect(result).toContain("/home/user/project/.fusion/memory/MEMORY.md");
|
||||
expect(result).not.toContain("/home/user/project/.worktrees/happy-robin/.fusion/memory/MEMORY.md");
|
||||
});
|
||||
|
||||
it("omits attachment section when no attachments", () => {
|
||||
@@ -2417,7 +2417,7 @@ describe("buildExecutionPrompt", () => {
|
||||
} as any);
|
||||
expect(result).toContain("Execute this task.");
|
||||
expect(result).toContain("## Project Memory");
|
||||
expect(result).toContain(".fusion/memory.md");
|
||||
expect(result).toContain(".fusion/memory/");
|
||||
});
|
||||
|
||||
it("excludes memory instructions when memoryEnabled: false", () => {
|
||||
@@ -2434,7 +2434,7 @@ describe("buildExecutionPrompt", () => {
|
||||
const result = buildExecutionPrompt(task, "/project", {} as any);
|
||||
expect(result).toContain("Execute this task.");
|
||||
expect(result).toContain("## Project Memory");
|
||||
expect(result).toContain(".fusion/memory.md");
|
||||
expect(result).toContain(".fusion/memory/");
|
||||
});
|
||||
|
||||
it("includes selective memory write instruction for durable learnings at end of execution", () => {
|
||||
@@ -2456,22 +2456,22 @@ describe("buildExecutionPrompt", () => {
|
||||
const result = buildExecutionPrompt(task, "/project", {
|
||||
memoryEnabled: true,
|
||||
} as any);
|
||||
expect(result).toContain("`.fusion/memory.md`");
|
||||
expect(result).toContain(".fusion/memory/");
|
||||
});
|
||||
});
|
||||
|
||||
describe("memoryBackendType setting", () => {
|
||||
it("includes .fusion/memory.md for file backend", () => {
|
||||
it("includes .fusion/memory/ guidance 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
|
||||
// Check that the Project Memory section contains .fusion/memory/ guidance
|
||||
const memorySectionMatch = result.match(/## Project Memory\n([\s\S]*?)(?=\n## [^#]|$)/);
|
||||
expect(memorySectionMatch).toBeTruthy();
|
||||
expect(memorySectionMatch![1]).toContain(".fusion/memory.md");
|
||||
expect(memorySectionMatch![1]).toContain(".fusion/memory/");
|
||||
});
|
||||
|
||||
it("includes read-only wording for readonly backend without write directives in memory section", () => {
|
||||
@@ -10620,7 +10620,7 @@ describe("buildExecutionPrompt", () => {
|
||||
|
||||
const prompt = buildExecutionPrompt(task, "/project");
|
||||
|
||||
expect(prompt).toContain(".fusion/memory.md");
|
||||
expect(prompt).toContain(".fusion/memory/");
|
||||
expect(prompt).toContain("memory");
|
||||
expect(prompt).toContain("durable");
|
||||
});
|
||||
|
||||
@@ -300,7 +300,7 @@ If the task's PROMPT.md includes a "Documentation Requirements" section listing
|
||||
You are running in an **isolated git worktree**. This means:
|
||||
|
||||
- **All code changes must be made inside the current worktree directory.** Do not modify files outside the worktree — the worktree is your isolated execution environment.
|
||||
- **Exception — Project memory:** You MAY read and write to .fusion/memory.md at the project root to save durable project learnings (architecture patterns, conventions, pitfalls).
|
||||
- **Exception — Project memory:** You MAY read and write to files under .fusion/memory/ at the project root (MEMORY.md for durable learnings, YYYY-MM-DD.md for daily notes) to save durable project learnings.
|
||||
- **Exception — Task attachments:** You MAY read files under .fusion/tasks/{taskId}/attachments/ at the project root for context screenshots and documents attached to this task.
|
||||
- **Shell commands** run inside the worktree by default. Avoid using cd to navigate outside the worktree.
|
||||
|
||||
@@ -4477,7 +4477,7 @@ ${reviewLevel >= 3 ? `After tests, also call review_step with type="code" for te
|
||||
You are running in an **isolated git worktree**. This means:
|
||||
|
||||
- **All code changes must be made inside the current worktree directory.** Do not modify files outside the worktree.
|
||||
- **Exception — Project memory:** You MAY read and write to \`.fusion/memory.md\` at the project root to save durable project learnings.
|
||||
- **Exception — Project memory:** You MAY read and write to files under \`.fusion/memory/\` at the project root (MEMORY.md for durable learnings, YYYY-MM-DD.md for daily notes).
|
||||
- **Exception — Task attachments:** You MAY read files under \`.fusion/tasks/{taskId}/attachments/\` at the project root for context.
|
||||
- **Shell commands** run inside the worktree by default. Avoid using \`cd\` to navigate outside the worktree.
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ describe("worktree path boundary helpers", () => {
|
||||
expect(mockReadTool.execute).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows project root .fusion/memory.md from worktree session", async () => {
|
||||
it("allows project root .fusion/memory/ paths from worktree session", async () => {
|
||||
const mockReadTool = {
|
||||
name: "read",
|
||||
label: "Read",
|
||||
@@ -165,17 +165,35 @@ describe("worktree path boundary helpers", () => {
|
||||
};
|
||||
|
||||
const { wrapToolsWithBoundary } = await import("./pi.js");
|
||||
|
||||
|
||||
const wrapped = wrapToolsWithBoundary(
|
||||
[mockReadTool as any],
|
||||
"/project/.worktrees/fn-001",
|
||||
"/project",
|
||||
);
|
||||
|
||||
// Reading project root .fusion/memory.md should be allowed
|
||||
const result = await (wrapped[0] as any).execute("call-1", { path: "/project/.fusion/memory.md" });
|
||||
// Reading project root .fusion/memory.md (legacy) should be allowed
|
||||
const legacyResult = await (wrapped[0] as any).execute("call-1", { path: "/project/.fusion/memory.md" });
|
||||
expect(mockReadTool.execute).toHaveBeenCalled();
|
||||
expect(result).toEqual({ ok: true, content: [{ type: "text", text: "memory content" }] });
|
||||
expect(legacyResult).toEqual({ ok: true, content: [{ type: "text", text: "memory content" }] });
|
||||
|
||||
// Reading project root .fusion/memory/MEMORY.md should also be allowed
|
||||
mockReadTool.execute.mockClear();
|
||||
const memoryResult = await (wrapped[0] as any).execute("call-2", { path: "/project/.fusion/memory/MEMORY.md" });
|
||||
expect(mockReadTool.execute).toHaveBeenCalled();
|
||||
expect(memoryResult).toEqual({ ok: true, content: [{ type: "text", text: "memory content" }] });
|
||||
|
||||
// Reading project root .fusion/memory/2026-04-18.md should also be allowed
|
||||
mockReadTool.execute.mockClear();
|
||||
const dailyResult = await (wrapped[0] as any).execute("call-3", { path: "/project/.fusion/memory/2026-04-18.md" });
|
||||
expect(mockReadTool.execute).toHaveBeenCalled();
|
||||
expect(dailyResult).toEqual({ ok: true, content: [{ type: "text", text: "memory content" }] });
|
||||
|
||||
// Reading project root .fusion/memory/DREAMS.md should also be allowed
|
||||
mockReadTool.execute.mockClear();
|
||||
const dreamsResult = await (wrapped[0] as any).execute("call-4", { path: "/project/.fusion/memory/DREAMS.md" });
|
||||
expect(mockReadTool.execute).toHaveBeenCalled();
|
||||
expect(dreamsResult).toEqual({ ok: true, content: [{ type: "text", text: "memory content" }] });
|
||||
});
|
||||
|
||||
it("allows task attachments from worktree session", async () => {
|
||||
|
||||
@@ -559,7 +559,7 @@ async function assertValidWorktreeSession(cwd: string, projectRoot: string): Pro
|
||||
* Check if a path is allowed to be accessed from a worktree session.
|
||||
* Rules:
|
||||
* - Paths inside the worktree are always allowed
|
||||
* - Project root .fusion/memory.md is allowed (for durable project learnings)
|
||||
* - Project root .fusion/memory/ directory is allowed (MEMORY.md, YYYY-MM-DD.md, DREAMS.md)
|
||||
* - Task attachments under .fusion/tasks/N/attachments/ are allowed (for reading context files)
|
||||
* - All other paths outside the worktree are rejected
|
||||
*
|
||||
@@ -580,9 +580,9 @@ function isWorktreeAllowedPath(worktreePath: string, projectRoot: string, reques
|
||||
return true; // Path is inside the worktree
|
||||
}
|
||||
|
||||
// Exception: project root `.fusion/memory.md` for durable project learnings
|
||||
// Exception: project root `.fusion/memory/` directory for durable project learnings
|
||||
const relToProjectRoot = relative(projectRootResolved, requestedResolved);
|
||||
if (relToProjectRoot === ".fusion/memory.md") {
|
||||
if (relToProjectRoot === ".fusion/memory.md" || relToProjectRoot.startsWith(".fusion/memory/")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -640,7 +640,7 @@ export function wrapToolsWithBoundary(
|
||||
ok: false,
|
||||
error: `Path "${relToProject}" is outside the worktree boundary. ` +
|
||||
`Coding agents can only modify files inside the current worktree. ` +
|
||||
`Exception: .fusion/memory.md (project root) and .fusion/tasks/*/attachments/* are permitted for reading.`,
|
||||
`Exception: .fusion/memory/ directory (project root) and .fusion/tasks/*/attachments/* are permitted for reading.`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -483,7 +483,7 @@ describe("REVIEWER_SYSTEM_PROMPT", () => {
|
||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("Worktree Boundary Review");
|
||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("assigned task worktree");
|
||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("blocking REVISE");
|
||||
expect(REVIEWER_SYSTEM_PROMPT).toContain(".fusion/memory.md");
|
||||
expect(REVIEWER_SYSTEM_PROMPT).toContain(".fusion/memory/");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -633,7 +633,7 @@ describe("reviewStep — user comments in spec review", () => {
|
||||
expect(capturedPrompt).toContain("## Worktree Boundary");
|
||||
expect(capturedPrompt).toContain("Assigned task worktree: `/tmp/project/.worktrees/happy-robin`");
|
||||
expect(capturedPrompt).toContain("primary project checkout");
|
||||
expect(capturedPrompt).toContain(".fusion/memory.md");
|
||||
expect(capturedPrompt).toContain(".fusion/memory/");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ For code reviews, verify that implementation changes are in the assigned task
|
||||
worktree. The review request includes the current worktree path. Inspect git
|
||||
state and recent commits from that worktree, and treat changes outside it as a
|
||||
blocking REVISE unless they are expected project-root state such as
|
||||
\`.fusion/memory.md\`, task attachments, or other explicitly documented
|
||||
\`.fusion/memory/\` directory (MEMORY.md, YYYY-MM-DD.md, DREAMS.md), task attachments, or other explicitly documented
|
||||
Fusion metadata. If you see edits or commits in the primary project checkout
|
||||
instead of the task worktree, call that out directly and ask the worker to move
|
||||
the changes into the assigned worktree.
|
||||
@@ -478,7 +478,7 @@ function buildReviewRequest(
|
||||
"",
|
||||
"## Worktree Boundary",
|
||||
`Assigned task worktree: \`${cwd}\``,
|
||||
"Verify that implementation changes are in this worktree. If you find changes or commits in the primary project checkout or any other path, issue REVISE unless the outside path is an expected project-root exception such as .fusion/memory.md, task attachments, or explicitly documented Fusion metadata.",
|
||||
"Verify that implementation changes are in this worktree. If you find changes or commits in the primary project checkout or any other path, issue REVISE unless the outside path is an expected project-root exception such as .fusion/memory/ (MEMORY.md, daily notes, dreams), task attachments, or explicitly documented Fusion metadata.",
|
||||
"",
|
||||
);
|
||||
if (baseline) {
|
||||
|
||||
@@ -459,7 +459,7 @@ Do important work.
|
||||
"`/repo/project/packages/engine/src/new-module.ts`",
|
||||
).replace(
|
||||
"- `src/types.ts`",
|
||||
"- `/repo/project/.fusion/memory.md`",
|
||||
"- `/repo/project/.fusion/memory/MEMORY.md`",
|
||||
);
|
||||
const task = makeTaskDetail({ prompt });
|
||||
const result = buildStepPrompt(
|
||||
@@ -471,8 +471,8 @@ Do important work.
|
||||
);
|
||||
|
||||
expect(result).toContain("/repo/project/.worktrees/happy-robin/packages/engine/src/new-module.ts");
|
||||
expect(result).toContain("/repo/project/.fusion/memory.md");
|
||||
expect(result).not.toContain("/repo/project/.worktrees/happy-robin/.fusion/memory.md");
|
||||
expect(result).toContain("/repo/project/.fusion/memory/MEMORY.md");
|
||||
expect(result).not.toContain("/repo/project/.worktrees/happy-robin/.fusion/memory/MEMORY.md");
|
||||
});
|
||||
|
||||
it("handles step 0 (preflight) correctly", () => {
|
||||
|
||||
Reference in New Issue
Block a user