automagik-genie
Version:
Self-evolving AI agent orchestration framework with Model Context Protocol support
217 lines (216 loc) • 7.25 kB
JavaScript
;
/**
* Migrate agent frontmatter from old schema to new schema
*
* Old schema:
* genie:
* executor: CLAUDE_CODE
* executorVariant: DEFAULT
* model: sonnet
* dangerously_skip_permissions: false
*
* New schema:
* genie:
* executor: CLAUDE_CODE
* variant: DEFAULT
* background: true
* forge:
* model: claude-sonnet-4-5-20250929
* dangerously_skip_permissions: false
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.migrateDirectory = migrateDirectory;
exports.migrateFile = migrateFile;
exports.extractFrontMatter = extractFrontMatter;
exports.migrateFrontmatter = migrateFrontmatter;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const yaml_1 = require("yaml");
// Fields that should move from genie.* to forge.*
const FORGE_FIELDS = [
'model',
'dangerously_skip_permissions',
'sandbox',
'dangerously_allow_all',
'model_reasoning_effort',
'model_reasoning_summary',
'model_reasoning_summary_format',
'profile',
'base_instructions',
'include_plan_tool',
'include_apply_patch_tool',
'claude_code_router',
'plan',
'approvals',
'base_command_override',
'additional_params',
'ask_for_approval',
'oss',
'force',
'yolo',
'allow_all_tools',
'allow_tool',
'deny_tool',
'add_dir',
'disable_mcp_server',
'agent'
];
// Fields that stay in genie.*
const GENIE_FIELDS = [
'executor',
'variant', // Will be renamed from executorVariant
'background'
];
function extractFrontMatter(content) {
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
if (!match) {
return null;
}
return {
frontmatter: match[1],
body: match[2]
};
}
function migrateFrontmatter(frontmatter) {
const migrated = { ...frontmatter };
const movedFields = [];
// Initialize forge object if genie has executor fields
if (migrated.genie) {
migrated.forge = migrated.forge || {};
// Rename executorVariant to variant
if (migrated.genie.executorVariant !== undefined) {
migrated.genie.variant = migrated.genie.executorVariant;
delete migrated.genie.executorVariant;
movedFields.push('executorVariant → variant');
}
// Move executor-specific fields to forge namespace
for (const field of FORGE_FIELDS) {
if (migrated.genie[field] !== undefined) {
migrated.forge[field] = migrated.genie[field];
delete migrated.genie[field];
movedFields.push(`genie.${field} → forge.${field}`);
}
}
// Clean up empty genie object
if (Object.keys(migrated.genie).length === 0) {
delete migrated.genie;
}
// Clean up empty forge object
if (Object.keys(migrated.forge).length === 0) {
delete migrated.forge;
}
}
return { migrated, movedFields };
}
async function migrateFile(filePath) {
const content = fs_1.default.readFileSync(filePath, 'utf-8');
const parsed = extractFrontMatter(content);
if (!parsed) {
throw new Error('No frontmatter found');
}
const frontmatter = (0, yaml_1.parse)(parsed.frontmatter);
const { migrated, movedFields } = migrateFrontmatter(frontmatter);
// Only write if changes were made
if (movedFields.length === 0) {
return [];
}
// Reconstruct file with migrated frontmatter
const newFrontmatter = (0, yaml_1.stringify)(migrated, {
lineWidth: 0, // Don't wrap lines
defaultStringType: 'PLAIN'
});
const newContent = `---\n${newFrontmatter}---\n${parsed.body}`;
fs_1.default.writeFileSync(filePath, newContent, 'utf-8');
return movedFields;
}
function findMarkdownFiles(dir, skipDirs = []) {
const files = [];
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path_1.default.join(dir, entry.name);
if (entry.isDirectory()) {
// Skip certain directories
if (skipDirs.includes(entry.name)) {
continue;
}
// Recursively search subdirectories
files.push(...findMarkdownFiles(fullPath, skipDirs));
}
else if (entry.isFile() && entry.name.endsWith('.md')) {
files.push(fullPath);
}
}
return files;
}
async function migrateDirectory(dir) {
const stats = {
totalFiles: 0,
migratedFiles: 0,
skippedFiles: 0,
errors: [],
changes: []
};
// Find all .md files recursively, skipping certain directories
const skipDirs = ['node_modules', 'dist', '.git', 'spells', 'workflows', 'reports', 'wishes'];
const files = findMarkdownFiles(dir, skipDirs);
stats.totalFiles = files.length;
for (const file of files) {
try {
const movedFields = await migrateFile(file);
if (movedFields.length > 0) {
stats.migratedFiles++;
stats.changes.push({
file: path_1.default.relative(dir, file),
movedFields
});
console.log(`✓ Migrated: ${path_1.default.relative(dir, file)} (${movedFields.length} changes)`);
}
else {
stats.skippedFiles++;
}
}
catch (error) {
stats.errors.push(`${path_1.default.relative(dir, file)}: ${error.message}`);
console.warn(`⚠ Skipped: ${path_1.default.relative(dir, file)} - ${error.message}`);
}
}
return stats;
}
async function main() {
const args = process.argv.slice(2);
const targetDir = args[0] || '.genie';
if (!fs_1.default.existsSync(targetDir)) {
console.error(`❌ Directory not found: ${targetDir}`);
process.exit(1);
}
console.log(`🔄 Migrating frontmatter in: ${targetDir}\n`);
const stats = await migrateDirectory(targetDir);
console.log('\n📊 Migration Summary:');
console.log(` Total files scanned: ${stats.totalFiles}`);
console.log(` Files migrated: ${stats.migratedFiles}`);
console.log(` Files skipped (no changes): ${stats.skippedFiles}`);
console.log(` Errors: ${stats.errors.length}`);
if (stats.errors.length > 0) {
console.log('\n⚠️ Errors:');
stats.errors.forEach(err => console.log(` - ${err}`));
}
if (stats.changes.length > 0) {
console.log('\n📝 Changes made:');
stats.changes.forEach(change => {
console.log(` ${change.file}:`);
change.movedFields.forEach(field => console.log(` - ${field}`));
});
}
console.log('\n✅ Migration complete!');
}
// Run if executed directly
if (require.main === module) {
main().catch(error => {
console.error('❌ Migration failed:', error);
process.exit(1);
});
}