From 5a283a765bf062711536165dcfc61c983a8c0a0f Mon Sep 17 00:00:00 2001 From: Tobias Wilken Date: Sat, 15 Nov 2025 19:03:15 +0100 Subject: [PATCH] feat: add Origin field parser and warnings for repository migration Add support for Origin field in REPOSITORIES.md to enable future repository migration automation from 'powered by worlddriven' to 'worlddriven project'. Parser changes: - Add origin field parsing in parse-repositories.js - Update type definitions to include origin?: string - Add comprehensive tests for origin field Drift detection: - Add pendingTransfer category for repos with origin field - Display clear warning when origin field detected - Exit code doesn't treat pending transfers as errors Sync automation: - Warn when repositories with origin field are found - List pending transfer repositories - Explain feature not yet implemented Documentation: - Add Origin field to REPOSITORIES.md properties - Add Repository Migration section explaining planned workflow - Reference GitHub issue #9 for implementation progress Related: #9 --- REPOSITORIES.md | 20 +++++++++++++++++ scripts/detect-drift.js | 32 ++++++++++++++++++++++++++- scripts/parse-repositories.js | 4 +++- scripts/parse-repositories.test.js | 35 ++++++++++++++++++++++++++++++ scripts/sync-repositories.js | 13 +++++++++++ 5 files changed, 102 insertions(+), 2 deletions(-) diff --git a/REPOSITORIES.md b/REPOSITORIES.md index 30b82cc..3f50109 100644 --- a/REPOSITORIES.md +++ b/REPOSITORIES.md @@ -34,6 +34,7 @@ Each repository is defined using markdown headers and properties: - **Repository Name**: Markdown heading level 2 (`##`) - **Description** (required): Brief description of the repository's purpose - **Topics** (optional): Comma-separated list of repository topics for discoverability +- **Origin** (optional, not yet implemented): Source repository for migration (e.g., `owner/repo-name`) ## Example @@ -47,6 +48,25 @@ Each repository is defined using markdown headers and properties: - Topics: documentation, organization-management, governance ``` +## Repository Migration (Coming Soon) + +🚧 **Feature in development** - Repository transfer automation is not yet implemented. + +The `Origin` field will enable migrating repositories from "powered by worlddriven" to "worlddriven project": + +- **Powered by worlddriven**: Repository stays under owner's control, uses worlddriven for PR automation +- **Worlddriven project**: Repository lives in worlddriven org with full democratic governance + +**Planned workflow** (not yet functional): +1. Origin repository owner grants admin permissions to worlddriven org +2. Add repository to REPOSITORIES.md with `Origin: owner/repo-name` +3. Drift detection verifies permissions +4. On merge, repository automatically transfers to worlddriven org +5. Standard democratic configurations applied + +**Current status**: Parser supports Origin field, transfer logic pending implementation. +Track progress in the GitHub issue for repository migration feature. + --- ## Current Repositories diff --git a/scripts/detect-drift.js b/scripts/detect-drift.js index 0adafb1..bb64089 100755 --- a/scripts/detect-drift.js +++ b/scripts/detect-drift.js @@ -29,6 +29,7 @@ function detectDrift(desiredRepos, actualRepos) { extra: [], // In GitHub but not in REPOSITORIES.md descriptionDiff: [], // Description mismatch topicsDiff: [], // Topics mismatch + pendingTransfer: [], // Repositories with origin field (not yet implemented) }; // Create lookup maps @@ -39,6 +40,12 @@ function detectDrift(desiredRepos, actualRepos) { for (const desired of desiredRepos) { const actual = actualMap.get(desired.name); + // Check for origin field (repository transfer - not yet implemented) + if (desired.origin) { + drift.pendingTransfer.push(desired); + continue; // Skip other checks for transfer repos + } + if (!actual) { drift.missing.push(desired); } else { @@ -86,7 +93,8 @@ function formatDriftReport(drift, desiredCount, actualCount) { const hasDrift = drift.missing.length > 0 || drift.extra.length > 0 || drift.descriptionDiff.length > 0 || - drift.topicsDiff.length > 0; + drift.topicsDiff.length > 0 || + drift.pendingTransfer.length > 0; if (!hasDrift) { lines.push('āœ… **No drift detected** - REPOSITORIES.md matches GitHub organization state'); @@ -96,6 +104,21 @@ function formatDriftReport(drift, desiredCount, actualCount) { lines.push('āš ļø **Drift detected** - Differences found between REPOSITORIES.md and GitHub'); lines.push(''); + // Pending transfers (not yet implemented) + if (drift.pendingTransfer.length > 0) { + lines.push(`## 🚧 Repository Transfer Pending (${drift.pendingTransfer.length})`); + lines.push(''); + lines.push('āš ļø **FEATURE NOT YET IMPLEMENTED** - These repositories have an `Origin` field for migration:'); + lines.push(''); + for (const repo of drift.pendingTransfer) { + lines.push(`- **${repo.name}** ← \`${repo.origin}\``); + lines.push(` - Description: ${repo.description}`); + lines.push(` - **Action required**: Repository transfer automation is not yet implemented`); + lines.push(` - See issue for implementation progress and manual transfer instructions`); + } + lines.push(''); + } + // Missing repositories if (drift.missing.length > 0) { lines.push(`## šŸ“ Missing in GitHub (${drift.missing.length})`); @@ -173,11 +196,18 @@ async function main() { console.log(report); // Exit with error code if drift detected (useful for CI) + // Note: pendingTransfer is informational, not an error const hasDrift = drift.missing.length > 0 || drift.extra.length > 0 || drift.descriptionDiff.length > 0 || drift.topicsDiff.length > 0; + // Warn about pending transfers + if (drift.pendingTransfer.length > 0) { + console.error('\nāš ļø Warning: Repository transfer feature is not yet implemented'); + console.error(' PRs with Origin field cannot be merged until implementation is complete'); + } + process.exit(hasDrift ? 1 : 0); } catch (error) { console.error(`āŒ Error: ${error.message}`); diff --git a/scripts/parse-repositories.js b/scripts/parse-repositories.js index cf1f242..e3450d3 100755 --- a/scripts/parse-repositories.js +++ b/scripts/parse-repositories.js @@ -15,7 +15,7 @@ const __dirname = dirname(__filename); /** * Parse markdown content and extract repository definitions * @param {string} content - Markdown content - * @returns {Array<{name: string, description: string, topics?: string[]}>} + * @returns {Array<{name: string, description: string, topics?: string[], origin?: string}>} */ function parseRepositories(content) { const repositories = []; @@ -67,6 +67,8 @@ function parseRepositories(content) { currentRepo.description = value.trim(); } else if (keyLower === 'topics') { currentRepo.topics = value.split(',').map(t => t.trim()).filter(Boolean); + } else if (keyLower === 'origin') { + currentRepo.origin = value.trim(); } } } diff --git a/scripts/parse-repositories.test.js b/scripts/parse-repositories.test.js index c366d60..095e57c 100644 --- a/scripts/parse-repositories.test.js +++ b/scripts/parse-repositories.test.js @@ -185,6 +185,41 @@ Here's an example: assert.strictEqual(result[0].topics, undefined); }); + test('should parse repository with origin field for migration', () => { + const content = ` +## core +- Description: Democratic governance system for GitHub pull requests +- Topics: democracy, open-source, governance +- Origin: TooAngel/worlddriven +`; + const result = parseRepositories(content); + assert.deepStrictEqual(result, [ + { + name: 'core', + description: 'Democratic governance system for GitHub pull requests', + topics: ['democracy', 'open-source', 'governance'], + origin: 'TooAngel/worlddriven' + } + ]); + }); + + test('should parse multiple repositories with and without origin', () => { + const content = ` +## documentation +- Description: Core documentation repository +- Topics: documentation, worlddriven + +## core +- Description: Democratic governance system +- Topics: governance +- Origin: TooAngel/worlddriven +`; + const result = parseRepositories(content); + assert.strictEqual(result.length, 2); + assert.strictEqual(result[0].origin, undefined); + assert.strictEqual(result[1].origin, 'TooAngel/worlddriven'); + }); + test('should match actual REPOSITORIES.md structure', () => { const content = `# Worlddriven Organization Repositories diff --git a/scripts/sync-repositories.js b/scripts/sync-repositories.js index c61e32f..c8b5a87 100755 --- a/scripts/sync-repositories.js +++ b/scripts/sync-repositories.js @@ -663,6 +663,19 @@ async function main() { console.error('šŸ” Detecting drift...'); const drift = detectDrift(desiredRepos, actualRepos); + // Check for pending transfers (not yet implemented) + if (drift.pendingTransfer && drift.pendingTransfer.length > 0) { + console.error(''); + console.error('🚧 WARNING: Repository transfer feature not yet implemented'); + console.error(` Found ${drift.pendingTransfer.length} repository(ies) with Origin field:`); + for (const repo of drift.pendingTransfer) { + console.error(` - ${repo.name} ← ${repo.origin}`); + } + console.error(' These repositories will be SKIPPED until transfer feature is implemented'); + console.error(' See GitHub issue for implementation progress'); + console.error(''); + } + // Generate sync plan console.error('šŸ“‹ Generating sync plan...'); const plan = generateSyncPlan(drift, desiredRepos);