Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 3 additions & 2 deletions packages/core/src/memory/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,15 @@ function findCanonicalGitRoot(startPath: string): string | null {

/**
* Returns the base directory for all auto-memory storage.
* Defaults to the global qwen dir (`~/.qwen` or `$QWEN_HOME`);
* Defaults to the runtime output dir (`runtimeOutputDir`, `QWEN_RUNTIME_DIR`,
* or the global qwen dir);
* overridable via QWEN_CODE_MEMORY_BASE_DIR for tests.
*/
export function getMemoryBaseDir(): string {
if (process.env['QWEN_CODE_MEMORY_BASE_DIR']) {
return process.env['QWEN_CODE_MEMORY_BASE_DIR'];
}
return Storage.getGlobalQwenDir();
return Storage.getRuntimeBaseDir();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] Changing from getGlobalQwenDir() to getRuntimeBaseDir() silently relocates auto-memory from ~/.qwen/projects/... to <runtimeOutputDir>/projects/.... There is no migration logic, no symlink, and no fallback read from the old path. Users who have advanced.runtimeOutputDir set in settings.json will experience unexplained loss of all accumulated auto-memory (user preferences, project knowledge, feedback).

Suggested fix: Add a one-time migration — when the new path doesn't exist but the old getGlobalQwenDir()-based path does, either copy/symlink the old tree or fall back to reading from the old path with a deprecation log.

— qwen3.7-max via Qwen Code /review

}

// Memoize by projectRoot — findCanonicalGitRoot() walks the file system (existsSync
Expand Down
76 changes: 76 additions & 0 deletions packages/core/src/memory/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,67 @@ import {
getAutoMemoryMetadataPath,
getAutoMemoryRoot,
getAutoMemoryTopicPath,
clearAutoMemoryRootCache,
} from './paths.js';
import {
createDefaultAutoMemoryIndex,
createDefaultAutoMemoryMetadata,
ensureAutoMemoryScaffold,
readAutoMemoryIndex,
} from './store.js';
import { Storage } from '../config/storage.js';
import { sanitizeCwd } from '../utils/paths.js';

const originalMemoryLocal = process.env['QWEN_CODE_MEMORY_LOCAL'];
const originalMemoryBaseDir = process.env['QWEN_CODE_MEMORY_BASE_DIR'];
const originalRuntimeDir = process.env['QWEN_RUNTIME_DIR'];

describe('auto-memory storage scaffold', () => {
let tempDir: string;
let projectRoot: string;

beforeEach(async () => {
clearAutoMemoryRootCache();
Storage.setRuntimeBaseDir(null);
if (originalMemoryLocal === undefined) {
delete process.env['QWEN_CODE_MEMORY_LOCAL'];
} else {
process.env['QWEN_CODE_MEMORY_LOCAL'] = originalMemoryLocal;
}
if (originalMemoryBaseDir === undefined) {
delete process.env['QWEN_CODE_MEMORY_BASE_DIR'];
} else {
process.env['QWEN_CODE_MEMORY_BASE_DIR'] = originalMemoryBaseDir;
}
if (originalRuntimeDir === undefined) {
delete process.env['QWEN_RUNTIME_DIR'];
} else {
process.env['QWEN_RUNTIME_DIR'] = originalRuntimeDir;
}

tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'auto-memory-'));
projectRoot = path.join(tempDir, 'project');
await fs.mkdir(projectRoot, { recursive: true });
});

afterEach(async () => {
clearAutoMemoryRootCache();
Storage.setRuntimeBaseDir(null);
if (originalMemoryLocal === undefined) {
delete process.env['QWEN_CODE_MEMORY_LOCAL'];
} else {
process.env['QWEN_CODE_MEMORY_LOCAL'] = originalMemoryLocal;
}
if (originalMemoryBaseDir === undefined) {
delete process.env['QWEN_CODE_MEMORY_BASE_DIR'];
} else {
process.env['QWEN_CODE_MEMORY_BASE_DIR'] = originalMemoryBaseDir;
}
if (originalRuntimeDir === undefined) {
delete process.env['QWEN_RUNTIME_DIR'];
} else {
process.env['QWEN_RUNTIME_DIR'] = originalRuntimeDir;
}
await fs.rm(tempDir, {
recursive: true,
force: true,
Expand Down Expand Up @@ -63,6 +105,40 @@ describe('auto-memory storage scaffold', () => {
);
});

it('uses the runtime output directory for managed auto-memory', () => {
delete process.env['QWEN_CODE_MEMORY_LOCAL'];
const runtimeDir = path.join(tempDir, 'runtime-output');
Storage.setRuntimeBaseDir(runtimeDir);
clearAutoMemoryRootCache();

expect(getAutoMemoryRoot(projectRoot)).toBe(
path.join(
runtimeDir,
'projects',
sanitizeCwd(path.resolve(projectRoot)),
'memory',
),
);
});

it('keeps QWEN_CODE_MEMORY_BASE_DIR ahead of the runtime output directory', () => {
delete process.env['QWEN_CODE_MEMORY_LOCAL'];
const memoryBaseDir = path.join(tempDir, 'memory-base');
const runtimeDir = path.join(tempDir, 'runtime-output');
process.env['QWEN_CODE_MEMORY_BASE_DIR'] = memoryBaseDir;
Storage.setRuntimeBaseDir(runtimeDir);
clearAutoMemoryRootCache();

expect(getAutoMemoryRoot(projectRoot)).toBe(
path.join(
memoryBaseDir,
'projects',
sanitizeCwd(path.resolve(projectRoot)),
'memory',
),
);
});

it('creates a complete managed auto-memory scaffold', async () => {
const now = new Date('2026-04-01T08:00:00.000Z');
await ensureAutoMemoryScaffold(projectRoot, now);
Expand Down
Loading