/ scripts / cleanup-template-readmes.ts
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);