Files
proffesor-for-testing__agen…/scripts/rollback-migration.ts
Profa b3d2018ee2 feat: v1.8.0 - Quality Hardening & Pattern Isolation
Critical bug fixes and code quality improvements from brutal-honest-review.

## Critical Fixes (9/10 completed, 1 deferred)

1. Fixed Agent Pattern Isolation (CRITICAL)
   - Added SwarmMemoryManager.queryPatternsByAgent()
   - SQL filtering by agent_id in metadata
   - Each agent now isolated

2. Fixed Async Method Cascade
   - Changed getPatterns() to async
   - Fixed 10 callers across 6 files
   - Build passes

3. Fixed Misleading Logging
   - Added isRealAgentDB() detection
   - Honest mock vs real reporting

4. Consolidated Embedding Generation
   - Created EmbeddingGenerator utility
   - Removed 50+ duplicate lines

5. Added AgentDB Initialization Checks
   - Empty DB detection
   - HNSW index verification

6. Integration Test Suite
   - 26 new test cases
   - Error path coverage

7. Repository Cleanup
   - Removed 19 temp test files

8. New QE Skill: sherlock-review
   - Evidence-based investigative review
   - 38 QE skills total

9. Deferred: Wire Up Real Test Execution
   - Deferred to v1.9.0 (documented)

## Statistics
- Fixes: 9/10 (90%, 1 deferred)
- Files Modified: 16
- Files Created: 3
- Files Deleted: 19
- Integration Tests: 26
- Build: PASSING

## Breaking Changes
- getPatterns() now async - add await

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 12:32:03 +00:00

