mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-03 02:21:23 +00:00
fix(tools): align claude-memory insert with memory_20250818 line semantics
Anthropic's memory_20250818 spec defines insert as: insert_text is inserted AFTER line insert_line, 0 inserts at the beginning of the file, and the valid range is [0, n_lines]. The implementation treated insert_line as a 1-based insert-BEFORE index with range [1, n_lines + 1]. Since the caller of this tool is Claude itself, which is trained on the spec semantics, every model-driven insert landed one line earlier than intended, insert_line: 0 (insert at top of file) was rejected as invalid, and insert_line: n_lines (append) inserted before the last line instead of after it. Fix the validation range to [0, n_lines], splice at insert_line directly (0-based insert-after), and update the error and success messages to match. One existing tool-operations test encoded the old insert-before behavior; its insert_line is adjusted so its expected output is unchanged under spec semantics. Adds four regression tests covering top-of-file, middle, append, and both out-of-range directions.
This commit is contained in:
parent
bec73e28ad
commit
a949311131
3 changed files with 83 additions and 7 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ describe("ClaudeMemoryTool", () => {
|
|||
const result = await tool.handleCommand({
|
||||
command: "insert",
|
||||
path: FILE_PATH,
|
||||
insert_line: 2,
|
||||
insert_line: 1,
|
||||
insert_text: "",
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue