tractatus/scripts/migrate-to-memory-proxy.js
TheFlow 0e6be3eaf1 refactor: remove website code and fix critical startup crashes (Phase 8)
CRITICAL FIX: Server would CRASH ON STARTUP (multiple import errors)

REMOVED (2 scripts):
1. scripts/framework-watchdog.js
   - Monitored .claude/session-state.json (OUR Claude Code setup)
   - Monitored .claude/token-checkpoints.json (OUR file structure)
   - Implementers won't have our .claude/ directory

2. scripts/init-db.js
   - Created website collections: blog_posts, media_inquiries, case_submissions
   - Created website collections: resources, moderation_queue, users, citations
   - Created website collections: translations, koha_donations
   - Next steps referenced deleted scripts (npm run seed:admin)

REWRITTEN (2 files):

src/models/index.js (29 lines → 27 lines)
- REMOVED imports: Document, BlogPost, MediaInquiry, CaseSubmission, Resource
- REMOVED imports: ModerationQueue, User (all deleted in Phase 2)
- KEPT imports: AuditLog, DeliberationSession, GovernanceLog, GovernanceRule
- KEPT imports: Precedent, Project, SessionState, VariableValue, VerificationLog
- Result: Only framework models exported

src/server.js (284 lines → 163 lines, 43% reduction)
- REMOVED: Imports to deleted middleware (csrf-protection, response-sanitization)
- REMOVED: Stripe webhook handling (/api/koha/webhook)
- REMOVED: Static file caching (for deleted public/ directory)
- REMOVED: Static file serving (public/ deleted in Phase 6)
- REMOVED: CSRF token endpoint
- REMOVED: Website homepage with "auth, documents, blog, admin" references
- REMOVED: Instruction sync (scripts/sync-instructions-to-db.js reference)
- REMOVED: Hardcoded log path (${process.env.HOME}/var/log/tractatus/...)
- REMOVED: Website-specific security middleware
- KEPT: Security headers, rate limiting, CORS, body parsers
- KEPT: API routes, governance services, MongoDB connections
- RESULT: Clean framework-only server

RESULT: Repository can now start without crashes, all imports resolve

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 22:17:02 +13:00

