fix: keep root memory uppercase (#70621)

Thanks @mbelinky.
This commit is contained in:
Mariano
2026-04-23 17:10:36 +02:00
committed by GitHub
parent 645294510c
commit 10a9acbf29
26 changed files with 677 additions and 203 deletions

View File

@@ -530,17 +530,12 @@ async function scanMemoryFiles(
): Promise<SourceScan> {
const issues: string[] = [];
const memoryFile = path.join(workspaceDir, "MEMORY.md");
const altMemoryFile = path.join(workspaceDir, "memory.md");
const memoryDir = path.join(workspaceDir, "memory");
const primary = await checkReadableFile(memoryFile);
const alt = await checkReadableFile(altMemoryFile);
if (primary.issue) {
issues.push(primary.issue);
}
if (alt.issue) {
issues.push(alt.issue);
}
const resolvedExtraPaths = normalizeExtraMemoryPaths(workspaceDir, extraPaths);
for (const extraPath of resolvedExtraPaths) {
@@ -606,9 +601,6 @@ async function scanMemoryFiles(
if (primary.exists) {
files.add(memoryFile);
}
if (alt.exists) {
files.add(altMemoryFile);
}
}
totalFiles = files.size;
}

View File

@@ -370,7 +370,6 @@ export abstract class MemoryManagerSyncOps {
}
const watchPaths = new Set<string>([
path.join(this.workspaceDir, "MEMORY.md"),
path.join(this.workspaceDir, "memory.md"),
path.join(this.workspaceDir, "memory"),
]);
const additionalPaths = normalizeExtraMemoryPaths(this.workspaceDir, this.settings.extraPaths);

View File

@@ -139,7 +139,6 @@ describe("memory watcher config", () => {
expect(watchedPaths).toEqual(
expect.arrayContaining([
path.join(workspaceDir, "MEMORY.md"),
path.join(workspaceDir, "memory.md"),
path.join(workspaceDir, "memory"),
path.join(extraDir, "**", "*.md"),
]),

View File

@@ -878,7 +878,7 @@ describe("QmdMemoryManager", () => {
expect(logWarnMock).toHaveBeenCalledWith(expect.stringContaining("rebinding"));
});
it("rebinds legacy memory-alt when it still owns the root slot for MEMORY.md", async () => {
it("adds canonical memory-root without treating legacy memory-alt as equivalent", async () => {
await fs.writeFile(path.join(workspaceDir, "MEMORY.md"), "# canonical root");
cfg = {
...cfg,
@@ -930,15 +930,10 @@ describe("QmdMemoryManager", () => {
const pathArg = args[2] ?? "";
const name = args[args.indexOf("--name") + 1] ?? "";
const pattern = args[args.indexOf("--glob") + 1] ?? args[args.indexOf("--mask") + 1] ?? "";
const hasConflict = [...listedCollections.entries()].some(([existingName, info]) => {
if (existingName === name || info.path !== pathArg) {
return false;
}
const isRootPatternPair =
(info.pattern === "MEMORY.md" || info.pattern === "memory.md") &&
(pattern === "MEMORY.md" || pattern === "memory.md");
return info.pattern === pattern || isRootPatternPair;
});
const hasConflict = [...listedCollections.entries()].some(
([existingName, info]) =>
existingName !== name && info.path === pathArg && info.pattern === pattern,
);
if (hasConflict) {
emitAndClose(child, "stderr", "A collection already exists for this path and pattern", 1);
return child;
@@ -953,10 +948,10 @@ describe("QmdMemoryManager", () => {
const { manager } = await createManager({ mode: "full" });
await manager.close();
expect(removeCalls).toContain("memory-alt");
expect(removeCalls).not.toContain("memory-alt");
expect(listedCollections.has("memory-root-main")).toBe(true);
expect(listedCollections.has("memory-alt")).toBe(false);
expect(logWarnMock).toHaveBeenCalledWith(expect.stringContaining("rebinding"));
expect(listedCollections.has("memory-alt")).toBe(true);
expect(logWarnMock).not.toHaveBeenCalledWith(expect.stringContaining("rebinding"));
});
it("warns instead of silently succeeding when add conflict metadata is unavailable", async () => {

View File

@@ -92,12 +92,7 @@ function isDefaultMemoryPath(relPath: string): boolean {
if (!normalized) {
return false;
}
if (
normalized === "MEMORY.md" ||
normalized === "memory.md" ||
normalized === "DREAMS.md" ||
normalized === "dreams.md"
) {
if (normalized === "MEMORY.md" || normalized === "DREAMS.md" || normalized === "dreams.md") {
return true;
}
return normalized.startsWith("memory/");
@@ -894,29 +889,22 @@ export class QmdMemoryManager implements MemorySearchManager {
return false;
}
try {
let sawCanonical = false;
let sawLegacyFallback = false;
for (const entry of fsSync.readdirSync(collectionPath, { withFileTypes: true })) {
if (entry.isSymbolicLink() || !entry.isFile()) {
continue;
}
if (entry.name === "MEMORY.md") {
sawCanonical = true;
} else if (entry.name === "memory.md") {
sawLegacyFallback = true;
return true;
}
}
if (sawCanonical && sawLegacyFallback) {
return false;
}
return sawCanonical || sawLegacyFallback;
return false;
} catch {
return false;
}
}
private isDefaultMemoryRootPattern(pattern: string): boolean {
return pattern === "MEMORY.md" || pattern === "memory.md";
return pattern === "MEMORY.md";
}
private pathsMatch(left: string, right: string): boolean {

View File

@@ -70,7 +70,7 @@ function parseMemoryDateFromPath(filePath: string): Date | null {
function isEvergreenMemoryPath(filePath: string): boolean {
const normalized = filePath.replaceAll("\\", "/").replace(/^\.\//, "");
if (normalized === "MEMORY.md" || normalized === "memory.md") {
if (normalized === "MEMORY.md") {
return true;
}
if (!normalized.startsWith("memory/")) {

View File

@@ -87,7 +87,7 @@ describe("listMemoryCorePublicArtifacts", () => {
]);
});
it("lists lowercase memory root when only the legacy filename exists", async () => {
it("ignores lowercase memory root when only the legacy filename exists", async () => {
const workspaceDir = path.join(fixtureRoot, "workspace-lowercase-root");
await fs.mkdir(workspaceDir, { recursive: true });
await fs.writeFile(path.join(workspaceDir, "memory.md"), "# Legacy Durable Memory\n", "utf8");
@@ -98,15 +98,6 @@ describe("listMemoryCorePublicArtifacts", () => {
},
};
await expect(listMemoryCorePublicArtifacts({ cfg })).resolves.toEqual([
{
kind: "memory-root",
workspaceDir,
relativePath: "memory.md",
absolutePath: path.join(workspaceDir, "memory.md"),
agentIds: ["main"],
contentType: "markdown",
},
]);
await expect(listMemoryCorePublicArtifacts({ cfg })).resolves.toEqual([]);
});
});

View File

@@ -40,7 +40,7 @@ async function collectWorkspaceArtifacts(params: {
.filter((entry) => entry.isFile())
.map((entry) => entry.name),
);
for (const relativePath of ["MEMORY.md", "memory.md"]) {
for (const relativePath of ["MEMORY.md"]) {
if (!workspaceEntries.has(relativePath)) {
continue;
}