Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions packages/tools/src/claude-memory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,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

Expand Down
14 changes: 8 additions & 6 deletions packages/tools/src/claude-memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,16 +437,18 @@ export class ClaudeMemoryTool {
readResult.document.raw || 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
Expand All @@ -464,7 +466,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 {
Expand Down
2 changes: 1 addition & 1 deletion packages/tools/src/tool-operations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ describe("ClaudeMemoryTool", () => {
const result = await tool.handleCommand({
command: "insert",
path: FILE_PATH,
insert_line: 2,
insert_line: 1,
insert_text: "",
})

Expand Down