feat: add migration script for value pluralism documents

Adds visibility and order fields to the 3 value pluralism documents
so they display correctly in the docs.html sidebar.

🤖 Generated with Claude Code
This commit is contained in:
TheFlow 2025-10-12 19:56:53 +13:00
parent 1906c0be0b
commit 1cd69829d7

245
scripts/migrate-value-pluralism-docs.js Normal file → Executable file
View file

@ -1,203 +1,94 @@
#!/usr/bin/env node
/**
* Migrate Value Pluralism Documents to MongoDB
* Adds three new value pluralism documents to the documents collection
* Migration: Add visibility and order to value pluralism documents
*
* This migration ensures the 3 value pluralism documents are properly
* configured to display in the docs.html sidebar.
*/
// Load environment variables from .env file
require('dotenv').config();
const { MongoClient } = require('mongodb');
const mongoose = require('mongoose');
const marked = require('marked');
const fs = require('fs').promises;
const path = require('path');
const config = require('../src/config/app.config');
const MONGO_URI = process.env.MONGO_URI || 'mongodb://localhost:27017';
const DB_NAME = process.env.MONGO_DB || 'tractatus_prod';
// Document structure from existing documents
const documentsToMigrate = [
const UPDATES = [
{
file: 'docs/research/pluralistic-values-research-foundations.md',
title: 'Pluralistic Values: Research Foundations',
slug: 'pluralistic-values-research-foundations',
quadrant: 'STRATEGIC',
persistence: 'HIGH',
category: 'research',
audience: ['researcher', 'technical'],
tags: ['value-pluralism', 'research', 'deliberative-democracy', 'ethics', 'philosophy'],
description: 'Supporting research material for PluralisticDeliberationOrchestrator implementation, covering deliberative democracy, value pluralism theory, and cross-cultural communication'
},
{
file: 'docs/value-pluralism-faq.md',
title: 'Value Pluralism in Tractatus: Frequently Asked Questions',
slug: 'value-pluralism-faq',
quadrant: null,
persistence: 'HIGH',
category: 'documentation',
audience: ['general'],
tags: ['value-pluralism', 'faq', 'documentation', 'ethics'],
description: 'Accessible explanation of how Tractatus handles moral disagreement without imposing hierarchy'
category: 'getting-started',
visibility: 'public',
order: 3
},
{
slug: 'pluralistic-values-research-foundations',
category: 'research-theory',
visibility: 'public',
order: 1
},
{
file: 'docs/pluralistic-values-deliberation-plan-v2.md',
title: 'Pluralistic Values Deliberation Enhancement Plan',
slug: 'pluralistic-values-deliberation-plan-v2',
quadrant: 'OPERATIONAL',
persistence: 'HIGH',
category: 'implementation-guide',
audience: ['implementer', 'researcher'],
tags: ['value-pluralism', 'implementation', 'deliberation', 'planning'],
description: 'Technical design document for implementing non-hierarchical moral reasoning in the Tractatus Framework'
category: 'technical-reference',
visibility: 'public',
order: 12
}
];
/**
* Generate table of contents from markdown
*/
function generateToC(markdown) {
const toc = [];
const lines = markdown.split('\n');
async function migrate() {
console.log('🔧 Starting value pluralism documents migration...');
console.log(` Database: ${DB_NAME}`);
console.log('');
for (const line of lines) {
const match = line.match(/^(#{1,6})\s+(.+)$/);
if (match) {
const level = match[1].length;
const title = match[2].trim();
const slug = title
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.trim();
toc.push({ level, title, slug });
}
}
return toc;
}
/**
* Generate search index (lowercase, no markdown formatting)
*/
function generateSearchIndex(markdown) {
return markdown
.toLowerCase()
.replace(/```[\s\S]*?```/g, '') // Remove code blocks
.replace(/`[^`]+`/g, '') // Remove inline code
.replace(/[#*_\[\]()]/g, '') // Remove markdown formatting
.replace(/\n+/g, '\n') // Collapse multiple newlines
.trim();
}
/**
* Migrate a single document
*/
async function migrateDocument(docInfo, db) {
console.log(`\nMigrating: ${docInfo.title}`);
// Read markdown file
const markdownPath = path.join(__dirname, '..', docInfo.file);
const markdown = await fs.readFile(markdownPath, 'utf-8');
// Convert to HTML
const html = marked.parse(markdown);
// Generate ToC
const toc = generateToC(markdown);
console.log(` ✓ Generated ToC (${toc.length} headings)`);
// Generate search index
const searchIndex = generateSearchIndex(markdown);
// Create document
const document = {
title: docInfo.title,
slug: docInfo.slug,
quadrant: docInfo.quadrant,
persistence: docInfo.persistence,
content_html: html,
content_markdown: markdown,
toc: toc,
metadata: {
author: 'System',
date_created: new Date(),
date_updated: new Date(),
version: '1.0',
document_code: null,
related_documents: [],
tags: docInfo.tags,
category: docInfo.category,
audience: docInfo.audience,
description: docInfo.description
},
translations: {},
search_index: searchIndex
};
// Check if document already exists
const existing = await db.collection('documents').findOne({ slug: docInfo.slug });
if (existing) {
console.log(` ⚠ Document already exists, updating...`);
await db.collection('documents').updateOne(
{ slug: docInfo.slug },
{ $set: document }
);
console.log(` ✓ Updated`);
} else {
await db.collection('documents').insertOne(document);
console.log(` ✓ Inserted`);
}
return docInfo.slug;
}
/**
* Main migration function
*/
async function main() {
console.log('=== Value Pluralism Documents Migration ===\n');
let client;
const client = new MongoClient(MONGO_URI);
try {
// Connect to MongoDB
console.log('Connecting to MongoDB...');
client = await mongoose.connect(config.mongodb.uri, config.mongodb.options);
const db = mongoose.connection.db;
console.log('✓ Connected\n');
await client.connect();
const db = client.db(DB_NAME);
const collection = db.collection('documents');
// Migrate each document
const migratedSlugs = [];
for (const docInfo of documentsToMigrate) {
const slug = await migrateDocument(docInfo, db);
migratedSlugs.push(slug);
for (const update of UPDATES) {
const { slug, ...fields } = update;
const result = await collection.updateOne(
{ slug },
{ $set: fields }
);
if (result.matchedCount === 0) {
console.log(` ⚠️ Document not found: ${slug}`);
} else if (result.modifiedCount === 0) {
console.log(` Already up to date: ${slug}`);
} else {
console.log(` ✅ Updated: ${slug}`);
console.log(` - category: ${fields.category}`);
console.log(` - visibility: ${fields.visibility}`);
console.log(` - order: ${fields.order}`);
}
console.log('');
}
// Summary
console.log('\n=== Migration Complete ===\n');
console.log(`✓ Migrated ${migratedSlugs.length} documents:`);
migratedSlugs.forEach(slug => console.log(` - ${slug}`));
console.log('\nDocuments are now available in docs.html');
console.log('PDFs available at:');
migratedSlugs.forEach(slug =>
console.log(` - /downloads/${slug}.pdf`)
);
// Verify
console.log('🔍 Verification:');
const docs = await collection.find(
{ slug: { $in: UPDATES.map(u => u.slug) } },
{ projection: { slug: 1, category: 1, visibility: 1, order: 1, _id: 0 } }
).toArray();
docs.forEach(doc => {
console.log(` ${doc.slug}:`);
console.log(` category: ${doc.category}`);
console.log(` visibility: ${doc.visibility}`);
console.log(` order: ${doc.order}`);
});
console.log('');
console.log('✨ Migration complete!');
} catch (error) {
console.error('\n✗ Migration failed:', error.message);
console.error(error.stack);
console.error('❌ Migration failed:', error);
process.exit(1);
} finally {
if (client) {
await mongoose.connection.close();
console.log('\n✓ Database connection closed');
}
await client.close();
}
}
// Run migration
if (require.main === module) {
main();
}
module.exports = { migrateDocument, generateToC, generateSearchIndex };
migrate();