* Returns true if the given branch is owned by a persistent campaign * (campaign frontmatter has worktree_status: active). * Scans .planning/campaigns/ for a matching branch field.
(branch)
| 83 | * Scans .planning/campaigns/ for a matching branch field. |
| 84 | */ |
| 85 | function isPersistentWorktree(branch) { |
| 86 | try { |
| 87 | const campaignsDir = path.join(PROJECT_ROOT, '.planning', 'campaigns'); |
| 88 | if (!fs.existsSync(campaignsDir)) return false; |
| 89 | const files = fs.readdirSync(campaignsDir).filter(f => f.endsWith('.md') && !fs.statSync(path.join(campaignsDir, f)).isDirectory()); |
| 90 | for (const file of files) { |
| 91 | const filePath = path.join(campaignsDir, file); |
| 92 | const content = fs.readFileSync(filePath, 'utf8'); |
| 93 | // Extract YAML frontmatter (between first --- and second ---) |
| 94 | const fmMatch = content.match(/^---\n([\s\S]*?)\n---/); |
| 95 | if (!fmMatch) continue; |
| 96 | const fm = fmMatch[1]; |
| 97 | // Check branch field matches |
| 98 | const branchMatch = fm.match(/^branch:\s*(.+)$/m); |
| 99 | if (!branchMatch) continue; |
| 100 | const fileBranch = branchMatch[1].trim().replace(/^["']|["']$/g, ''); |
| 101 | if (fileBranch !== branch) continue; |
| 102 | // Check worktree_status is active |
| 103 | const statusMatch = fm.match(/^worktree_status:\s*(.+)$/m); |
| 104 | if (!statusMatch) continue; |
| 105 | const status = statusMatch[1].trim().replace(/^["']|["']$/g, ''); |
| 106 | if (status === 'active') return true; |
| 107 | } |
| 108 | } catch { /* non-critical — if we can't read, assume ephemeral */ } |
| 109 | return false; |
| 110 | } |
| 111 | |
| 112 | function queueMergeCheck(branch, worktree) { |
| 113 | try { |