diff --git a/scripts/lib/skill-evolution/versioning.js b/scripts/lib/skill-evolution/versioning.js index a57178ac3..9adcce14b 100644 --- a/scripts/lib/skill-evolution/versioning.js +++ b/scripts/lib/skill-evolution/versioning.js @@ -179,7 +179,7 @@ function allocateSnapshotVersion(skillPath) { nextVersion += 1; } if (!Number.isSafeInteger(nextVersion)) { - throw new Error('Skill snapshot version range is exhausted; existing snapshots were preserved'); + throw new Error('Skill snapshot version range is exhausted; safe integer allocation is unavailable; existing snapshots were preserved'); } return nextVersion; } diff --git a/tests/lib/skill-evolution.test.js b/tests/lib/skill-evolution.test.js index b23b65757..1860de247 100644 --- a/tests/lib/skill-evolution.test.js +++ b/tests/lib/skill-evolution.test.js @@ -221,8 +221,29 @@ 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").strictEqual(require(process.argv[1]).createVersion(process.argv[2]).version, 5)', + 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'); + // Unsafe names are excluded from listing, while the actual safe range boundary refuses allocation. + const exhaustedSnapshot = path.join(versioning.getVersionsDir(skillDir), `v${Number.MAX_SAFE_INTEGER}.md`); + fs.writeFileSync(exhaustedSnapshot, 'Preserve the range boundary'); + assert.throws(() => versioning.createVersion(skillDir), /safe integer/); + assert.strictEqual(fs.readFileSync(exhaustedSnapshot, 'utf8'), 'Preserve the range boundary'); } finally { cleanupTempDir(fixtureRoot); }