( memoryFiles: MemoryFileInfo[], toolUseContext: ToolUseContext, triggerFilePath?: string, )
| 1714 | |
| 1715 | /** Exported for testing — regression guard for LRU-eviction re-injection. */ |
| 1716 | export function memoryFilesToAttachments( |
| 1717 | memoryFiles: MemoryFileInfo[], |
| 1718 | toolUseContext: ToolUseContext, |
| 1719 | triggerFilePath?: string, |
| 1720 | ): Attachment[] { |
| 1721 | const attachments: Attachment[] = [] |
| 1722 | const shouldFireHook = hasInstructionsLoadedHook() |
| 1723 | |
| 1724 | for (const memoryFile of memoryFiles) { |
| 1725 | // Dedup: loadedNestedMemoryPaths is a non-evicting Set; readFileState |
| 1726 | // is a 100-entry LRU that drops entries in busy sessions, so relying |
| 1727 | // on it alone re-injects the same CLAUDE.md on every eviction cycle. |
| 1728 | if (toolUseContext.loadedNestedMemoryPaths?.has(memoryFile.path)) { |
| 1729 | continue |
| 1730 | } |
| 1731 | if (!toolUseContext.readFileState.has(memoryFile.path)) { |
| 1732 | attachments.push({ |
| 1733 | type: 'nested_memory', |
| 1734 | path: memoryFile.path, |
| 1735 | content: memoryFile, |
| 1736 | displayPath: relative(getCwd(), memoryFile.path), |
| 1737 | }) |
| 1738 | toolUseContext.loadedNestedMemoryPaths?.add(memoryFile.path) |
| 1739 | |
| 1740 | // Mark as loaded in readFileState — this provides cross-function and |
| 1741 | // cross-turn dedup via the .has() check above. |
| 1742 | // |
| 1743 | // When the injected content doesn't match disk (stripped HTML comments, |
| 1744 | // stripped frontmatter, truncated MEMORY.md), cache the RAW disk bytes |
| 1745 | // with `isPartialView: true`. Edit/Write see the flag and require a real |
| 1746 | // Read first; getChangedFiles sees real content + undefined offset/limit |
| 1747 | // so mid-session change detection still works. |
| 1748 | toolUseContext.readFileState.set(memoryFile.path, { |
| 1749 | content: memoryFile.contentDiffersFromDisk |
| 1750 | ? (memoryFile.rawContent ?? memoryFile.content) |
| 1751 | : memoryFile.content, |
| 1752 | timestamp: Date.now(), |
| 1753 | offset: undefined, |
| 1754 | limit: undefined, |
| 1755 | isPartialView: memoryFile.contentDiffersFromDisk, |
| 1756 | }) |
| 1757 | |
| 1758 | |
| 1759 | // Fire InstructionsLoaded hook for audit/observability (fire-and-forget) |
| 1760 | if (shouldFireHook && isInstructionsMemoryType(memoryFile.type)) { |
| 1761 | const loadReason = memoryFile.globs |
| 1762 | ? 'path_glob_match' |
| 1763 | : memoryFile.parent |
| 1764 | ? 'include' |
| 1765 | : 'nested_traversal' |
| 1766 | void executeInstructionsLoadedHooks( |
| 1767 | memoryFile.path, |
| 1768 | memoryFile.type, |
| 1769 | loadReason, |
| 1770 | { |
| 1771 | globs: memoryFile.globs, |
| 1772 | triggerFilePath, |
| 1773 | parentFilePath: memoryFile.parent, |
no test coverage detected