DATABASE SYNC INFRASTRUCTURE:
- scripts/sync-instructions-to-db.js
- Syncs .claude/instruction-history.json to MongoDB governanceRules collection
- Handles inserts, updates, and deactivations
- Validates file and database counts match
- Used in governance audit (54 → 56 → 59 active rules)
- Required for production deployment of governance rules
CSP COMPLIANCE CHECKING:
- scripts/check-csp-violations.js
- Enforces Content Security Policy compliance (inst_008)
- Checks staged HTML files for:
- Inline scripts (<script> tags with code)
- Inline event handlers (onclick, onload, etc.)
- Inline styles (style attributes)
- Integrated with .git/hooks/pre-commit
- Blocks commits with CSP violations
REASON FOR CREATION:
- sync-instructions-to-db.js: Needed to deploy governance rules to production
- check-csp-violations.js: Pre-commit hook was calling missing script
USAGE:
- Sync to DB: node scripts/sync-instructions-to-db.js
- CSP check: Runs automatically on git commit (via pre-commit hook)
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
198 lines
7.1 KiB
JavaScript
Executable file
198 lines
7.1 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Sync Instructions to MongoDB
|
|
*
|
|
* Syncs .claude/instruction-history.json to MongoDB governanceRules collection
|
|
* Handles: inserts, updates, deactivations
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const mongoose = require('mongoose');
|
|
require('dotenv').config();
|
|
|
|
const GovernanceRule = require('../src/models/GovernanceRule.model');
|
|
|
|
const INSTRUCTION_FILE = path.join(__dirname, '../.claude/instruction-history.json');
|
|
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/tractatus_dev';
|
|
|
|
async function syncToDatabase() {
|
|
console.log('═══════════════════════════════════════════════════════════');
|
|
console.log(' SYNC INSTRUCTIONS TO MONGODB');
|
|
console.log('═══════════════════════════════════════════════════════════\n');
|
|
|
|
try {
|
|
// Connect to MongoDB
|
|
console.log('📡 Connecting to MongoDB...');
|
|
await mongoose.connect(MONGODB_URI);
|
|
console.log(` ✓ Connected to ${MONGODB_URI}\n`);
|
|
|
|
// Read instruction history
|
|
console.log('📖 Reading instruction-history.json...');
|
|
const data = JSON.parse(fs.readFileSync(INSTRUCTION_FILE, 'utf8'));
|
|
console.log(` ✓ Version: ${data.version}`);
|
|
console.log(` ✓ Total instructions: ${data.instructions.length}`);
|
|
console.log(` ✓ Active instructions: ${data.instructions.filter(i => i.active !== false).length}\n`);
|
|
|
|
// Get current rules from database
|
|
console.log('📊 Fetching current rules from database...');
|
|
const existingRules = await GovernanceRule.find({});
|
|
const existingRuleIds = new Set(existingRules.map(r => r.id));
|
|
console.log(` ✓ Found ${existingRules.length} existing rules\n`);
|
|
|
|
// Sync stats
|
|
let inserted = 0;
|
|
let updated = 0;
|
|
let deactivated = 0;
|
|
let skipped = 0;
|
|
const errors = [];
|
|
|
|
console.log('🔄 Syncing instructions...\n');
|
|
|
|
// Process each instruction
|
|
for (const inst of data.instructions) {
|
|
try {
|
|
const ruleData = {
|
|
id: inst.id,
|
|
text: inst.text,
|
|
quadrant: inst.quadrant,
|
|
persistence: inst.persistence,
|
|
temporalScope: inst.temporal_scope || 'PERMANENT',
|
|
active: inst.active !== false,
|
|
notes: inst.notes || '',
|
|
source: inst.session_id ? 'user_instruction' : 'framework_default',
|
|
createdBy: 'claude-code'
|
|
};
|
|
|
|
// Handle additional fields if present
|
|
if (inst.parameters) {
|
|
// Store parameters as notes if not already in notes
|
|
if (!ruleData.notes.includes('Parameters:')) {
|
|
ruleData.notes += `\n\nParameters: ${JSON.stringify(inst.parameters)}`;
|
|
}
|
|
}
|
|
|
|
if (inst.deprecates) {
|
|
if (!ruleData.notes.includes('Deprecates:')) {
|
|
ruleData.notes += `\n\nDeprecates: ${inst.deprecates.join(', ')}`;
|
|
}
|
|
}
|
|
|
|
if (inst.replaces) {
|
|
if (!ruleData.notes.includes('Replaces:')) {
|
|
ruleData.notes += `\n\nReplaces: ${inst.replaces.join(', ')}`;
|
|
}
|
|
}
|
|
|
|
if (inst.part_of) {
|
|
if (!ruleData.notes.includes('Part of:')) {
|
|
ruleData.notes += `\n\nPart of: ${inst.part_of}`;
|
|
}
|
|
}
|
|
|
|
if (inst.created_date) {
|
|
if (!ruleData.notes.includes('Created:')) {
|
|
ruleData.notes += `\n\nCreated: ${inst.created_date}`;
|
|
}
|
|
}
|
|
|
|
if (inst.deprecation_reason) {
|
|
if (!ruleData.notes.includes('Deprecation reason:')) {
|
|
ruleData.notes += `\n\nDeprecation reason: ${inst.deprecation_reason}`;
|
|
}
|
|
}
|
|
|
|
// Clean up notes (remove leading/trailing whitespace)
|
|
ruleData.notes = ruleData.notes.trim();
|
|
|
|
if (existingRuleIds.has(inst.id)) {
|
|
// Update existing rule
|
|
const result = await GovernanceRule.findOneAndUpdate(
|
|
{ id: inst.id },
|
|
ruleData,
|
|
{ new: true, runValidators: true }
|
|
);
|
|
|
|
if (result) {
|
|
updated++;
|
|
console.log(` ↻ Updated ${inst.id}`);
|
|
} else {
|
|
errors.push({ id: inst.id, error: 'Update returned null' });
|
|
console.log(` ✗ Failed to update ${inst.id}`);
|
|
}
|
|
} else {
|
|
// Insert new rule
|
|
const newRule = new GovernanceRule(ruleData);
|
|
await newRule.save();
|
|
inserted++;
|
|
console.log(` + Inserted ${inst.id}`);
|
|
}
|
|
} catch (err) {
|
|
errors.push({ id: inst.id, error: err.message });
|
|
console.log(` ✗ Error processing ${inst.id}: ${err.message}`);
|
|
}
|
|
}
|
|
|
|
console.log('');
|
|
|
|
// Deactivate rules that no longer exist in JSON
|
|
const jsonRuleIds = new Set(data.instructions.map(i => i.id));
|
|
for (const existingRule of existingRules) {
|
|
if (!jsonRuleIds.has(existingRule.id) && existingRule.active) {
|
|
existingRule.active = false;
|
|
existingRule.notes += `\n\nDeactivated during sync on ${new Date().toISOString()} - no longer in instruction-history.json`;
|
|
await existingRule.save();
|
|
deactivated++;
|
|
console.log(` ⊝ Deactivated ${existingRule.id}`);
|
|
}
|
|
}
|
|
|
|
if (deactivated > 0) console.log('');
|
|
|
|
// Summary
|
|
console.log('═══════════════════════════════════════════════════════════');
|
|
console.log(' SYNC SUMMARY');
|
|
console.log('═══════════════════════════════════════════════════════════\n');
|
|
console.log(` Inserted: ${inserted}`);
|
|
console.log(` Updated: ${updated}`);
|
|
console.log(` Deactivated: ${deactivated}`);
|
|
console.log(` Errors: ${errors.length}`);
|
|
console.log('');
|
|
|
|
if (errors.length > 0) {
|
|
console.log(' Errors encountered:');
|
|
errors.forEach(({ id, error }) => {
|
|
console.log(` - ${id}: ${error}`);
|
|
});
|
|
console.log('');
|
|
}
|
|
|
|
// Verify final counts
|
|
const activeCount = await GovernanceRule.countDocuments({ active: true });
|
|
const totalCount = await GovernanceRule.countDocuments({});
|
|
|
|
console.log(` Database: ${activeCount} active / ${totalCount} total`);
|
|
console.log(` JSON file: ${data.stats.active_instructions} active / ${data.stats.total_instructions} total`);
|
|
console.log('');
|
|
|
|
if (activeCount === data.stats.active_instructions) {
|
|
console.log('✅ Sync successful - counts match!');
|
|
} else {
|
|
console.log('⚠️ WARNING: Active counts do not match');
|
|
console.log(` Expected ${data.stats.active_instructions}, got ${activeCount}`);
|
|
}
|
|
|
|
console.log('');
|
|
|
|
} catch (err) {
|
|
console.error('❌ Sync failed:', err.message);
|
|
console.error(err.stack);
|
|
process.exit(1);
|
|
} finally {
|
|
await mongoose.disconnect();
|
|
console.log('📡 Disconnected from MongoDB\n');
|
|
}
|
|
}
|
|
|
|
syncToDatabase();
|