diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..cb3c9b0 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,52 @@ +# GitHub Actions Workflows + +This directory contains automated workflows for the master-plan repository. + +## Sync Roadmap Workflow + +**File:** `sync-roadmap.yml` + +### Purpose + +Automatically synchronizes the roadmap section in `README.md` with the actual state of GitHub issues across the deep-assistant organization. + +### How It Works + +1. **Triggers:** + - Runs daily at 00:00 UTC (scheduled) + - Can be manually triggered from the Actions tab + - Automatically runs when issues are opened, closed, reopened, edited, or deleted + +2. **Process:** + - Scans `README.md` for issue references in the roadmap section + - Fetches current state of each issue from GitHub API + - Updates checkboxes: `[ ]` for open issues, `[x]` for closed issues + - Commits changes if any updates are detected + +3. **Script:** `sync-roadmap.js` + - Node.js script that performs the actual synchronization + - Parses markdown to find issue links + - Uses GitHub API to check issue states + - Preserves existing roadmap structure and formatting + +### Manual Execution + +You can manually trigger this workflow from the GitHub Actions tab: + +1. Go to the repository's Actions tab +2. Select "Sync Master Plan Roadmap" workflow +3. Click "Run workflow" button + +### Configuration + +The workflow requires: +- `contents: write` permission to commit changes +- `issues: read` permission to fetch issue states +- `GITHUB_TOKEN` secret (automatically provided by GitHub Actions) + +### Maintenance + +To update the roadmap structure or add new issues: +1. Manually edit the `README.md` file +2. Add issue references in the format: `[#123](https://github.com/deep-assistant/repo-name/issues/123)` +3. The workflow will automatically sync checkbox states on the next run diff --git a/.github/workflows/sync-roadmap.js b/.github/workflows/sync-roadmap.js new file mode 100644 index 0000000..4e25b48 --- /dev/null +++ b/.github/workflows/sync-roadmap.js @@ -0,0 +1,153 @@ +#!/usr/bin/env node + +/** + * Sync Master Plan Roadmap Script + * + * This script automatically updates the roadmap section in README.md + * by synchronizing checkbox states with actual GitHub issue states. + * + * Features: + * - Updates checkboxes: [ ] for open issues, [x] for closed issues + * - Preserves the existing roadmap structure and formatting + * - Works with issues from multiple repositories + */ + +const fs = require('fs'); +const https = require('https'); + +const GITHUB_TOKEN = process.env.GITHUB_TOKEN; +const ORG_NAME = 'deep-assistant'; + +/** + * Fetch data from GitHub API + */ +function fetchGitHub(path) { + return new Promise((resolve, reject) => { + const options = { + hostname: 'api.github.com', + path: path, + method: 'GET', + headers: { + 'User-Agent': 'Master-Plan-Sync-Bot', + 'Authorization': `token ${GITHUB_TOKEN}`, + 'Accept': 'application/vnd.github.v3+json' + } + }; + + https.get(options, (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => { + if (res.statusCode === 200) { + resolve(JSON.parse(data)); + } else { + reject(new Error(`GitHub API error: ${res.statusCode} - ${data}`)); + } + }); + }).on('error', reject); + }); +} + +/** + * Get issue state (open/closed) from GitHub + */ +async function getIssueState(repo, issueNumber) { + try { + const issue = await fetchGitHub(`/repos/${ORG_NAME}/${repo}/issues/${issueNumber}`); + return issue.state === 'closed' ? 'closed' : 'open'; + } catch (error) { + console.error(`Error fetching issue ${repo}#${issueNumber}:`, error.message); + return 'open'; // Default to open if we can't fetch + } +} + +/** + * Parse issue reference from markdown link + * Returns: { repo: string, number: number } or null + */ +function parseIssueLink(line) { + // Match patterns like: + // - **Link:** [#46](https://github.com/deep-assistant/telegram-bot/issues/46) + // - **Link:** [#10](https://github.com/deep-assistant/master-plan/issues/10) + const linkPattern = /\[#(\d+)\]\(https:\/\/github\.com\/deep-assistant\/([^\/]+)\/issues\/\d+\)/; + const match = line.match(linkPattern); + + if (match) { + return { + repo: match[2], + number: parseInt(match[1], 10) + }; + } + + return null; +} + +/** + * Update checkbox state in a task line + */ +function updateCheckbox(line, isClosed) { + const checkbox = isClosed ? '[x]' : '[ ]'; + // Replace existing checkbox at the start of the line + return line.replace(/^(\s*)-\s*\[([ x])\]/, `$1- ${checkbox}`); +} + +/** + * Main sync function + */ +async function syncRoadmap() { + console.log('🚀 Starting roadmap sync...'); + + // Read README.md + const readmePath = 'README.md'; + let content = fs.readFileSync(readmePath, 'utf8'); + const lines = content.split('\n'); + + let updated = false; + let currentIssue = null; + + // Process each line + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + + // Check if this line contains an issue link + const issueRef = parseIssueLink(line); + if (issueRef) { + currentIssue = issueRef; + console.log(`📋 Found issue reference: ${issueRef.repo}#${issueRef.number}`); + } + + // Check if this is a task line with a checkbox and we have a current issue + if (currentIssue && line.match(/^\s*-\s*\[([ x])\]/)) { + // Get the actual state from GitHub + const state = await getIssueState(currentIssue.repo, currentIssue.number); + const isClosed = state === 'closed'; + + // Update the checkbox if needed + const newLine = updateCheckbox(line, isClosed); + if (newLine !== line) { + console.log(`✅ Updating ${currentIssue.repo}#${currentIssue.number}: ${state}`); + lines[i] = newLine; + updated = true; + } + + // Reset current issue after processing its task line + currentIssue = null; + } + } + + // Write back if updated + if (updated) { + fs.writeFileSync(readmePath, lines.join('\n'), 'utf8'); + console.log('✨ Roadmap successfully updated!'); + } else { + console.log('✓ Roadmap is already up to date!'); + } + + return updated; +} + +// Run the sync +syncRoadmap().catch(error => { + console.error('❌ Error syncing roadmap:', error); + process.exit(1); +}); diff --git a/.github/workflows/sync-roadmap.yml b/.github/workflows/sync-roadmap.yml new file mode 100644 index 0000000..56e3025 --- /dev/null +++ b/.github/workflows/sync-roadmap.yml @@ -0,0 +1,52 @@ +name: Sync Master Plan Roadmap + +on: + schedule: + # Run daily at 00:00 UTC + - cron: '0 0 * * *' + workflow_dispatch: # Allow manual trigger + issues: + types: [opened, closed, reopened, edited, deleted] + +permissions: + contents: write + issues: read + +jobs: + sync-roadmap: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Sync roadmap with issues + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + node .github/workflows/sync-roadmap.js + + - name: Check for changes + id: git-check + run: | + git diff --exit-code README.md || echo "changed=true" >> $GITHUB_OUTPUT + + - name: Commit and push if changed + if: steps.git-check.outputs.changed == 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add README.md + git commit -m "chore: sync roadmap with GitHub issues + + 🤖 Automatically synchronized the roadmap section in README.md with the current state of GitHub issues across the organization. + + 🤖 Generated with GitHub Actions" + git push