diff --git a/.gitignore b/.gitignore index 85bbc968..d7a8cb83 100644 --- a/.gitignore +++ b/.gitignore @@ -47,4 +47,4 @@ analysis/ gaps.md /improve.md philosophy.md -autoresearch-results.tsv +/autoresearch-results.tsv diff --git a/package-lock.json b/package-lock.json index a6b4ac3e..af60f0a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "maxsimcli-workspace", "version": "1.0.0", + "license": "MIT", "workspaces": [ "packages/cli", "packages/website" @@ -10516,7 +10517,7 @@ }, "packages/cli": { "name": "maxsimcli", - "version": "5.10.0", + "version": "5.12.0", "license": "MIT", "dependencies": { "@octokit/plugin-retry": "^8.1.0", diff --git a/package.json b/package.json index c5caca63..85bb9ee8 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "packages/website" ], "description": "A meta-prompting, context engineering and spec-driven development system for Claude Code by MayStudios.", + "license": "MIT", "engines": { "node": ">=22.0.0" }, diff --git a/packages/cli/scripts/inject-version.cjs b/packages/cli/scripts/inject-version.cjs index 8ed5e634..e7533408 100644 --- a/packages/cli/scripts/inject-version.cjs +++ b/packages/cli/scripts/inject-version.cjs @@ -21,15 +21,41 @@ const versionTsPath = path.join(pkgCliRoot, 'src', 'core', 'version.ts'); const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')); const version = pkg.version; -const content = `/** MaxsimCLI version — auto-injected from package.json at build time. */\nexport const VERSION = '${version}';\n`; +const versionLine = `export const VERSION = '${version}';`; const existing = fs.existsSync(versionTsPath) ? fs.readFileSync(versionTsPath, 'utf8') : ''; -if (existing === content) { +if (existing.includes(versionLine)) { console.log(` [version] version.ts already up-to-date (${version})`); +} else if (existing && /export const VERSION = '.*';/.test(existing)) { + // Replace the VERSION line in-place, preserving utility functions + const updated = existing.replace(/export const VERSION = '.*';/, versionLine); + fs.writeFileSync(versionTsPath, updated, 'utf8'); + console.log(` [version] Injected version ${version} -> src/core/version.ts`); } else { + // File missing or doesn't contain VERSION — write the full template + const content = `/** MaxsimCLI version — auto-injected from package.json at build time. */\n${versionLine}\n`; fs.writeFileSync(versionTsPath, content, 'utf8'); console.log(` [version] Injected version ${version} -> src/core/version.ts`); } + +// Also update templates/templates/config.json version +const configJsonPath = path.join(pkgCliRoot, '..', '..', 'templates', 'templates', 'config.json'); +if (fs.existsSync(configJsonPath)) { + try { + const config = JSON.parse(fs.readFileSync(configJsonPath, 'utf8')); + if (config.version !== version) { + config.version = version; + fs.writeFileSync(configJsonPath, JSON.stringify(config, null, 2) + '\n', 'utf8'); + console.log(` [version] Injected version ${version} -> templates/templates/config.json`); + } else { + console.log(` [version] config.json already up-to-date (${version})`); + } + } catch (err) { + console.warn(` [version] Warning: Could not update config.json: ${err.message}`); + } +} else { + console.warn(' [version] Warning: templates/templates/config.json not found'); +} diff --git a/packages/cli/src/core/index.ts b/packages/cli/src/core/index.ts index 4143a49a..30f0cdba 100644 --- a/packages/cli/src/core/index.ts +++ b/packages/cli/src/core/index.ts @@ -23,7 +23,7 @@ export { export { loadConfig, saveConfig, resolveModel, resolveMaxAgents, getConfigPath } from './config.js'; // Version -export { VERSION } from './version.js'; +export { VERSION, parseVersion, isVersionAtLeast, getVersion } from './version.js'; // Utils export { claudeDir, maxsimDir, agentMemoryDir, configPath, parseFrontmatter } from './utils.js'; diff --git a/packages/cli/src/core/version.ts b/packages/cli/src/core/version.ts index 94fd3b9c..978bd489 100644 --- a/packages/cli/src/core/version.ts +++ b/packages/cli/src/core/version.ts @@ -1,2 +1,33 @@ /** MaxsimCLI version — auto-injected from package.json at build time. */ -export const VERSION = '5.10.0'; +export const VERSION = '5.12.0'; + +/** + * Parse a semantic version string into components. + * Returns null if the string is not a valid semver. + */ +export function parseVersion(versionStr: string): { major: number; minor: number; patch: number } | null { + const match = versionStr.match(/^(\d+)\.(\d+)\.(\d+)/); + if (!match) return null; + return { + major: Number.parseInt(match[1], 10), + minor: Number.parseInt(match[2], 10), + patch: Number.parseInt(match[3], 10), + }; +} + +/** + * Check if the current VERSION is at least the given minimum version. + */ +export function isVersionAtLeast(minimum: string): boolean { + const current = parseVersion(VERSION); + const target = parseVersion(minimum); + if (!current || !target) return false; + if (current.major !== target.major) return current.major > target.major; + if (current.minor !== target.minor) return current.minor > target.minor; + return current.patch >= target.patch; +} + +/** Get the current version string. */ +export function getVersion(): string { + return VERSION; +} diff --git a/packages/cli/src/install/hooks.ts b/packages/cli/src/install/hooks.ts index 1acb779f..3d0260e7 100644 --- a/packages/cli/src/install/hooks.ts +++ b/packages/cli/src/install/hooks.ts @@ -34,6 +34,11 @@ export function installHooks(projectDir: string): { installed: string[] } { const installed: string[] = []; if (copied === 0) { + // Guarantee settings.json exists even if no hooks were copied + if (!fs.existsSync(settingsPath)) { + fs.mkdirSync(path.dirname(settingsPath), { recursive: true }); + fs.writeFileSync(settingsPath, `${JSON.stringify({ hooks: {} }, null, 2)}\n`, 'utf8'); + } return { installed }; } diff --git a/packages/cli/tests/unit/types.test.ts b/packages/cli/tests/unit/types.test.ts index a02a22eb..898a7c03 100644 --- a/packages/cli/tests/unit/types.test.ts +++ b/packages/cli/tests/unit/types.test.ts @@ -17,7 +17,7 @@ import { PARALLELISM_LIMITS, DEFAULT_CONFIG, } from '../../src/core/types.js'; -import { VERSION } from '../../src/core/version.js'; +import { VERSION, parseVersion, isVersionAtLeast, getVersion } from '../../src/core/version.js'; describe('CmdResult', () => { it('cmdOk creates a successful result with data', () => { @@ -148,6 +148,49 @@ describe('VERSION', () => { }); }); +describe('parseVersion', () => { + it('parses a valid semver string', () => { + expect(parseVersion('1.2.3')).toEqual({ major: 1, minor: 2, patch: 3 }); + }); + + it('parses semver with pre-release suffix', () => { + expect(parseVersion('2.0.1-beta.1')).toEqual({ major: 2, minor: 0, patch: 1 }); + }); + + it('returns null for invalid strings', () => { + expect(parseVersion('invalid')).toBeNull(); + expect(parseVersion('')).toBeNull(); + }); +}); + +describe('getVersion', () => { + it('returns a non-empty string matching VERSION', () => { + const v = getVersion(); + expect(typeof v).toBe('string'); + expect(v.length).toBeGreaterThan(0); + expect(v).toBe(VERSION); + }); +}); + +describe('isVersionAtLeast', () => { + it('returns true when current version meets the minimum', () => { + // Current VERSION should be at least 1.0.0 + expect(isVersionAtLeast('1.0.0')).toBe(true); + }); + + it('returns true when current version equals the minimum', () => { + expect(isVersionAtLeast(VERSION)).toBe(true); + }); + + it('returns false when minimum is higher than current', () => { + expect(isVersionAtLeast('999.0.0')).toBe(false); + }); + + it('returns false for invalid version strings', () => { + expect(isVersionAtLeast('invalid')).toBe(false); + }); +}); + describe('PARALLELISM_LIMITS', () => { it('has entries for all 3 profiles', () => { expect(Object.keys(PARALLELISM_LIMITS)).toHaveLength(3); diff --git a/templates/templates/config.json b/templates/templates/config.json index f0d508d1..33ca3190 100644 --- a/templates/templates/config.json +++ b/templates/templates/config.json @@ -1,5 +1,5 @@ { - "version": "6.0.0", + "version": "5.12.0", "execution": { "model_profile": "balanced", "parallelism": { @@ -9,7 +9,13 @@ }, "verification": { "strict_mode": true, - "gates": ["tests", "build", "lint", "spec", "review"], + "gates": [ + "tests", + "build", + "lint", + "spec", + "review" + ], "require_code_review": true, "auto_resolve_conflicts": true }