tractatus/scripts/migrate-to-memory-proxy.js
TheFlow c735a4e91f feat: Phase 5 PoC Week 3 - MemoryProxy integration with Tractatus services
Complete integration of MemoryProxy service with BoundaryEnforcer and BlogCuration.
All services enhanced with persistent rule storage and audit trail logging.

**Week 3 Summary**:
- MemoryProxy integrated with 2 production services
- 100% backward compatibility (99/99 tests passing)
- Comprehensive audit trail (JSONL format)
- Migration script for .claude/ → .memory/ transition

**BoundaryEnforcer Integration**:
- Added initialize() method to load inst_016, inst_017, inst_018
- Enhanced enforce() with async audit logging
- 43/43 existing tests passing
- 5/5 new integration scenarios passing (100% accuracy)
- Non-blocking audit to .memory/audit/decisions-{date}.jsonl

**BlogCuration Integration**:
- Added initialize() method for rule loading
- Enhanced _validateContent() with audit trail
- 26/26 existing tests passing
- Validation logic unchanged (backward compatible)
- Audit logging for all content validation decisions

**Migration Script**:
- Created scripts/migrate-to-memory-proxy.js
- Migrated 18 rules from .claude/instruction-history.json
- Automatic backup creation
- Full verification (18/18 rules + 3/3 critical rules)
- Dry-run mode for safe testing

**Performance**:
- MemoryProxy overhead: ~2ms per service (~5% increase)
- Audit logging: <1ms (async, non-blocking)
- Rule loading: 1ms for 3 rules (cache enabled)
- Total latency impact: negligible

**Files Modified**:
- src/services/BoundaryEnforcer.service.js (MemoryProxy integration)
- src/services/BlogCuration.service.js (MemoryProxy integration)
- tests/poc/memory-tool/week3-boundary-enforcer-integration.js (new)
- scripts/migrate-to-memory-proxy.js (new)
- docs/research/phase-5-week-3-summary.md (new)
- .memory/governance/tractatus-rules-v1.json (migrated rules)

**Test Results**:
- MemoryProxy: 25/25 
- BoundaryEnforcer: 43/43 + 5/5 integration 
- BlogCuration: 26/26 
- Total: 99/99 tests passing (100%)

**Next Steps**:
- Optional: Context editing experiments (50+ turn conversations)
- Production deployment with MemoryProxy initialization
- Monitor audit trail for governance insights

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-10 12:22:06 +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 };