diff --git a/src/core/lockfile.ts b/src/core/lockfile.ts index fe4a6ef5..b5afe06f 100644 --- a/src/core/lockfile.ts +++ b/src/core/lockfile.ts @@ -74,7 +74,7 @@ export function invalidateLockCache(skillsDir?: string): void { export function readLock(skillsDir: string): SkilldLock | null { const cached = lockCache.get(skillsDir) if (cached) - return cached + return { skills: { ...cached.skills } } const lockPath = join(skillsDir, 'skilld-lock.yaml') if (!existsSync(lockPath)) return null diff --git a/test/unit/lockfile.test.ts b/test/unit/lockfile.test.ts index 2728111e..c6cd3ee6 100644 --- a/test/unit/lockfile.test.ts +++ b/test/unit/lockfile.test.ts @@ -236,6 +236,35 @@ describe('core/lockfile', () => { expect(written).toContain('generator: external') }) + it('readLock returns isolated copies from cache', async () => { + const { existsSync, readFileSync } = await import('node:fs') + const { readLock } = await import('../../src/core/lockfile') + + vi.mocked(existsSync).mockReturnValue(true) + vi.mocked(readFileSync).mockReturnValue( + 'skills:\n' + + ' vue:\n' + + ' packageName: vue\n' + + ' version: "3.5.0"\n', + ) + + const first = readLock('/skills') + const second = readLock('/skills') + + // Both should parse correctly + expect(first?.skills.vue?.packageName).toBe('vue') + expect(second?.skills.vue?.packageName).toBe('vue') + + // Mutating first should not affect second (both are isolated copies) + delete first!.skills.vue + expect(first!.skills.vue).toBeUndefined() + expect(second?.skills.vue?.packageName).toBe('vue') + + // A third read should still see the original data + const third = readLock('/skills') + expect(third?.skills.vue?.packageName).toBe('vue') + }) + it('writeLock omits git fields when not set', async () => { const { existsSync, writeFileSync } = await import('node:fs') const { writeLock } = await import('../../src/core/lockfile')