feat(scripts): add value pluralism support scripts

- load-inst-035.js: Load precedent database rule to MongoDB
  - Fixes collection name (governanceRules vs governance_rules)
  - Eliminates "Precedent database rule not found" warning

- migrate-value-pluralism-docs.js: Migrate docs to MongoDB
  - Generate ToC from markdown headings
  - Create search indexes
  - Add full metadata (quadrant, persistence, audience, tags)
  - Proper MongoDB integration for docs.html

- generate-markdown-pdfs.js: Generate PDFs using Puppeteer
  - Workaround for missing pandoc PDF engines
  - Styled PDFs with proper typography
  - 3 value pluralism documents

All scripts support production deployment workflow

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
TheFlow 2025-10-12 16:36:03 +13:00
parent a7ce65ca30
commit 09f7eaa5f7
3 changed files with 395 additions and 0 deletions

View file

@ -0,0 +1,96 @@
/**
* Quick PDF Generator for Markdown Files
* Uses Puppeteer to generate PDFs directly from markdown files
*/
const puppeteer = require('puppeteer');
const marked = require('marked');
const fs = require('fs').promises;
const path = require('path');
async function generatePdfFromMarkdown(markdownPath, outputPath, title) {
const markdown = await fs.readFile(markdownPath, 'utf-8');
const html = marked.parse(markdown);
const fullHtml = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${title}</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-size: 11pt;
line-height: 1.6;
color: #1f2937;
max-width: 900px;
margin: 0 auto;
padding: 2cm;
}
h1 { font-size: 2rem; margin-top: 2rem; margin-bottom: 1rem; page-break-after: avoid; }
h2 { font-size: 1.5rem; margin-top: 1.5rem; margin-bottom: 0.75rem; page-break-after: avoid; }
h3 { font-size: 1.25rem; margin-top: 1.25rem; margin-bottom: 0.5rem; page-break-after: avoid; }
p { margin-bottom: 1rem; line-height: 1.75; }
code { background: #f3f4f6; padding: 0.125rem 0.375rem; border-radius: 0.25rem; font-size: 0.875rem; }
pre { background: #f9fafb; padding: 1rem; border-radius: 0.375rem; overflow-x: auto; page-break-inside: avoid; }
pre code { background: transparent; padding: 0; }
table { width: 100%; border-collapse: collapse; margin-bottom: 1.25rem; page-break-inside: avoid; }
th, td { border: 1px solid #d1d5db; padding: 0.625rem 0.875rem; text-align: left; }
th { background: #f3f4f6; font-weight: 600; }
blockquote { border-left: 4px solid #3b82f6; padding-left: 1rem; margin: 1.25rem 0; background: #f9fafb; padding: 0.875rem; }
</style>
</head>
<body>
<h1>${title}</h1>
${html}
</body>
</html>
`;
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const page = await browser.newPage();
await page.setContent(fullHtml, { waitUntil: 'networkidle0' });
await page.pdf({
path: outputPath,
format: 'A4',
printBackground: true,
margin: { top: '2cm', right: '2cm', bottom: '2cm', left: '2cm' }
});
await browser.close();
console.log(`✓ Generated: ${path.basename(outputPath)}`);
}
async function main() {
const files = [
{
input: 'docs/research/pluralistic-values-research-foundations.md',
output: 'public/downloads/pluralistic-values-research-foundations.pdf',
title: 'Pluralistic Values: Research Foundations'
},
{
input: 'docs/value-pluralism-faq.md',
output: 'public/downloads/value-pluralism-faq.pdf',
title: 'Value Pluralism in Tractatus: FAQ'
},
{
input: 'docs/pluralistic-values-deliberation-plan-v2.md',
output: 'public/downloads/pluralistic-values-deliberation-plan-v2.pdf',
title: 'Pluralistic Values Deliberation Enhancement Plan'
}
];
for (const file of files) {
await generatePdfFromMarkdown(file.input, file.output, file.title);
}
console.log('\n✓ All PDFs generated successfully');
}
main().catch(console.error);

99
scripts/load-inst-035.js Normal file
View file

@ -0,0 +1,99 @@
#!/usr/bin/env node
/**
* Load inst_035 (Precedent Database Design) into MongoDB
* This resolves the startup warning about missing inst_035
*/
const mongoose = require('mongoose');
const config = require('../src/config/app.config');
const instructionData = {
id: "inst_035",
text: "Precedent database stores past deliberations as INFORMATIVE (not binding) precedents. Each entry documents: decision context, moral frameworks in tension, stakeholders consulted, values prioritized/deprioritized, moral remainder, dissenting views, justification, precedent applicability SCOPE (not universal rule), review date. When similar case arises: (1) CrossReferenceValidator identifies relevant precedents, (2) Human reviews for context similarity, (3) Precedent INFORMS new deliberation but doesn't dictate outcome, (4) Document why following or departing from precedent. Precedents are PROVISIONAL - reviewable when context changes, scale shifts, new evidence emerges. Prevent precedent creep into rigid hierarchy.",
timestamp: "2025-10-12T14:35:00Z",
quadrant: "OPERATIONAL",
persistence: "HIGH",
temporal_scope: "PERMANENT",
verification_required: "MANDATORY",
explicitness: 1.0,
source: "user",
session_id: "2025-10-12-value-pluralism-implementation",
parameters: {
precedent_type: "informative_not_binding",
precedent_fields: [
"context",
"frameworks_in_tension",
"stakeholders",
"values_prioritized",
"values_deprioritized",
"moral_remainder",
"dissent",
"justification",
"applicability_scope",
"review_date"
],
precedent_matching: "CrossReferenceValidator identifies similar cases",
human_review_required: "Context similarity assessment",
precedent_role: "Informs, doesn't dictate",
departure_documentation: "Explain why not following precedent",
provisional_nature: "Reviewable when context/scale/evidence changes",
prevent: "Precedent creep into universal rules",
related_component: [
"PluralisticDeliberationOrchestrator",
"CrossReferenceValidator"
]
},
active: true,
notes: "CORE VALUE PLURALISM IMPLEMENTATION 2025-10-12 - Precedent database design prevents rigid hierarchy while enabling learning from past deliberations. Precedents are PROVISIONAL (Gutmann & Thompson) - decisions aren't final, they're revisable. Key distinction: precedent = 'in similar past case we did X' NOT 'therefore you must do X'. Context matters: scale changes (1000 users → 87 million users = re-deliberate), new evidence (theoretical harm now documented = re-deliberate), changed circumstances = review. Git-like versioning tracks how thinking evolved over time."
};
async function loadInstruction() {
try {
console.log('🔌 Connecting to MongoDB...');
await mongoose.connect(config.mongodb.uri, config.mongodb.options);
console.log('✅ Connected to MongoDB');
const db = mongoose.connection.db;
const collection = db.collection('governanceRules'); // Must match model collection name
// Check if inst_035 already exists
const existing = await collection.findOne({ id: 'inst_035' });
if (existing) {
console.log('⚠️ inst_035 already exists in database');
console.log(' Updating with latest version...');
await collection.updateOne(
{ id: 'inst_035' },
{ $set: instructionData }
);
console.log('✅ inst_035 updated successfully');
} else {
console.log('📝 Inserting inst_035...');
await collection.insertOne(instructionData);
console.log('✅ inst_035 inserted successfully');
}
// Verify insertion
const inserted = await collection.findOne({ id: 'inst_035' });
console.log('\n📋 Verification:');
console.log(` ID: ${inserted.id}`);
console.log(` Quadrant: ${inserted.quadrant}`);
console.log(` Persistence: ${inserted.persistence}`);
console.log(` Text: ${inserted.text.substring(0, 80)}...`);
console.log(` Related Components: ${inserted.parameters.related_component.join(', ')}`);
await mongoose.connection.close();
console.log('\n✅ Done! MongoDB connection closed.');
process.exit(0);
} catch (error) {
console.error('❌ Error loading instruction:', error);
await mongoose.connection.close();
process.exit(1);
}
}
loadInstruction();

View file

@ -0,0 +1,200 @@
/**
* Migrate Value Pluralism Documents to MongoDB
* Adds three new value pluralism documents to the documents collection
*/
const mongoose = require('mongoose');
const marked = require('marked');
const fs = require('fs').promises;
const path = require('path');
const config = require('../src/config/app.config');
// Document structure from existing documents
const documentsToMigrate = [
{
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'
},
{
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'
}
];
/**
* Generate table of contents from markdown
*/
function generateToC(markdown) {
const toc = [];
const lines = markdown.split('\n');
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;
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');
// Migrate each document
const migratedSlugs = [];
for (const docInfo of documentsToMigrate) {
const slug = await migrateDocument(docInfo, db);
migratedSlugs.push(slug);
}
// 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`)
);
} catch (error) {
console.error('\n✗ Migration failed:', error.message);
console.error(error.stack);
process.exit(1);
} finally {
if (client) {
await mongoose.connection.close();
console.log('\n✓ Database connection closed');
}
}
}
// Run migration
if (require.main === module) {
main();
}
module.exports = { migrateDocument, generateToC, generateSearchIndex };