mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-10 03:28:14 +00:00
Merge 9d2358bf0e into cfa6c7cb17
This commit is contained in:
commit
da9543dc4e
3 changed files with 65 additions and 7 deletions
|
|
@ -210,3 +210,42 @@ describe("ClaudeMemoryTool str_replace replacement literalness", () => {
|
||||||
expect(stored).toContain(`price is ${dollarSequence} today`)
|
expect(stored).toContain(`price is ${dollarSequence} today`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("ClaudeMemoryTool path normalization collision resistance", () => {
|
||||||
|
it("produces distinct customIds for paths that previously collided", () => {
|
||||||
|
const tool = new ClaudeMemoryTool("test-api-key")
|
||||||
|
const paths = [
|
||||||
|
"/memories/notes.txt",
|
||||||
|
"/memories/notes_txt",
|
||||||
|
"/memories/notes/txt",
|
||||||
|
"/memories/project/a.md",
|
||||||
|
"/memories/project_a.md",
|
||||||
|
]
|
||||||
|
|
||||||
|
const ids = paths.map((path) => tool.normalizePathToCustomId(path))
|
||||||
|
const uniqueIds = new Set(ids)
|
||||||
|
|
||||||
|
expect(uniqueIds.size).toBe(paths.length)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("resolves documents stored under legacy customId format", async () => {
|
||||||
|
// Mock a document saved with legacy normalization (memories_notes_txt)
|
||||||
|
mockDocuments([
|
||||||
|
{
|
||||||
|
id: "legacy-doc",
|
||||||
|
customId: "memories_notes_txt",
|
||||||
|
filePath: "/memories/notes.txt",
|
||||||
|
content: "legacy content",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const tool = new ClaudeMemoryTool("test-api-key")
|
||||||
|
const result = await tool.handleCommand({
|
||||||
|
command: "view",
|
||||||
|
path: "/memories/notes.txt",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.success).toBe(true)
|
||||||
|
expect(result.content).toContain("legacy content")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -57,13 +57,22 @@ export class ClaudeMemoryTool {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize file path to be used as customId
|
* Normalize file path to be used as customId
|
||||||
* Converts /memories/file.txt -> memories_file_txt
|
* Reversibly encodes path components to prevent collisions between paths like
|
||||||
|
* `/memories/notes.txt`, `/memories/notes_txt`, and `/memories/notes/txt`.
|
||||||
*/
|
*/
|
||||||
private normalizePathToCustomId(path: string): string {
|
normalizePathToCustomId(path: string): string {
|
||||||
return path
|
return path
|
||||||
.replace(/^\//, "") // Remove leading slash
|
.replace(/^\//, "") // Remove leading slash
|
||||||
.replace(/\//g, "_") // Replace / with _
|
.replace(/_/g, "__") // Escape underscores: _ -> __
|
||||||
.replace(/\./g, "_") // Replace . with _
|
.replace(/\//g, "_s_") // Encode slashes: / -> _s_
|
||||||
|
.replace(/\./g, "_d_") // Encode dots: . -> _d_
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy normalization used in older versions (/ and . both flattened to _)
|
||||||
|
*/
|
||||||
|
private legacyNormalizePathToCustomId(path: string): string {
|
||||||
|
return path.replace(/^\//, "").replace(/\//g, "_").replace(/\./g, "_")
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(apiKey: string, config?: ClaudeMemoryConfig) {
|
constructor(apiKey: string, config?: ClaudeMemoryConfig) {
|
||||||
|
|
@ -650,8 +659,12 @@ export class ClaudeMemoryTool {
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const document of response.memories) {
|
for (const document of response.memories) {
|
||||||
|
const isMatchingCustomId =
|
||||||
|
document.customId === normalizedId ||
|
||||||
|
document.customId === this.legacyNormalizePathToCustomId(filePath)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
document.customId === normalizedId &&
|
isMatchingCustomId &&
|
||||||
this.getDocumentFilePath(document) === filePath &&
|
this.getDocumentFilePath(document) === filePath &&
|
||||||
this.isDocumentInConfiguredScope(document)
|
this.isDocumentInConfiguredScope(document)
|
||||||
) {
|
) {
|
||||||
|
|
@ -681,8 +694,12 @@ export class ClaudeMemoryTool {
|
||||||
hasUnverifiedCandidate = true
|
hasUnverifiedCandidate = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
const isMatchingCustomId =
|
||||||
|
document.customId === normalizedId ||
|
||||||
|
document.customId === this.legacyNormalizePathToCustomId(filePath)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
document.customId !== normalizedId ||
|
!isMatchingCustomId ||
|
||||||
this.getDocumentFilePath(document) !== filePath ||
|
this.getDocumentFilePath(document) !== filePath ||
|
||||||
!this.hasExactContainerTags(document.containerTags)
|
!this.hasExactContainerTags(document.containerTags)
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -344,7 +344,9 @@ describe("ClaudeMemoryTool", () => {
|
||||||
|
|
||||||
expect(result.success).toBe(true)
|
expect(result.success).toBe(true)
|
||||||
expect(clientAdd).toHaveBeenCalledWith(
|
expect(clientAdd).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({ customId: "memories_renamed_txt" }),
|
expect.objectContaining({
|
||||||
|
customId: tool.normalizePathToCustomId("/memories/renamed.txt"),
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
expect(documentsDeleteBulk).toHaveBeenCalledWith({ ids: [DOCUMENT_ID] })
|
expect(documentsDeleteBulk).toHaveBeenCalledWith({ ids: [DOCUMENT_ID] })
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue