/** * SkillOpt version-store tests. Covers D8 history-intent-first ordering * and crash-recovery via revertAllPending. */ import { describe, expect, test, beforeEach, afterEach } from 'bun:test'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import { acceptCandidate, bestPath, historyPath, loadHistory, proposedPath, revertAllPending, skillPath, versionsDir, writeProposed, } from '../../src/core/skillopt/version-store.ts'; let tmpDir: string; const SKILL = 'test-skill'; beforeEach(() => { tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'skillopt-versions-')); // Seed a baseline SKILL.md so atomic writes have a real file. fs.mkdirSync(path.join(tmpDir, SKILL), { recursive: true }); fs.writeFileSync(skillPath(tmpDir, SKILL), '---\nname: test\n---\nbaseline body\n'); }); afterEach(() => { try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch { /* ignore */ } }); describe('acceptCandidate (D8 two-phase commit)', () => { test('writes 4 files in order + flips history to committed', () => { const candidate = '---\nname: test\n---\nimproved body\n'; const r = acceptCandidate({ skillsDir: tmpDir, skillName: SKILL, runId: 'run-1', epoch: 1, step: 1, edits: [{ op: 'replace', target: 'baseline', replacement: 'improved' }], candidateText: candidate, selScore: 0.85, delta: 0.10, }); expect(r.versionN).toBe(1); // SKILL.md replaced. expect(fs.readFileSync(skillPath(tmpDir, SKILL), 'utf8')).toBe(candidate); // best.md is a pointer copy. expect(fs.readFileSync(bestPath(tmpDir, SKILL), 'utf8')).toBe(candidate); // versions/v0001_e1_s1.md is a snapshot. expect(fs.existsSync(r.versionFilePath)).toBe(true); expect(fs.readFileSync(r.versionFilePath, 'utf8')).toBe(candidate); // history.json has a committed row. const hist = loadHistory(tmpDir, SKILL); expect(hist).toHaveLength(1); expect(hist[0]!.status).toBe('committed'); expect(hist[0]!.version_n).toBe(1); expect(hist[0]!.sel_score).toBe(0.85); }); test('version_n increments across runs', () => { const c1 = '---\nname: test\n---\nv1\n'; const c2 = '---\nname: test\n---\nv2\n'; const r1 = acceptCandidate({ skillsDir: tmpDir, skillName: SKILL, runId: 'a', epoch: 1, step: 1, edits: [], candidateText: c1, selScore: 0.5, delta: 0.5, }); const r2 = acceptCandidate({ skillsDir: tmpDir, skillName: SKILL, runId: 'b', epoch: 1, step: 1, edits: [], candidateText: c2, selScore: 0.6, delta: 0.1, }); expect(r1.versionN).toBe(1); expect(r2.versionN).toBe(2); expect(loadHistory(tmpDir, SKILL)).toHaveLength(2); }); }); describe('writeProposed', () => { test('writes distinct best and proposed artifacts without mutating SKILL.md (#2635)', () => { const candidate = '---\nname: test\n---\nproposed body\n'; const written = writeProposed(tmpDir, SKILL, candidate); expect(written).toBe(proposedPath(tmpDir, SKILL)); expect(fs.readFileSync(bestPath(tmpDir, SKILL), 'utf8')).toBe(candidate); expect(fs.readFileSync(proposedPath(tmpDir, SKILL), 'utf8')).toBe(candidate); expect(fs.readFileSync(skillPath(tmpDir, SKILL), 'utf8')).toContain('baseline body'); }); }); describe('revertAllPending (D8 crash recovery)', () => { test('no-op when no pending rows', () => { const reverted = revertAllPending(tmpDir, SKILL); expect(reverted).toBe(0); }); test('cleans up a pending row left behind by a simulated crash', () => { // Simulate: history has a pending row; snapshot exists; best.md points // at the pending text; SKILL.md still has the baseline (the commit step // never ran). This is the EXACT crash scenario D8 guards against. fs.mkdirSync(versionsDir(tmpDir, SKILL), { recursive: true }); const snapPath = path.join(versionsDir(tmpDir, SKILL), 'v0001_e1_s1.md'); fs.writeFileSync(snapPath, 'pending body that never committed'); fs.writeFileSync(bestPath(tmpDir, SKILL), 'pending body that never committed'); fs.writeFileSync( historyPath(tmpDir, SKILL), JSON.stringify({ schema: 1, rows: [{ status: 'pending', run_id: 'crashed-run', version_n: 1, ts: '2026-05-27T12:00:00Z', edits: [], sel_score: 0.4, delta: 0.1, }], }, null, 2), ); const reverted = revertAllPending(tmpDir, SKILL); expect(reverted).toBe(1); // Snapshot removed. expect(fs.existsSync(snapPath)).toBe(false); // best.md removed (no prior committed version to restore from). expect(fs.existsSync(bestPath(tmpDir, SKILL))).toBe(false); // History row gone. expect(loadHistory(tmpDir, SKILL)).toEqual([]); // SKILL.md untouched (still baseline). expect(fs.readFileSync(skillPath(tmpDir, SKILL), 'utf8')).toContain('baseline body'); }); test('restores best.md from prior committed version on revert', () => { // First commit a clean version. const v1 = '---\nname: test\n---\nclean version\n'; acceptCandidate({ skillsDir: tmpDir, skillName: SKILL, runId: 'good', epoch: 1, step: 1, edits: [], candidateText: v1, selScore: 0.5, delta: 0.5, }); // Then simulate a pending row from a later crashed run. const snapPath = path.join(versionsDir(tmpDir, SKILL), 'v0002_e1_s1.md'); fs.writeFileSync(snapPath, 'corrupted pending'); fs.writeFileSync(bestPath(tmpDir, SKILL), 'corrupted pending'); const history = loadHistory(tmpDir, SKILL); history.push({ status: 'pending', run_id: 'crashed', version_n: 2, ts: new Date().toISOString(), edits: [], sel_score: 0.6, delta: 0.1, }); fs.writeFileSync(historyPath(tmpDir, SKILL), JSON.stringify({ schema: 1, rows: history }, null, 2)); revertAllPending(tmpDir, SKILL); // best.md should be restored to v1. expect(fs.readFileSync(bestPath(tmpDir, SKILL), 'utf8')).toBe(v1); // Pending row dropped. expect(loadHistory(tmpDir, SKILL).every((r) => r.status === 'committed')).toBe(true); }); });