From 6d8e69df52ef25b97bf23acde18b899e6f793289 Mon Sep 17 00:00:00 2001 From: Eneko Ruiz <196690101+eneekoruiz@users.noreply.github.com> Date: Sun, 11 Oct 2026 16:29:15 +0200 Subject: [PATCH] fix: skip occupied snapshot paths without overflowing --- scripts/lib/skill-evolution/versioning.js | 11 +++++++++-- tests/lib/skill-evolution.test.js | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/scripts/lib/skill-evolution/versioning.js b/scripts/lib/skill-evolution/versioning.js index 83a309d74..d5043ae2e 100644 --- a/scripts/lib/skill-evolution/versioning.js +++ b/scripts/lib/skill-evolution/versioning.js @@ -163,8 +163,15 @@ function createVersion(skillPath, options = {}) { ensureSkillVersioning(skillPath); const versions = listVersions(skillPath); - const nextVersion = versions.length === 0 ? 1 : versions[versions.length - 1].version + 1; - const snapshotPath = path.join(getVersionsDir(skillPath), `v${nextVersion}.md`); + let nextVersion = versions.length === 0 ? 1 : versions[versions.length - 1].version + 1; + let snapshotPath = path.join(getVersionsDir(skillPath), `v${nextVersion}.md`); + while (Number.isSafeInteger(nextVersion) && fs.existsSync(snapshotPath)) { + nextVersion += 1; + snapshotPath = path.join(getVersionsDir(skillPath), `v${nextVersion}.md`); + } + if (!Number.isSafeInteger(nextVersion)) { + throw new Error('Skill version exceeds the safe integer range'); + } const skillContent = fs.readFileSync(skillFilePath, 'utf8'); const createdAt = options.timestamp || new Date().toISOString(); diff --git a/tests/lib/skill-evolution.test.js b/tests/lib/skill-evolution.test.js index 6cd18aebe..501a05155 100644 --- a/tests/lib/skill-evolution.test.js +++ b/tests/lib/skill-evolution.test.js @@ -221,8 +221,24 @@ function runTests() { ); assert.strictEqual(versioning.getCurrentVersion(skillDir), 1); assert.ok(fs.statSync(snapshotDirectory).isDirectory()); + for (const version of [2, 3]) { + fs.mkdirSync(path.join(versioning.getVersionsDir(skillDir), `v${version}.md`)); + } + assert.strictEqual(versioning.createVersion(skillDir).version, 4); + assert.deepStrictEqual(versioning.listVersions(skillDir).map(entry => entry.version), [1, 4]); + for (const version of [2, 3]) { + assert.ok(fs.statSync(path.join(versioning.getVersionsDir(skillDir), `v${version}.md`)).isDirectory()); + } assert.strictEqual(versioning.getCurrentVersion(path.join(fixtureRoot, 'missing-skill')), 0); assert.throws(() => versioning.createVersion(snapshotDirectory), /Skill file not found/); + const oversizedSnapshot = path.join(versioning.getVersionsDir(skillDir), 'v9007199254740992.md'); + fs.writeFileSync(oversizedSnapshot, 'Preserve this snapshot'); + const overflow = spawnSync(process.execPath, [ + '-e', 'require("assert").throws(() => require(process.argv[1]).createVersion(process.argv[2]), /safe integer/)', + require.resolve('../../scripts/lib/skill-evolution/versioning'), skillDir, + ], { encoding: 'utf8', timeout: 10000 }); + assert.strictEqual(overflow.status, 0, overflow.error?.message || overflow.stderr); + assert.strictEqual(fs.readFileSync(oversizedSnapshot, 'utf8'), 'Preserve this snapshot'); } finally { cleanupTempDir(fixtureRoot); }