diff --git a/packages/tools/src/claude-memory.test.ts b/packages/tools/src/claude-memory.test.ts index 7337441a..622470e9 100644 --- a/packages/tools/src/claude-memory.test.ts +++ b/packages/tools/src/claude-memory.test.ts @@ -180,6 +180,80 @@ describe("ClaudeMemoryTool exact-file matching", () => { }) }) +describe("ClaudeMemoryTool insert line semantics", () => { + let tool: ClaudeMemoryTool + + beforeEach(() => { + searchExecute.mockReset() + addMock.mockReset() + mockDocument(FILE_CONTENT) + tool = new ClaudeMemoryTool("test-api-key") + }) + + // The memory_20250818 spec: insert_text is inserted AFTER line insert_line, + // 0 inserts at the beginning of the file, and the valid range is [0, n_lines]. + + it("insert_line: 0 inserts at the beginning of the file", async () => { + const result = await tool.handleCommand({ + command: "insert", + path: FILE_PATH, + insert_line: 0, + insert_text: "header", + }) + + expect(result.success).toBe(true) + const stored = addMock.mock.calls[0]?.[0]?.content as string + expect(stored).toBe("header\nline1\nline2\nline3\nline4\nline5") + }) + + it("inserts AFTER the given line, not before it", async () => { + const result = await tool.handleCommand({ + command: "insert", + path: FILE_PATH, + insert_line: 2, + insert_text: "after2", + }) + + expect(result.success).toBe(true) + const stored = addMock.mock.calls[0]?.[0]?.content as string + // Regression guard: the old 1-based insert-BEFORE landed this one line early. + expect(stored).toBe("line1\nline2\nafter2\nline3\nline4\nline5") + }) + + it("insert_line: n_lines appends at the end of the file", async () => { + const result = await tool.handleCommand({ + command: "insert", + path: FILE_PATH, + insert_line: 5, + insert_text: "tail", + }) + + expect(result.success).toBe(true) + const stored = addMock.mock.calls[0]?.[0]?.content as string + expect(stored).toBe("line1\nline2\nline3\nline4\nline5\ntail") + }) + + it("rejects insert_line outside [0, n_lines] without writing", async () => { + const below = await tool.handleCommand({ + command: "insert", + path: FILE_PATH, + insert_line: -1, + insert_text: "x", + }) + expect(below.success).toBe(false) + expect(below.error).toContain("[0, 5]") + + const above = await tool.handleCommand({ + command: "insert", + path: FILE_PATH, + insert_line: 6, + insert_text: "x", + }) + expect(above.success).toBe(false) + expect(addMock).not.toHaveBeenCalled() + }) +}) + describe("ClaudeMemoryTool str_replace replacement literalness", () => { let tool: ClaudeMemoryTool diff --git a/packages/tools/src/claude-memory.ts b/packages/tools/src/claude-memory.ts index 867c4d82..1184bf4b 100644 --- a/packages/tools/src/claude-memory.ts +++ b/packages/tools/src/claude-memory.ts @@ -490,16 +490,18 @@ export class ClaudeMemoryTool { const originalContent = readResult.document.content const lines = originalContent.split("\n") - // Validate line number - if (insertLine < 1 || insertLine > lines.length + 1) { + // Validate line number. Per the memory_20250818 spec the valid range + // is [0, n_lines]: text is inserted AFTER `insert_line`, and 0 means + // the beginning of the file. + if (insertLine < 0 || insertLine > lines.length) { return { success: false, - error: `Invalid line number: ${insertLine}. File has ${lines.length} lines.`, + error: `Invalid insert_line parameter: ${insertLine}. It should be within the range of lines of the file: [0, ${lines.length}]`, } } - // Insert the text (insertLine is 1-based) - lines.splice(insertLine - 1, 0, insertText) + // Insert the text after line `insertLine` (0-based insert-after) + lines.splice(insertLine, 0, insertText) const newContent = lines.join("\n") // Update the document @@ -517,7 +519,7 @@ export class ClaudeMemoryTool { return { success: true, - content: `Text inserted at line ${insertLine} in file: ${filePath}`, + content: `Text inserted after line ${insertLine} in file: ${filePath}`, } } catch (error) { return { diff --git a/packages/tools/src/tool-operations.test.ts b/packages/tools/src/tool-operations.test.ts index 8efef91d..63ff8e24 100644 --- a/packages/tools/src/tool-operations.test.ts +++ b/packages/tools/src/tool-operations.test.ts @@ -309,7 +309,7 @@ describe("ClaudeMemoryTool", () => { const result = await tool.handleCommand({ command: "insert", path: FILE_PATH, - insert_line: 2, + insert_line: 1, insert_text: "", })