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:
parent
05806b10f9
commit
ceb359a276
3 changed files with 395 additions and 0 deletions
96
scripts/generate-markdown-pdfs.js
Normal file
96
scripts/generate-markdown-pdfs.js
Normal 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
99
scripts/load-inst-035.js
Normal 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();
|
||||
200
scripts/migrate-value-pluralism-docs.js
Normal file
200
scripts/migrate-value-pluralism-docs.js
Normal 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 };
|
||||
Loading…
Add table
Reference in a new issue