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