Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ analysis/
gaps.md
/improve.md
philosophy.md
autoresearch-results.tsv
/autoresearch-results.tsv
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
30 changes: 28 additions & 2 deletions packages/cli/scripts/inject-version.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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`;
Comment on lines +38 to +39
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback branch that rewrites src/core/version.ts when the file is missing (or lacks a VERSION export) only writes the VERSION constant, but this PR now relies on additional exports (parseVersion, isVersionAtLeast, getVersion). If this branch ever runs, the build/tests will break because those exports disappear. Update the generated template content to include all expected exports, not just VERSION.

Suggested change
// 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`;
// File missing or doesn't contain VERSION — write the full template with all expected exports
const content = [
'/** MaxsimCLI version — auto-injected from package.json at build time. */',
versionLine,
'',
'export interface ParsedVersion {',
' major: number;',
' minor: number;',
' patch: number;',
' prerelease?: string;',
'}',
'',
'export function parseVersion(v: string = VERSION): ParsedVersion {',
" const match = v.match(/^(\\d+)\\.(\\d+)\\.(\\d+)(-.+)?$/);",
' if (!match) {',
' return { major: 0, minor: 0, patch: 0, prerelease: v };',
' }',
' const [, major, minor, patch, prerelease] = match;',
' return {',
' major: Number(major),',
' minor: Number(minor),',
' patch: Number(patch),',
' ...(prerelease ? { prerelease: prerelease.slice(1) } : {}),',
' };',
'}',
'',
'export function isVersionAtLeast(min: string, current: string = VERSION): boolean {',
' const a = parseVersion(current);',
' const b = parseVersion(min);',
' if (a.major !== b.major) return a.major > b.major;',
' if (a.minor !== b.minor) return a.minor > b.minor;',
' if (a.patch !== b.patch) return a.patch > b.patch;',
' if (a.prerelease === b.prerelease) return true;',
' if (!a.prerelease && b.prerelease) return true;',
' if (a.prerelease && !b.prerelease) return false;',
' if (!a.prerelease && !b.prerelease) return true;',
' return String(a.prerelease) >= String(b.prerelease);',
'}',
'',
'export function getVersion(): string {',
' return VERSION;',
'}',
'',
].join('\n');

Copilot uses AI. Check for mistakes.
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');
}
2 changes: 1 addition & 1 deletion packages/cli/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
33 changes: 32 additions & 1 deletion packages/cli/src/core/version.ts
Original file line number Diff line number Diff line change
@@ -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),
};
Comment on lines +5 to +15
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseVersion claims to validate semver, but the regex only matches a major.minor.patch prefix (e.g. it will accept 1.2.3.4 or 1.2.3foo as “valid”). This can make isVersionAtLeast return incorrect results for malformed inputs. Tighten the regex to require the full string (optionally allowing prerelease/build metadata), or adjust the docstring/behavior accordingly.

Copilot uses AI. Check for mistakes.
}

/**
* 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;
}
5 changes: 5 additions & 0 deletions packages/cli/src/install/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}

Expand Down
45 changes: 44 additions & 1 deletion packages/cli/tests/unit/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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);
Expand Down
10 changes: 8 additions & 2 deletions templates/templates/config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "6.0.0",
"version": "5.12.0",
"execution": {
"model_profile": "balanced",
"parallelism": {
Expand All @@ -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
}
Expand Down
Loading