diff --git a/scripts/build-binary.js b/scripts/build-binary.js index ab3819b..d9cc71b 100644 --- a/scripts/build-binary.js +++ b/scripts/build-binary.js @@ -84,7 +84,8 @@ function parseGitDescribe(describe) { function calculateMinVer() { // Allow override via environment (for Docker/CI) if (process.env.VERSION) { - return process.env.VERSION; + // Strip leading "v" if present — callers add their own prefix + return process.env.VERSION.replace(/^v/i, ""); } try { const result = spawnSync("git", ["describe", "--tags", "--long", "--always", "--dirty"], { diff --git a/src/agent/version.ts b/src/agent/version.ts index 52a6afe..2d51ac4 100644 --- a/src/agent/version.ts +++ b/src/agent/version.ts @@ -95,7 +95,8 @@ export function getVersion(): string { // Check for build-time injected version if (typeof __HYPERAGENT_VERSION__ !== "undefined") { - cachedVersion = __HYPERAGENT_VERSION__; + // Strip leading "v" if present — callers add their own prefix + cachedVersion = __HYPERAGENT_VERSION__.replace(/^v/i, ""); return cachedVersion; } diff --git a/src/plugin-system/manager.ts b/src/plugin-system/manager.ts index 139fea5..0a67870 100644 --- a/src/plugin-system/manager.ts +++ b/src/plugin-system/manager.ts @@ -209,6 +209,16 @@ export function validateManifest(raw: unknown): string[] { if ("version" in obj && typeof obj.version !== "string") { errors.push("version must be a string"); } + if ( + "version" in obj && + typeof obj.version === "string" && + /^v/i.test(obj.version) + ) { + // Callers prepend "v" for display — a prefixed version causes "vv1.0.0" + errors.push( + 'version must not start with "v" (use bare semver, e.g. "1.0.0")', + ); + } if ("description" in obj && typeof obj.description !== "string") { errors.push("description must be a string"); } diff --git a/src/plugin-system/types.ts b/src/plugin-system/types.ts index d8c3cba..f1a17ab 100644 --- a/src/plugin-system/types.ts +++ b/src/plugin-system/types.ts @@ -61,7 +61,7 @@ export interface ConfigSchemaEntry { export interface PluginManifest { /** Unique plugin name (kebab-case, matches directory name). */ name: string; - /** SemVer version string. */ + /** SemVer version string (bare, no "v" prefix — e.g. "1.0.0" not "v1.0.0"). */ version: string; /** One-line description of what the plugin does. */ description: string; diff --git a/tests/plugin-manager.test.ts b/tests/plugin-manager.test.ts index afd1fb7..2d50065 100644 --- a/tests/plugin-manager.test.ts +++ b/tests/plugin-manager.test.ts @@ -82,6 +82,19 @@ describe("validateManifest", () => { expect(errors).toContain("hostModules must be an array"); }); + it('should reject version with "v" prefix', () => { + const manifest = { + name: "test", + version: "v1.0.0", + description: "Test", + hostModules: ["fs"], + }; + const errors = validateManifest(manifest); + expect(errors).toContain( + 'version must not start with "v" (use bare semver, e.g. "1.0.0")', + ); + }); + it("should reject empty hostModules array", () => { const manifest = { name: "test",