259 lines
7.2 KiB
TypeScript
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env tsx
/**
* AgentDB Migration Rollback Script
*
* Restores database from backup and reverts migration changes.
*
* Features:
* - Automatic backup detection
* - Integrity verification
* - Safe restoration
* - Cleanup options
*
* Usage:
* npm run migrate:rollback # Use latest backup
* npm run migrate:rollback -- --backup-file=<path> # Specific backup
* npm run migrate:rollback -- --cleanup # Remove target after rollback
*/
import * as fs from 'fs';
import * as path from 'path';
import { createHash } from 'crypto';
import Database from 'better-sqlite3';
import chalk from 'chalk';
interface RollbackOptions {
backupFile?: string;
cleanup?: boolean;
verbose?: boolean;
targetDir?: string;
}
interface RollbackResult {
success: boolean;
backupRestored?: string;
targetRemoved?: boolean;
error?: string;
}
/**
* Calculate SHA-256 checksum of a file
*/
function calculateChecksum(filePath: string): string {
const data = fs.readFileSync(filePath);
return createHash('sha256').update(data).digest('hex');
}
/**
* Verify database integrity
*/
function verifyDatabaseIntegrity(dbPath: string): boolean {
const db = new Database(dbPath, { readonly: true });
try {
const result = db.pragma('integrity_check');
return Array.isArray(result) && result.length === 1 && result[0].integrity_check === 'ok';
} finally {
db.close();
}
}
/**
* Find latest backup file
*/
function findLatestBackup(basePath: string): string | null {
const backupPattern = `${basePath}.backup.`;
const dir = path.dirname(basePath);
const files = fs.readdirSync(dir);
const backupFiles = files
.filter(f => f.startsWith(path.basename(backupPattern)))
.map(f => ({
name: f,
path: path.join(dir, f),
timestamp: parseInt(f.split('.').pop() || '0')
}))
.sort((a, b) => b.timestamp - a.timestamp);
return backupFiles.length > 0 ? backupFiles[0].path : null;
}
/**
* Get database statistics
*/
function getDatabaseStats(dbPath: string): any {
const db = new Database(dbPath, { readonly: true });
try {
const tables = db.prepare(`
SELECT name FROM sqlite_master
WHERE type='table' AND name NOT LIKE 'sqlite_%'
ORDER BY name
`).all() as Array<{ name: string }>;
const stats: any = {
tables: {},
totalRecords: 0
};
for (const table of tables) {
const countResult = db.prepare(`SELECT COUNT(*) as count FROM ${table.name}`).get() as { count: number };
stats.tables[table.name] = countResult.count;
stats.totalRecords += countResult.count;
}
return stats;
} finally {
db.close();
}
}
/**
* Main rollback function
*/
export async function rollbackMigration(options: RollbackOptions = {}): Promise<RollbackResult> {
const {
backupFile,
cleanup = false,
verbose = false,
targetDir = '.agentic-qe'
} = options;
console.log(chalk.cyan('\n⏮️ Starting AgentDB Migration Rollback...\n'));
try {
// 1. Determine backup file to use
let backupPath: string;
if (backupFile) {
backupPath = path.resolve(backupFile);
if (!fs.existsSync(backupPath)) {
throw new Error(`Backup file not found: ${backupPath}`);
}
console.log(chalk.green(`✓ Using specified backup: ${backupPath}`));
} else {
// Find latest backup
const sourcePath = path.join(process.cwd(), 'agentdb.db');
const latestBackup = findLatestBackup(sourcePath);
if (!latestBackup) {
throw new Error('No backup files found. Cannot rollback.');
}
backupPath = latestBackup;
console.log(chalk.green(`✓ Found latest backup: ${backupPath}`));
}
// 2. Verify backup integrity
console.log(chalk.cyan('\n🔍 Verifying backup integrity...'));
if (!verifyDatabaseIntegrity(backupPath)) {
throw new Error('Backup database integrity check failed!');
}
console.log(chalk.green('✓ Backup integrity verified'));
// 3. Get backup statistics
if (verbose) {
console.log(chalk.cyan('\n📊 Backup statistics:'));
const stats = getDatabaseStats(backupPath);
console.log(chalk.gray(` Total records: ${stats.totalRecords}`));
for (const [tableName, count] of Object.entries(stats.tables)) {
console.log(chalk.gray(` - ${tableName}: ${count}`));
}
}
// 4. Restore backup to original location
console.log(chalk.cyan('\n♻️ Restoring backup...'));
const sourcePath = path.join(process.cwd(), 'agentdb.db');
// Create safety backup of current state
if (fs.existsSync(sourcePath)) {
const safetyBackup = `${sourcePath}.pre-rollback.${Date.now()}`;
fs.copyFileSync(sourcePath, safetyBackup);
console.log(chalk.gray(` Safety backup: ${safetyBackup}`));
}
// Restore from backup
fs.copyFileSync(backupPath, sourcePath);
console.log(chalk.green(`✓ Backup restored to: ${sourcePath}`));
// 5. Verify restoration
console.log(chalk.cyan('\n🔐 Verifying restoration...'));
const backupChecksum = calculateChecksum(backupPath);
const restoredChecksum = calculateChecksum(sourcePath);
if (backupChecksum !== restoredChecksum) {
throw new Error('Checksum mismatch after restoration!');
}
console.log(chalk.green('✓ Restoration verified'));
// 6. Cleanup target directory if requested
let targetRemoved = false;
if (cleanup) {
console.log(chalk.cyan('\n🧹 Cleaning up target directory...'));
const targetPath = path.join(process.cwd(), targetDir, 'agentdb.db');
if (fs.existsSync(targetPath)) {
// Move to trash instead of deleting
const trashPath = `${targetPath}.removed.${Date.now()}`;
fs.renameSync(targetPath, trashPath);
console.log(chalk.green(`✓ Target moved to: ${trashPath}`));
targetRemoved = true;
} else {
console.log(chalk.gray(' No target database to remove'));
}
}
// 7. Success summary
console.log(chalk.green.bold('\n✅ Rollback Complete!\n'));
console.log(chalk.gray('Summary:'));
console.log(chalk.gray(` Backup: ${backupPath}`));
console.log(chalk.gray(` Restored: ${sourcePath}`));
console.log(chalk.gray(` Checksum: ${restoredChecksum}`));
if (cleanup) {
console.log(chalk.gray(` Cleanup: ${targetRemoved ? 'Yes' : 'No target found'}`));
}
return {
success: true,
backupRestored: backupPath,
targetRemoved
};
} catch (error: any) {
console.error(chalk.red('\n❌ Rollback failed:'), error.message);
return {
success: false,
error: error.message
};
}
}
/**
* CLI entry point
*/
if (require.main === module) {
const args = process.argv.slice(2);
const options: RollbackOptions = {
cleanup: args.includes('--cleanup'),
verbose: args.includes('--verbose') || args.includes('-v')
};
// Parse --backup-file argument
const backupArg = args.find(arg => arg.startsWith('--backup-file='));
if (backupArg) {
options.backupFile = backupArg.split('=')[1];
}
rollbackMigration(options)
.then((result) => {
if (!result.success) {
process.exit(1);
}
})
.catch((error) => {
console.error(chalk.red('Fatal error:'), error);
process.exit(1);
});
}