cleanup-template-readmes.ts
1 #!/usr/bin/env npx tsx 2 /** 3 * Migration Script: Clean up boilerplate README noise from DreamNodes 4 * 5 * This script identifies DreamNode READMEs that contain the old template 6 * boilerplate and replaces them with a minimal placeholder, while preserving 7 * any meaningful content that may have been appended below the boilerplate. 8 * 9 * Usage: 10 * npx tsx scripts/cleanup-template-readmes.ts --dry-run # Preview changes 11 * npx tsx scripts/cleanup-template-readmes.ts # Apply changes 12 */ 13 14 import * as fs from 'fs'; 15 import * as path from 'path'; 16 import { discoverAllDreamNodes } from '../src/services/standalone-adapter.js'; 17 18 // The boilerplate signature - if a README contains this, it has the old template 19 const BOILERPLATE_SIGNATURE = 'This is a DreamNode - a git repository representing a thought-form or person in the **InterBrain** knowledge management system.'; 20 21 // The old boilerplate content (lines 2-51 of the template, after the title) 22 const BOILERPLATE_CONTENT = ` 23 This is a DreamNode - a git repository representing a thought-form or person in the **InterBrain** knowledge management system. 24 25 ## Universal Dream Description (UDD) 26 27 The \`udd.json\` file contains the essential metadata for this DreamNode: 28 29 \`\`\`json 30 { 31 "uuid": "Unique identifier (constant)", 32 "title": "Display name/title", 33 "type": "dream or dreamer", 34 "dreamTalk": "Path to symbolic representation", 35 "liminalWebRelationships": ["Connected DreamNode UUIDs"], 36 "submodules": ["Child DreamNode UUIDs"], 37 "supermodules": ["Parent DreamNode UUIDs"] 38 } 39 \`\`\` 40 41 ## Relationships 42 43 ### Liminal Web (Horizontal) 44 - **Dreams** connect to **Dreamers** who hold them 45 - **Dreamers** connect to **Dreams** they carry 46 - Forms the social fabric of shared knowledge 47 48 ### Holonic Structure (Vertical) 49 - **Submodules**: Ideas that are part of this idea 50 - **Supermodules**: Larger ideas this idea participates in 51 - Enables fractal knowledge organization 52 53 ## Coherence Beacons 54 55 This DreamNode includes git hooks that maintain relationship coherence: 56 57 - **pre-commit**: Integrates external references as submodules 58 - **post-commit**: Updates bidirectional relationship tracking 59 60 Changes propagate through the peer-to-peer network via **Radicle**. 61 62 ## License 63 64 This DreamNode is shared under the **GNU Affero General Public License v3.0** - a strong copyleft license ensuring this knowledge remains free and open for all. 65 66 ## InterBrain 67 68 Part of the **InterBrain** project: transcending personal knowledge management toward collective knowledge gardening. 69 70 - **Repository**: https://github.com/ProjectLiminality/InterBrain 71 - **Vision**: Building DreamOS - a decentralized operating system for collective sensemaking`; 72 73 interface CleanupResult { 74 path: string; 75 title: string; 76 action: 'cleaned' | 'preserved' | 'skipped'; 77 hadExtraContent: boolean; 78 extraContent?: string; 79 } 80 81 /** 82 * Extract the title from a DreamNode README 83 */ 84 function extractTitle(content: string): string | null { 85 // Match "# DreamNode: Title" or "# Title" 86 const dreamNodeMatch = content.match(/^#\s+DreamNode:\s*(.+)/m); 87 if (dreamNodeMatch) { 88 return dreamNodeMatch[1].trim(); 89 } 90 91 const simpleMatch = content.match(/^#\s+(.+)/m); 92 if (simpleMatch) { 93 return simpleMatch[1].trim(); 94 } 95 96 return null; 97 } 98 99 /** 100 * Check if README contains the boilerplate template 101 */ 102 function hasBoilerplate(content: string): boolean { 103 return content.includes(BOILERPLATE_SIGNATURE); 104 } 105 106 /** 107 * Extract any content that appears after the boilerplate 108 */ 109 function extractExtraContent(content: string): string | null { 110 // The boilerplate ends with the DreamOS sensemaking line 111 const endMarker = '- **Vision**: Building DreamOS - a decentralized operating system for collective sensemaking'; 112 const endIndex = content.indexOf(endMarker); 113 114 if (endIndex === -1) { 115 return null; 116 } 117 118 const afterBoilerplate = content.slice(endIndex + endMarker.length).trim(); 119 120 // If there's meaningful content after the boilerplate, return it 121 if (afterBoilerplate.length > 0) { 122 return afterBoilerplate; 123 } 124 125 return null; 126 } 127 128 /** 129 * Generate the new minimal README content 130 */ 131 function generateMinimalReadme(title: string, extraContent: string | null): string { 132 let content = `# ${title}\n\n*Describe this idea here.*\n`; 133 134 if (extraContent) { 135 content += `\n${extraContent}\n`; 136 } 137 138 return content; 139 } 140 141 async function main() { 142 const args = process.argv.slice(2); 143 const dryRun = args.includes('--dry-run'); 144 const verbose = args.includes('--verbose') || args.includes('-v'); 145 146 console.log('🧹 DreamNode README Cleanup Migration'); 147 console.log('====================================='); 148 console.log(`Mode: ${dryRun ? 'DRY RUN (no changes will be made)' : 'APPLY CHANGES'}`); 149 console.log(''); 150 151 // Discover all DreamNodes 152 console.log('Discovering DreamNodes...'); 153 const dreamNodes = await discoverAllDreamNodes(); 154 console.log(`Found ${dreamNodes.length} DreamNodes\n`); 155 156 const results: CleanupResult[] = []; 157 let cleaned = 0; 158 let preserved = 0; 159 let skipped = 0; 160 let withExtraContent = 0; 161 162 for (const node of dreamNodes) { 163 const readmePath = path.join(node.path, 'README.md'); 164 165 // Skip if no README exists 166 if (!fs.existsSync(readmePath)) { 167 if (verbose) { 168 console.log(`⏭️ ${node.title}: No README.md found`); 169 } 170 skipped++; 171 results.push({ 172 path: node.path, 173 title: node.title, 174 action: 'skipped', 175 hadExtraContent: false 176 }); 177 continue; 178 } 179 180 const content = fs.readFileSync(readmePath, 'utf-8'); 181 182 // Skip if no boilerplate 183 if (!hasBoilerplate(content)) { 184 if (verbose) { 185 console.log(`✨ ${node.title}: Already clean (no boilerplate)`); 186 } 187 preserved++; 188 results.push({ 189 path: node.path, 190 title: node.title, 191 action: 'preserved', 192 hadExtraContent: false 193 }); 194 continue; 195 } 196 197 // Has boilerplate - extract title and any extra content 198 const title = extractTitle(content) || node.title; 199 const extraContent = extractExtraContent(content); 200 201 if (extraContent) { 202 withExtraContent++; 203 console.log(`📝 ${node.title}: Has boilerplate + extra content`); 204 if (verbose) { 205 console.log(` Extra content preview: ${extraContent.slice(0, 100)}...`); 206 } 207 } else { 208 console.log(`🧹 ${node.title}: Pure boilerplate, will clean`); 209 } 210 211 // Generate new content 212 const newContent = generateMinimalReadme(title, extraContent); 213 214 // Apply changes if not dry run 215 if (!dryRun) { 216 fs.writeFileSync(readmePath, newContent, 'utf-8'); 217 } 218 219 cleaned++; 220 results.push({ 221 path: node.path, 222 title: node.title, 223 action: 'cleaned', 224 hadExtraContent: !!extraContent, 225 extraContent: extraContent || undefined 226 }); 227 } 228 229 // Summary 230 console.log('\n====================================='); 231 console.log('Summary:'); 232 console.log(` Total DreamNodes: ${dreamNodes.length}`); 233 console.log(` Cleaned: ${cleaned}`); 234 console.log(` Preserved (already clean): ${preserved}`); 235 console.log(` Skipped (no README): ${skipped}`); 236 console.log(` Had extra content preserved: ${withExtraContent}`); 237 238 if (dryRun) { 239 console.log('\n⚠️ DRY RUN - No changes were made'); 240 console.log('Run without --dry-run to apply changes'); 241 } else { 242 console.log('\n✅ Changes applied successfully'); 243 } 244 245 // Write detailed report 246 const reportPath = path.join(process.cwd(), 'cleanup-report.json'); 247 fs.writeFileSync(reportPath, JSON.stringify(results, null, 2)); 248 console.log(`\nDetailed report written to: ${reportPath}`); 249 } 250 251 main().catch(console.error);