UNPKG

automagik-genie

Version:

Self-evolving AI agent orchestration framework with Model Context Protocol support

224 lines (223 loc) 9.64 kB
"use strict"; /** * Knowledge diff generation for v2.5.14+ efficient update mechanism * * Instead of full backups, generates a diff file showing changes in: * - AGENTS.md, CLAUDE.md (root files) * - .genie/ folder contents */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateKnowledgeDiff = generateKnowledgeDiff; const path_1 = __importDefault(require("path")); const fs_1 = require("fs"); const fs_utils_1 = require("./fs-utils"); /** * Generate knowledge diff between old and new Genie installations * * @param workspacePath - Root of workspace * @param oldGeniePath - Path to old .genie folder (before upgrade) * @param newGeniePath - Path to new .genie folder (after upgrade) * @param oldVersion - Old version string * @param newVersion - New version string * @returns Path to generated diff file */ async function generateKnowledgeDiff(workspacePath, oldGeniePath, newGeniePath, oldVersion, newVersion) { const diffId = (0, fs_utils_1.toIsoId)(); const diffFileName = `update-diff-${oldVersion}-to-${newVersion}-${diffId}.md`; const diffPath = path_1.default.join(workspacePath, '.genie', 'reports', diffFileName); await (0, fs_utils_1.ensureDir)(path_1.default.dirname(diffPath)); const oldFiles = await collectKnowledgeFiles(oldGeniePath, workspacePath); const newFiles = await collectKnowledgeFiles(newGeniePath, workspacePath); const diffs = await compareFiles(oldFiles, newFiles, oldGeniePath, newGeniePath, workspacePath); const report = await buildDiffReport(diffs, oldVersion, newVersion, diffId); await fs_1.promises.writeFile(diffPath, report, 'utf8'); const summary = { added: diffs.filter(d => d.status === 'added').length, removed: diffs.filter(d => d.status === 'removed').length, modified: diffs.filter(d => d.status === 'modified').length, unchanged: diffs.filter(d => d.status === 'unchanged').length }; return { diffPath, diffId, summary }; } /** * Collect knowledge files (agents, spells, workflows, product, root docs) * Excludes user content (wishes, reports, state, backups) */ async function collectKnowledgeFiles(geniePath, workspacePath) { const files = new Set(); const rootDocs = ['AGENTS.md', 'CLAUDE.md']; for (const doc of rootDocs) { const docPath = path_1.default.join(workspacePath, doc); if (await (0, fs_utils_1.pathExists)(docPath)) { files.add(doc); } } if (await (0, fs_utils_1.pathExists)(geniePath)) { const knowledgeDirs = ['agents', 'spells', 'workflows', 'product', 'code', 'create', 'neurons']; for (const dir of knowledgeDirs) { const dirPath = path_1.default.join(geniePath, dir); if (await (0, fs_utils_1.pathExists)(dirPath)) { const dirFiles = await (0, fs_utils_1.collectFiles)(dirPath, { filter: (relPath) => { const firstSeg = relPath.split(path_1.default.sep)[0]; const excludeDirs = ['wishes', 'reports', 'state', 'backups', 'scripts']; return !excludeDirs.includes(firstSeg); } }); for (const file of dirFiles) { files.add(path_1.default.join('.genie', dir, file)); } } } } return files; } /** * Compare files between old and new installations */ async function compareFiles(oldFiles, newFiles, oldGeniePath, newGeniePath, workspacePath) { const diffs = []; const allPaths = new Set([...oldFiles, ...newFiles]); for (const relPath of allPaths) { const inOld = oldFiles.has(relPath); const inNew = newFiles.has(relPath); if (!inOld && inNew) { const newPath = relPath.startsWith('.genie') ? path_1.default.join(newGeniePath, relPath.replace('.genie/', '').replace('.genie\\', '')) : path_1.default.join(workspacePath, relPath); const stats = await fs_1.promises.stat(newPath); diffs.push({ path: relPath, status: 'added', newSize: stats.size }); } else if (inOld && !inNew) { const oldPath = relPath.startsWith('.genie') ? path_1.default.join(oldGeniePath, relPath.replace('.genie/', '').replace('.genie\\', '')) : path_1.default.join(workspacePath, relPath); const stats = await fs_1.promises.stat(oldPath); diffs.push({ path: relPath, status: 'removed', oldSize: stats.size }); } else { const oldPath = relPath.startsWith('.genie') ? path_1.default.join(oldGeniePath, relPath.replace('.genie/', '').replace('.genie\\', '')) : path_1.default.join(workspacePath, relPath); const newPath = relPath.startsWith('.genie') ? path_1.default.join(newGeniePath, relPath.replace('.genie/', '').replace('.genie\\', '')) : path_1.default.join(workspacePath, relPath); const [oldContent, newContent] = await Promise.all([ fs_1.promises.readFile(oldPath, 'utf8'), fs_1.promises.readFile(newPath, 'utf8') ]); const [oldStats, newStats] = await Promise.all([ fs_1.promises.stat(oldPath), fs_1.promises.stat(newPath) ]); if (oldContent !== newContent) { diffs.push({ path: relPath, status: 'modified', oldSize: oldStats.size, newSize: newStats.size }); } else { diffs.push({ path: relPath, status: 'unchanged', oldSize: oldStats.size, newSize: newStats.size }); } } } return diffs.sort((a, b) => a.path.localeCompare(b.path)); } /** * Build markdown diff report */ async function buildDiffReport(diffs, oldVersion, newVersion, diffId) { const added = diffs.filter(d => d.status === 'added'); const removed = diffs.filter(d => d.status === 'removed'); const modified = diffs.filter(d => d.status === 'modified'); const lines = []; lines.push(`# Genie Knowledge Update Diff`); lines.push(``); lines.push(`**Version Transition:** ${oldVersion}${newVersion}`); lines.push(`**Diff ID:** ${diffId}`); lines.push(`**Generated:** ${new Date().toISOString()}`); lines.push(``); lines.push(`## Summary`); lines.push(``); lines.push(`- **Added:** ${added.length} files`); lines.push(`- **Removed:** ${removed.length} files`); lines.push(`- **Modified:** ${modified.length} files`); lines.push(`- **Total Changes:** ${added.length + removed.length + modified.length} files`); lines.push(``); if (added.length > 0) { lines.push(`## Added Files (${added.length})`); lines.push(``); for (const diff of added) { const size = diff.newSize ? ` (${formatBytes(diff.newSize)})` : ''; lines.push(`- ✅ \`${diff.path}\`${size}`); } lines.push(``); } if (removed.length > 0) { lines.push(`## Removed Files (${removed.length})`); lines.push(``); for (const diff of removed) { const size = diff.oldSize ? ` (${formatBytes(diff.oldSize)})` : ''; lines.push(`- ❌ \`${diff.path}\`${size}`); } lines.push(``); } if (modified.length > 0) { lines.push(`## Modified Files (${modified.length})`); lines.push(``); for (const diff of modified) { const oldSize = diff.oldSize ? formatBytes(diff.oldSize) : '?'; const newSize = diff.newSize ? formatBytes(diff.newSize) : '?'; const delta = diff.oldSize && diff.newSize ? ` (${diff.newSize > diff.oldSize ? '+' : ''}${formatBytes(diff.newSize - diff.oldSize)})` : ''; lines.push(`- 📝 \`${diff.path}\` (${oldSize}${newSize}${delta})`); } lines.push(``); } lines.push(`## Notes`); lines.push(``); lines.push(`This diff shows changes in Genie's "knowledge" (framework files) between versions.`); lines.push(`User content (wishes, reports, state) is excluded from this diff.`); lines.push(``); lines.push(`**Included in diff:**`); lines.push(`- Root documentation: AGENTS.md, CLAUDE.md`); lines.push(`- Framework directories: .genie/agents/, .genie/spells/, .genie/workflows/, .genie/product/`); lines.push(`- Collective directories: .genie/code/, .genie/create/, .genie/neurons/`); lines.push(``); lines.push(`**Excluded from diff:**`); lines.push(`- User content: .genie/wishes/, .genie/reports/`); lines.push(`- Runtime state: .genie/state/, .genie/backups/`); lines.push(`- Helper scripts: .genie/scripts/helpers/`); lines.push(``); return lines.join('\n'); } /** * Format bytes to human-readable string */ function formatBytes(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`; }