mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 08:18:04 +00:00
feat(memory): share project memory across account instances
This commit is contained in:
1 parent
9464615e38
commit
e2623e632c
3 files changed
+365
No files matched your search
@@ -0,0 +1,113 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import SharedManager from '../../src/management/shared-manager';
|
||||
|
||||
function getTestCcsDir(): string {
|
||||
if (!process.env.CCS_HOME) {
|
||||
throw new Error('CCS_HOME must be set in tests');
|
||||
}
|
||||
return path.join(path.resolve(process.env.CCS_HOME), '.ccs');
|
||||
}
|
||||
|
||||
describe('SharedManager project memory sync', () => {
|
||||
let tempRoot = '';
|
||||
let originalHome: string | undefined;
|
||||
let originalCcsHome: string | undefined;
|
||||
let originalCcsDir: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-shared-memory-test-'));
|
||||
originalHome = process.env.HOME;
|
||||
originalCcsHome = process.env.CCS_HOME;
|
||||
originalCcsDir = process.env.CCS_DIR;
|
||||
|
||||
const isolatedHome = path.join(tempRoot, 'home');
|
||||
fs.mkdirSync(isolatedHome, { recursive: true });
|
||||
process.env.HOME = isolatedHome;
|
||||
process.env.CCS_HOME = tempRoot;
|
||||
delete process.env.CCS_DIR;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalHome !== undefined) process.env.HOME = originalHome;
|
||||
else delete process.env.HOME;
|
||||
|
||||
if (originalCcsHome !== undefined) process.env.CCS_HOME = originalCcsHome;
|
||||
else delete process.env.CCS_HOME;
|
||||
|
||||
if (originalCcsDir !== undefined) process.env.CCS_DIR = originalCcsDir;
|
||||
else delete process.env.CCS_DIR;
|
||||
|
||||
if (tempRoot && fs.existsSync(tempRoot)) {
|
||||
fs.rmSync(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('migrates existing project memory and replaces it with a shared symlink', () => {
|
||||
const ccsDir = getTestCcsDir();
|
||||
const instancePath = path.join(ccsDir, 'instances', 'work');
|
||||
const projectName = '-tmp-my-project';
|
||||
const projectMemoryPath = path.join(instancePath, 'projects', projectName, 'memory');
|
||||
fs.mkdirSync(projectMemoryPath, { recursive: true });
|
||||
fs.writeFileSync(path.join(projectMemoryPath, 'MEMORY.md'), 'instance knowledge', 'utf8');
|
||||
|
||||
const manager = new SharedManager();
|
||||
manager.syncProjectMemories(instancePath);
|
||||
|
||||
const sharedMemoryFile = path.join(ccsDir, 'shared', 'memory', projectName, 'MEMORY.md');
|
||||
expect(fs.existsSync(sharedMemoryFile)).toBe(true);
|
||||
expect(fs.readFileSync(sharedMemoryFile, 'utf8')).toBe('instance knowledge');
|
||||
|
||||
const linkStats = fs.lstatSync(projectMemoryPath);
|
||||
expect(linkStats.isSymbolicLink()).toBe(true);
|
||||
|
||||
const resolvedTarget = path.resolve(path.dirname(projectMemoryPath), fs.readlinkSync(projectMemoryPath));
|
||||
expect(resolvedTarget).toBe(path.join(ccsDir, 'shared', 'memory', projectName));
|
||||
});
|
||||
|
||||
it('preserves canonical memory and writes conflict copy when contents differ', () => {
|
||||
const ccsDir = getTestCcsDir();
|
||||
const instancePath = path.join(ccsDir, 'instances', 'work');
|
||||
const projectName = '-tmp-shared-project';
|
||||
const projectMemoryPath = path.join(instancePath, 'projects', projectName, 'memory');
|
||||
const sharedProjectMemoryPath = path.join(ccsDir, 'shared', 'memory', projectName);
|
||||
fs.mkdirSync(projectMemoryPath, { recursive: true });
|
||||
fs.mkdirSync(sharedProjectMemoryPath, { recursive: true });
|
||||
|
||||
fs.writeFileSync(path.join(projectMemoryPath, 'MEMORY.md'), 'instance memory', 'utf8');
|
||||
fs.writeFileSync(path.join(sharedProjectMemoryPath, 'MEMORY.md'), 'shared memory', 'utf8');
|
||||
|
||||
const manager = new SharedManager();
|
||||
manager.syncProjectMemories(instancePath);
|
||||
|
||||
const canonicalFile = path.join(sharedProjectMemoryPath, 'MEMORY.md');
|
||||
expect(fs.readFileSync(canonicalFile, 'utf8')).toBe('shared memory');
|
||||
|
||||
const conflictFile = path.join(sharedProjectMemoryPath, 'MEMORY.md.migrated-from-work');
|
||||
expect(fs.existsSync(conflictFile)).toBe(true);
|
||||
expect(fs.readFileSync(conflictFile, 'utf8')).toBe('instance memory');
|
||||
|
||||
const linkStats = fs.lstatSync(projectMemoryPath);
|
||||
expect(linkStats.isSymbolicLink()).toBe(true);
|
||||
});
|
||||
|
||||
it('creates shared memory link for projects that do not have memory directory yet', () => {
|
||||
const ccsDir = getTestCcsDir();
|
||||
const instancePath = path.join(ccsDir, 'instances', 'work');
|
||||
const projectName = '-tmp-new-project';
|
||||
const projectPath = path.join(instancePath, 'projects', projectName);
|
||||
const projectMemoryPath = path.join(projectPath, 'memory');
|
||||
fs.mkdirSync(projectPath, { recursive: true });
|
||||
|
||||
const manager = new SharedManager();
|
||||
manager.syncProjectMemories(instancePath);
|
||||
|
||||
const linkStats = fs.lstatSync(projectMemoryPath);
|
||||
expect(linkStats.isSymbolicLink()).toBe(true);
|
||||
|
||||
const sharedProjectMemoryPath = path.join(ccsDir, 'shared', 'memory', projectName);
|
||||
expect(fs.existsSync(sharedProjectMemoryPath)).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user