diff --git a/docs/architecture.md b/docs/architecture.md index bf6c218be..af28fc796 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -242,7 +242,7 @@ Fusion supports OpenClaw-style project memory with legacy fallback support: - Long-term: `.fusion/memory/MEMORY.md` - Daily notes: `.fusion/memory/YYYY-MM-DD.md` - Dream processing: `.fusion/memory/DREAMS.md` -- Legacy fallback (still supported): `.fusion/memory.md` +- Legacy fallback (deprecated compatibility path): `.fusion/memory.md` **Memory subsystems:** - `memory-backend.ts` — backend contracts + file/readonly/qmd implementations @@ -485,7 +485,7 @@ SQLite schema is initialized in `packages/core/src/db.ts` and uses: - `.fusion/memory/MEMORY.md` - `.fusion/memory/YYYY-MM-DD.md` - `.fusion/memory/DREAMS.md` -- Legacy fallback still supported: `.fusion/memory.md` +- Legacy fallback (deprecated compatibility path): `.fusion/memory.md` ### File-based side stores Some data remains intentionally filesystem-based: diff --git a/docs/contributing.md b/docs/contributing.md index 0b6d13e6a..1d4e12a9e 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -104,23 +104,24 @@ Use task-ID-scoped conventional commits: ## Project Memory -When enabled, Fusion supports both the OpenClaw-style memory workspace and legacy compatibility paths: +When enabled, Fusion uses OpenClaw-style memory files as the canonical project memory workspace: -- `.fusion/memory/MEMORY.md` — OpenClaw long-term memory file -- `.fusion/memory/YYYY-MM-DD.md` — OpenClaw daily running notes -- `.fusion/memory/DREAMS.md` — OpenClaw dream-processing memory file -- `.fusion/memory.md` — Legacy compatibility path (still used by parts of the memory summarization pipeline) +- `.fusion/memory/MEMORY.md` — curated long-term memory for durable decisions, conventions, and pitfalls +- `.fusion/memory/YYYY-MM-DD.md` — append-only daily running notes +- `.fusion/memory/DREAMS.md` — synthesized patterns distilled from daily notes +- `.fusion/memory.md` — deprecated legacy fallback path (kept for backward compatibility) Use project memory for reusable patterns, constraints, and pitfalls that should persist across tasks. ### Background Memory Summarization -Fusion can automatically extract insights from working memory and prune transient content. Enable via `insightExtractionEnabled` setting: +Fusion can automatically extract insights from memory and prune transient content. Enable via `insightExtractionEnabled` setting: -- `.fusion/memory.md` — Working memory source currently compacted/pruned by extraction jobs -- `.fusion/memory/MEMORY.md` — OpenClaw long-term memory workspace file used by memory tooling/search -- `.fusion/memory-insights.md` — Distilled insights output -- `.fusion/memory-audit.md` — Audit report after each extraction (includes pruning outcome) +- `.fusion/memory/MEMORY.md` — canonical long-term memory target for durable knowledge +- `.fusion/memory/YYYY-MM-DD.md` — daily notes source used for ongoing synthesis +- `.fusion/memory/DREAMS.md` — synthesized memory patterns +- `.fusion/memory-insights.md` — legacy distilled insights artifact (still generated by the current extraction pipeline) +- `.fusion/memory-audit.md` — legacy audit artifact for extraction runs See [Settings Reference](./settings-reference.md#background-memory-summarization--audit) for configuration details. diff --git a/docs/memory-plugin-contract.md b/docs/memory-plugin-contract.md index 6f54419a0..c6a6f7bdc 100644 --- a/docs/memory-plugin-contract.md +++ b/docs/memory-plugin-contract.md @@ -29,7 +29,7 @@ This document defines the pluggable memory backend contract for Fusion, translat ```typescript // Constants -MEMORY_FILE_PATH: ".fusion/memory.md" +MEMORY_FILE_PATH: ".fusion/memory/MEMORY.md" // Functions memoryFilePath(rootDir: string): string @@ -41,7 +41,8 @@ readProjectMemory(rootDir: string): Promise // Read current content ``` **Key invariants:** -- `MEMORY_FILE_PATH` is always `.fusion/memory.md` (project-root relative, NOT worktree-local) +- Canonical memory path is `.fusion/memory/MEMORY.md` (project-root relative, NOT worktree-local) +- Legacy fallback `.fusion/memory.md` remains readable for backward compatibility - `ensureMemoryFile` is idempotent: safe to call multiple times; never overwrites existing content - Bootstrap failure is non-fatal: `store.ts` wraps the call in try/catch @@ -49,12 +50,12 @@ readProjectMemory(rootDir: string): Promise // Read current content The module implements a two-tier memory architecture with automatic pruning: -1. **Working memory** (`memory.md`) — agent-maintained, manually edited, automatically pruned -2. **Insights memory** (`memory-insights.md`) — AI-extracted distilled knowledge +1. **Working memory** (`.fusion/memory/MEMORY.md`, with legacy `.fusion/memory.md` fallback) — agent-maintained, manually edited, automatically pruned +2. **Insights memory** (`.fusion/memory-insights.md`) — AI-extracted distilled knowledge ```typescript // Constants -MEMORY_WORKING_PATH: ".fusion/memory.md" +MEMORY_WORKING_PATH: ".fusion/memory/MEMORY.md" MEMORY_INSIGHTS_PATH: ".fusion/memory-insights.md" DEFAULT_INSIGHT_SCHEDULE: "0 2 * * *" // Daily at 2 AM DEFAULT_MIN_INTERVAL_MS: 86400000 // 24 hours @@ -90,7 +91,7 @@ MemoryAuditReport: { ..., pruning: { applied: boolean, reason: string, sizeDelta **Pruning behavior (FN-1477):** - AI response may include `prunedMemory` field with a pruned working memory candidate - `validatePruneCandidate()` checks that at least 2 of 3 required sections are preserved (Architecture, Conventions, Pitfalls) -- `applyMemoryPruning()` only writes to `.fusion/memory.md` if validation passes +- `applyMemoryPruning()` only writes to `.fusion/memory/MEMORY.md` if validation passes - Invalid prune candidates are safely ignored; existing memory is preserved - Pruning outcome is included in audit reports for operator visibility @@ -103,7 +104,7 @@ MemoryAuditReport: { ..., pruning: { applied: boolean, reason: string, sizeDelta interface ProjectSettings { // ... other fields ... - /** When true, agents will consult and update .fusion/memory.md. + /** When true, agents will consult and update .fusion/memory/MEMORY.md. * Default: true (enabled for backward compatibility). */ memoryEnabled?: boolean; @@ -169,9 +170,9 @@ if (memoryEnabled && rootDir) { | Route | Method | Description | |-------|--------|-------------| | `/api/memory` | GET | Returns `{ content: string }` — empty string if file absent | -| `/api/memory` | PUT | Body: `{ content: string }` — writes to `.fusion/memory.md` | +| `/api/memory` | PUT | Body: `{ content: string }` — writes to canonical `.fusion/memory/MEMORY.md` (legacy fallback remains supported) | -Both routes use `readProjectFile` / `writeProjectFile` from `file-service.ts`, which enforces project-root path constraints (memory path is `.fusion/memory.md` — always within project scope). +Both routes use `readProjectFile` / `writeProjectFile` from `file-service.ts`, which enforces project-root path constraints (canonical memory path is `.fusion/memory/MEMORY.md`, with `.fusion/memory.md` treated as a compatibility fallback). ### 1.6 Dashboard Settings UI @@ -193,7 +194,7 @@ Both routes use `readProjectFile` / `writeProjectFile` from `file-service.ts`, w ### 1.8 Summary of Non-Negotiable Behaviors -1. **File path**: Memory always lives at `.fusion/memory.md` (project root, not worktree) +1. **File path**: Canonical memory lives at `.fusion/memory/MEMORY.md` (project root, not worktree); legacy `.fusion/memory.md` remains a compatibility fallback 2. **Toggle**: `memoryEnabled` controls all memory behavior — when `false`, no instructions injected, no reads/writes 3. **Default**: `memoryEnabled: true` (backward-compatible default) 4. **Idempotent bootstrap**: `ensureMemoryFile` never overwrites existing content @@ -269,7 +270,7 @@ OpenClaw backends fall back to a default (file-based) backend when: | Auto-flush | Manual writes via dashboard | Backend should handle internal buffering | | Search capability | Not implemented | Optional `search()` method in interface | | Fallback semantics | Always file-based | Need explicit fallback chain | -| Path abstraction | Hardcoded `.fusion/memory.md` | Backend config includes `rootDir` | +| Path abstraction | Canonical `.fusion/memory/MEMORY.md` with legacy fallback support | Backend config includes `rootDir` | ### 2.3 OpenClaw Memory Architecture Sources @@ -526,7 +527,7 @@ export type MemoryBackendFactory = ( ```typescript /** * Default file-based memory backend. - * Preserves exact current behavior: .fusion/memory.md on project root. + * Preserves canonical behavior: .fusion/memory/MEMORY.md on project root (with legacy .fusion/memory.md fallback). * * This backend is always registered as the fallback when no other backend * is configured or when configured backend is unavailable. @@ -713,24 +714,26 @@ The following return shapes are **contractually guaranteed** by all backends and | Function | Current Behavior | Contract Requirement | |----------|----------------|---------------------| | `GET /api/memory` → `{ content }` | Empty string if file absent | Backend `read()` must return `""` when no content exists | -| `readWorkingMemory(rootDir)` | `""` if `.fusion/memory.md` absent | Same — empty string NOT `null` | +| `readWorkingMemory(rootDir)` | `""` if `.fusion/memory/MEMORY.md` absent | Same — empty string NOT `null` | | `readInsightsMemory(rootDir)` | `null` if `.fusion/memory-insights.md` absent | Same — `null` NOT `""` | #### 3.8.2 Canonical Source-of-Truth **For the `"file"` backend (default):** -- The file `.fusion/memory.md` IS the canonical source -- All reads/writes go directly to the file +- The file `.fusion/memory/MEMORY.md` IS the canonical source +- Legacy `.fusion/memory.md` remains readable as a backward-compatible fallback +- All reads/writes target canonical memory semantics - No mirroring or sync required **For alternative backends:** - The backend is the canonical source for memory content -- **File mirroring is NOT required** — alternative backends need not maintain `.fusion/memory.md` -- If a backend stores content externally (database, cloud), `.fusion/memory.md` may be stale or absent +- **File mirroring is NOT required** — alternative backends need not maintain `.fusion/memory/MEMORY.md` +- If a backend stores content externally (database, cloud), `.fusion/memory/MEMORY.md` may be stale or absent +- Legacy `.fusion/memory.md` may exist but should be treated as compatibility-only data #### 3.8.3 File Bridge Adapter (For Dashboard/Agent Compatibility) -When using alternative backends, the dashboard and engine must still work with the canonical path `.fusion/memory.md`. This is achieved through an **adapter layer**: +When using alternative backends, the dashboard and engine must still work with the canonical path `.fusion/memory/MEMORY.md`. This is achieved through an **adapter layer**: ```typescript /** @@ -779,7 +782,7 @@ Dashboard routes (`/api/memory`) use `readProjectFile`/`writeProjectFile` from ` | Constraint | Source | Requirement | |------------|--------|-------------| -| Path validation | `file-service.ts:55` | Memory path `.fusion/memory.md` must be within project scope | +| Path validation | `file-service.ts:55` | Canonical memory path `.fusion/memory/MEMORY.md` (and legacy fallback `.fusion/memory.md`) must be within project scope | | File size limit | `MAX_FILE_SIZE = 1MB` | Backend writes must not exceed 1MB | | Text encoding | `utf-8` | All content encoded as UTF-8 | @@ -816,9 +819,9 @@ Prompt instructions branch based on the configured memory backend type via `reso | Backend Type | Path Hint | Behavior | |-------------|-----------|-----------| -| `file` | `.fusion/memory.md` | Full read/write instructions with explicit file path | +| `file` | `.fusion/memory/MEMORY.md` | Full read/write instructions with explicit file path | | `readonly` | `null` | Read-only instructions, no write/update directives | -| `qmd` / non-file | `null` | Generic instructions without unconditional `.fusion/memory.md` reference | +| `qmd` / non-file | `null` | Generic instructions without unconditional `.fusion/memory/MEMORY.md` reference | **API functions:** @@ -828,7 +831,7 @@ Prompt instructions branch based on the configured memory backend type via `reso **Behavior details:** -- **File backend**: Instructions include `.fusion/memory.md` guidance and read/write directives +- **File backend**: Instructions include `.fusion/memory/MEMORY.md` guidance and read/write directives (plus legacy `.fusion/memory.md` fallback context) - **Readonly backend**: Instructions include read-only wording but no write/update directives - **QMD/non-file backends**: Instructions are generic without assuming a file path; agents consult the project memory through backend-specific mechanisms - **Backward compatibility**: When `memoryBackendType` is omitted or unknown, defaults to file behavior @@ -891,10 +894,10 @@ Prompt instructions branch based on the configured memory backend type via `reso ### 4.3 Must-Not-Break Invariants -1. **File path invariant**: Memory always accessible at `.fusion/memory.md` (file backend is always available as fallback) +1. **File path invariant**: Canonical memory is always accessible at `.fusion/memory/MEMORY.md`, with `.fusion/memory.md` supported as legacy fallback 2. **Toggle invariant**: `memoryEnabled: false` always means zero memory **prompt** operations — agent instructions are NOT injected, but `GET /api/memory` remains readable and `PUT /api/memory` remains writable 3. **Bootstrap invariant**: `ensureMemoryFile` is always called on init when `memoryEnabled !== false` -4. **Prompt invariant**: Memory instructions always use project-root path (`.fusion/memory.md`), never worktree-local +4. **Prompt invariant**: Memory instructions always use project-root canonical paths (`.fusion/memory/MEMORY.md` and `.fusion/memory/*`), never worktree-local paths 5. **Non-fatal invariant**: Memory initialization/operation failures never block startup or settings updates 6. **Insights null invariant**: `readInsightsMemory()` returns `null` when `.fusion/memory-insights.md` is absent (not `""`) 7. **Insights write invariant**: `writeInsightsMemory()` creates `.fusion/` directory and `.fusion/memory-insights.md` if absent diff --git a/docs/settings-reference.md b/docs/settings-reference.md index 3c8322a7a..4bffa8935 100644 --- a/docs/settings-reference.md +++ b/docs/settings-reference.md @@ -390,7 +390,8 @@ Fusion can automatically extract insights from project memory and prune transien 1. **Scheduled Extraction**: When `insightExtractionEnabled` is `true`, a background automation runs on the configured `insightExtractionSchedule` (default: daily at 2 AM). -2. **AI-Powered Analysis**: The automation uses an AI agent to read `.fusion/memory.md` and `.fusion/memory-insights.md`, extract new insights, and produce a pruned working memory candidate. +2. **AI-Powered Analysis**: The automation uses an AI agent to read canonical OpenClaw memory files (`.fusion/memory/MEMORY.md`, `.fusion/memory/YYYY-MM-DD.md`) plus compatibility insight artifacts (`.fusion/memory-insights.md`), then extract new insights and produce a pruned working memory candidate. + - Legacy fallback note: `.fusion/memory.md` remains supported for backward compatibility in older projects. 3. **Insight Merging**: New insights are automatically merged into `.fusion/memory-insights.md` under the appropriate category (Patterns, Principles, Conventions, Pitfalls, Context). Duplicates are skipped. @@ -410,9 +411,11 @@ Fusion can automatically extract insights from project memory and prune transien | File | Description | |------|-------------| -| `.fusion/memory.md` | Working memory (updated when pruning is applied and validated) | -| `.fusion/memory-insights.md` | Long-term insights distilled from working memory | -| `.fusion/memory-audit.md` | Human-readable audit report after each extraction | +| `.fusion/memory/MEMORY.md` | Canonical long-term memory file (updated with durable content) | +| `.fusion/memory/YYYY-MM-DD.md` | Daily running notes used during synthesis/pruning workflows | +| `.fusion/memory/DREAMS.md` | Dream synthesis output derived from daily notes | +| `.fusion/memory-insights.md` | Legacy distilled-insights artifact kept for compatibility | +| `.fusion/memory-audit.md` | Legacy human-readable audit report after each extraction | ### Settings Interaction diff --git a/packages/core/src/agent-prompts.test.ts b/packages/core/src/agent-prompts.test.ts index 774992212..d8f9b2fc8 100644 --- a/packages/core/src/agent-prompts.test.ts +++ b/packages/core/src/agent-prompts.test.ts @@ -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", () => { diff --git a/packages/core/src/agent-prompts.ts b/packages/core/src/agent-prompts.ts index e2ff2e5e1..ec1ac4450 100644 --- a/packages/core/src/agent-prompts.ts +++ b/packages/core/src/agent-prompts.ts @@ -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. diff --git a/packages/core/src/memory-compaction.test.ts b/packages/core/src/memory-compaction.test.ts index a4f0804cf..b7708ae44 100644 --- a/packages/core/src/memory-compaction.test.ts +++ b/packages/core/src/memory-compaction.test.ts @@ -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"); }); }); diff --git a/packages/core/src/memory-compaction.ts b/packages/core/src/memory-compaction.ts index af09945b7..d217f82e1 100644 --- a/packages/core/src/memory-compaction.ts +++ b/packages/core/src/memory-compaction.ts @@ -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": , "newSize": , "reduction": "%"} @@ -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, diff --git a/packages/core/src/memory-insights.ts b/packages/core/src/memory-insights.ts index e182d61c6..bb2f4ce24 100644 --- a/packages/core/src/memory-insights.ts +++ b/packages/core/src/memory-insights.ts @@ -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", }); } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 6296da135..218e0bc51 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -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" */ diff --git a/packages/dashboard/app/components/MemoryView.tsx b/packages/dashboard/app/components/MemoryView.tsx index 9b30d5d4f..102db02b8 100644 --- a/packages/dashboard/app/components/MemoryView.tsx +++ b/packages/dashboard/app/components/MemoryView.tsx @@ -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": diff --git a/packages/engine/src/executor.test.ts b/packages/engine/src/executor.test.ts index 15d7323ad..ad906f25d 100644 --- a/packages/engine/src/executor.test.ts +++ b/packages/engine/src/executor.test.ts @@ -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"); }); diff --git a/packages/engine/src/executor.ts b/packages/engine/src/executor.ts index 940c5013e..ed01912e2 100644 --- a/packages/engine/src/executor.ts +++ b/packages/engine/src/executor.ts @@ -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. diff --git a/packages/engine/src/pi-create-kb-agent.test.ts b/packages/engine/src/pi-create-kb-agent.test.ts index f471800f0..ca0ff6e17 100644 --- a/packages/engine/src/pi-create-kb-agent.test.ts +++ b/packages/engine/src/pi-create-kb-agent.test.ts @@ -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 () => { diff --git a/packages/engine/src/pi.ts b/packages/engine/src/pi.ts index f5832b248..101dc100f 100644 --- a/packages/engine/src/pi.ts +++ b/packages/engine/src/pi.ts @@ -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.`, }; } diff --git a/packages/engine/src/reviewer.test.ts b/packages/engine/src/reviewer.test.ts index dda559e9f..008ca8e13 100644 --- a/packages/engine/src/reviewer.test.ts +++ b/packages/engine/src/reviewer.test.ts @@ -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/"); }); }); diff --git a/packages/engine/src/reviewer.ts b/packages/engine/src/reviewer.ts index a57fee2c4..4f52cce68 100644 --- a/packages/engine/src/reviewer.ts +++ b/packages/engine/src/reviewer.ts @@ -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) { diff --git a/packages/engine/src/step-session-executor.test.ts b/packages/engine/src/step-session-executor.test.ts index 127ccd72a..d62f92f49 100644 --- a/packages/engine/src/step-session-executor.test.ts +++ b/packages/engine/src/step-session-executor.test.ts @@ -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", () => {