432 lines
13 KiB
JavaScript
Executable file
Raw 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 node
/**
* Migration Script: .claude/instruction-history.json → .memory/governance/
*
* Migrates Tractatus governance rules from legacy .claude/ directory
* to new MemoryProxy-managed .memory/governance/ directory.
*
* Phase 5 PoC - Week 3: Production Migration
*
* Usage:
* node scripts/migrate-to-memory-proxy.js [--dry-run] [--backup]
*
* Options:
* --dry-run Preview migration without making changes
* --backup Create backup of source file before migration (default: true)
* --force Skip confirmation prompts
*/
const fs = require('fs').promises;
const path = require('path');
const { MemoryProxyService } = require('../src/services/MemoryProxy.service');
const logger = require('../src/utils/logger.util');
// Configuration
const SOURCE_PATH = path.join(__dirname, '../.claude/instruction-history.json');
const BACKUP_DIR = path.join(__dirname, '../.claude/backups');
const MEMORY_BASE_PATH = path.join(__dirname, '../.memory');
// Parse command line arguments
const args = process.argv.slice(2);
const isDryRun = args.includes('--dry-run');
const createBackup = !args.includes('--no-backup');
const forceMode = args.includes('--force');
/**
* Validate source file exists and is readable
*/
async function validateSource() {
try {
const stats = await fs.stat(SOURCE_PATH);
if (!stats.isFile()) {
throw new Error('Source path is not a file');
}
return true;
} catch (error) {
if (error.code === 'ENOENT') {
throw new Error(`Source file not found: ${SOURCE_PATH}`);
}
throw new Error(`Cannot access source file: ${error.message}`);
}
}
/**
* Load rules from source file
*/
async function loadSourceRules() {
try {
const data = await fs.readFile(SOURCE_PATH, 'utf8');
const parsed = JSON.parse(data);
if (!parsed.instructions || !Array.isArray(parsed.instructions)) {
throw new Error('Invalid source format: missing instructions array');
}
return parsed.instructions;
} catch (error) {
if (error instanceof SyntaxError) {
throw new Error(`Invalid JSON in source file: ${error.message}`);
}
throw error;
}
}
/**
* Create backup of source file
*/
async function createSourceBackup() {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupPath = path.join(BACKUP_DIR, `instruction-history-${timestamp}.json`);
try {
// Create backup directory if it doesn't exist
await fs.mkdir(BACKUP_DIR, { recursive: true });
// Copy source file to backup
await fs.copyFile(SOURCE_PATH, backupPath);
console.log(` ✓ Backup created: ${backupPath}`);
return backupPath;
} catch (error) {
throw new Error(`Failed to create backup: ${error.message}`);
}
}
/**
* Validate rules before migration
*/
function validateRules(rules) {
const issues = [];
rules.forEach((rule, index) => {
if (!rule.id) {
issues.push(`Rule ${index}: missing 'id' field`);
}
if (!rule.text) {
issues.push(`Rule ${index}: missing 'text' field`);
}
if (!rule.quadrant) {
issues.push(`Rule ${index}: missing 'quadrant' field`);
}
if (!rule.persistence) {
issues.push(`Rule ${index}: missing 'persistence' field`);
}
});
return issues;
}
/**
* Analyze rules for migration preview
*/
function analyzeRules(rules) {
const analysis = {
total: rules.length,
by_quadrant: {},
by_persistence: {},
active: 0,
inactive: 0,
critical_rules: []
};
rules.forEach(rule => {
// Count by quadrant
analysis.by_quadrant[rule.quadrant] = (analysis.by_quadrant[rule.quadrant] || 0) + 1;
// Count by persistence
analysis.by_persistence[rule.persistence] = (analysis.by_persistence[rule.persistence] || 0) + 1;
// Count active/inactive
if (rule.active !== false) {
analysis.active++;
} else {
analysis.inactive++;
}
// Identify critical enforcement rules
if (['inst_016', 'inst_017', 'inst_018'].includes(rule.id)) {
analysis.critical_rules.push(rule.id);
}
});
return analysis;
}
/**
* Perform migration
*/
async function migrate(rules) {
const memoryProxy = new MemoryProxyService({
memoryBasePath: MEMORY_BASE_PATH
});
try {
// Initialize MemoryProxy
await memoryProxy.initialize();
console.log(' ✓ MemoryProxy initialized');
// Persist rules
const result = await memoryProxy.persistGovernanceRules(rules);
return result;
} catch (error) {
throw new Error(`Migration failed: ${error.message}`);
}
}
/**
* Verify migration success
*/
async function verifyMigration(originalRules) {
const memoryProxy = new MemoryProxyService({
memoryBasePath: MEMORY_BASE_PATH
});
try {
await memoryProxy.initialize();
// Load rules from memory
const migratedRules = await memoryProxy.loadGovernanceRules();
// Compare counts
if (migratedRules.length !== originalRules.length) {
throw new Error(`Rule count mismatch: expected ${originalRules.length}, got ${migratedRules.length}`);
}
// Verify critical rules
const criticalRuleIds = ['inst_016', 'inst_017', 'inst_018'];
for (const ruleId of criticalRuleIds) {
const rule = await memoryProxy.getRule(ruleId);
if (!rule) {
throw new Error(`Critical rule ${ruleId} not found after migration`);
}
}
// Verify data integrity for all rules
for (let i = 0; i < originalRules.length; i++) {
const original = originalRules[i];
const migrated = migratedRules.find(r => r.id === original.id);
if (!migrated) {
throw new Error(`Rule ${original.id} missing after migration`);
}
// Check critical fields
if (migrated.text !== original.text) {
throw new Error(`Rule ${original.id}: text mismatch`);
}
if (migrated.quadrant !== original.quadrant) {
throw new Error(`Rule ${original.id}: quadrant mismatch`);
}
if (migrated.persistence !== original.persistence) {
throw new Error(`Rule ${original.id}: persistence mismatch`);
}
}
return {
success: true,
rulesVerified: migratedRules.length,
criticalRulesVerified: criticalRuleIds.length
};
} catch (error) {
throw new Error(`Verification failed: ${error.message}`);
}
}
/**
* Confirm migration with user (unless --force)
*/
async function confirmMigration(analysis) {
if (forceMode) {
return true;
}
console.log('\n⚠ Migration will:');
console.log(` • Copy ${analysis.total} rules to .memory/governance/`);
console.log(` • Preserve all rule metadata and fields`);
if (createBackup) {
console.log(` • Create backup of source file in .claude/backups/`);
}
console.log('\nContinue? (yes/no): ');
// Read user input
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise(resolve => {
readline.question('', answer => {
readline.close();
resolve(answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y');
});
});
}
/**
* Main migration workflow
*/
async function main() {
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log(' Tractatus Governance Rules Migration');
console.log(' .claude/ → .memory/governance/');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
if (isDryRun) {
console.log('🔍 DRY RUN MODE - No changes will be made\n');
}
const results = {
success: false,
rulesLoaded: 0,
rulesMigrated: 0,
backupPath: null,
errors: []
};
try {
// Step 1: Validate source
console.log('[Step 1] Validating source file...');
await validateSource();
console.log(` ✓ Source exists: ${SOURCE_PATH}\n`);
// Step 2: Load rules
console.log('[Step 2] Loading governance rules...');
const rules = await loadSourceRules();
results.rulesLoaded = rules.length;
console.log(` ✓ Loaded ${rules.length} rules\n`);
// Step 3: Validate rules
console.log('[Step 3] Validating rule format...');
const validationIssues = validateRules(rules);
if (validationIssues.length > 0) {
console.log(' ✗ Validation issues found:');
validationIssues.forEach(issue => console.log(`${issue}`));
throw new Error('Rule validation failed');
}
console.log(` ✓ All ${rules.length} rules valid\n`);
// Step 4: Analyze rules
console.log('[Step 4] Analyzing rules...');
const analysis = analyzeRules(rules);
console.log(` Total: ${analysis.total} rules`);
console.log(` Active: ${analysis.active} | Inactive: ${analysis.inactive}`);
console.log('\n By Quadrant:');
Object.entries(analysis.by_quadrant).forEach(([quadrant, count]) => {
console.log(` ${quadrant}: ${count}`);
});
console.log('\n By Persistence:');
Object.entries(analysis.by_persistence).forEach(([level, count]) => {
console.log(` ${level}: ${count}`);
});
console.log(`\n Critical Rules: ${analysis.critical_rules.join(', ')}\n`);
// Step 5: Confirm migration
if (!isDryRun) {
console.log('[Step 5] Confirming migration...');
const confirmed = await confirmMigration(analysis);
if (!confirmed) {
console.log('\n❌ Migration cancelled by user\n');
process.exit(0);
}
console.log(' ✓ Migration confirmed\n');
} else {
console.log('[Step 5] Skipping confirmation (dry-run mode)\n');
}
// Step 6: Create backup
if (createBackup && !isDryRun) {
console.log('[Step 6] Creating backup...');
results.backupPath = await createSourceBackup();
console.log();
} else if (isDryRun) {
console.log('[Step 6] Backup creation (skipped - dry-run)\n');
} else {
console.log('[Step 6] Backup creation (skipped - --no-backup)\n');
}
// Step 7: Migrate rules
if (!isDryRun) {
console.log('[Step 7] Migrating rules to MemoryProxy...');
const migrationResult = await migrate(rules);
results.rulesMigrated = migrationResult.rulesStored;
console.log(` ✓ Migrated ${migrationResult.rulesStored} rules`);
console.log(` Duration: ${migrationResult.duration}ms`);
console.log(` Path: ${migrationResult.path}\n`);
} else {
console.log('[Step 7] Migration (skipped - dry-run)\n');
}
// Step 8: Verify migration
if (!isDryRun) {
console.log('[Step 8] Verifying migration...');
const verification = await verifyMigration(rules);
console.log(` ✓ Verified ${verification.rulesVerified} rules`);
console.log(` ✓ Critical rules: ${verification.criticalRulesVerified}/3\n`);
} else {
console.log('[Step 8] Verification (skipped - dry-run)\n');
}
results.success = true;
} catch (error) {
console.error(`\n✗ MIGRATION FAILED: ${error.message}\n`);
if (error.stack && process.env.DEBUG) {
console.error('Stack trace:', error.stack);
}
results.errors.push(error.message);
results.success = false;
}
// Results summary
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log(' MIGRATION RESULTS');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
if (results.success) {
if (isDryRun) {
console.log('✅ DRY RUN SUCCESSFUL - Ready for actual migration');
console.log('\nTo perform migration, run:');
console.log(' node scripts/migrate-to-memory-proxy.js');
} else {
console.log('✅ MIGRATION SUCCESSFUL');
console.log('\nSummary:');
console.log(` • Rules loaded: ${results.rulesLoaded}`);
console.log(` • Rules migrated: ${results.rulesMigrated}`);
if (results.backupPath) {
console.log(` • Backup: ${results.backupPath}`);
}
console.log('\nNext Steps:');
console.log(' 1. Initialize services: await service.initialize()');
console.log(' 2. Verify services load rules from .memory/');
console.log(' 3. Monitor .memory/audit/ for decision logs');
}
} else {
console.log('❌ MIGRATION FAILED');
console.log('\nErrors:');
results.errors.forEach(err => console.log(`${err}`));
}
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
process.exit(results.success ? 0 : 1);
}
// Run migration
if (require.main === module) {
main().catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});
}
module.exports = { main };