| 245 | } |
| 246 | |
| 247 | function buildStackApprovalCapsule(projectRoot, stack) { |
| 248 | if (stack.status !== 'approval-needed') return null; |
| 249 | return { |
| 250 | generatedAt: stack.generatedAt, |
| 251 | projectRoot, |
| 252 | boundary: 'stack-approval', |
| 253 | risk: 'medium-high', |
| 254 | request: `Approve landing stack in order: ${stack.nextAction.command}`, |
| 255 | action: { |
| 256 | label: stack.nextAction.label, |
| 257 | command: stack.nextAction.command, |
| 258 | why: stack.nextAction.why, |
| 259 | confidence: 'high', |
| 260 | runbook: 'docs/CAMPAIGNS.md', |
| 261 | }, |
| 262 | context: { |
| 263 | pending: {}, |
| 264 | gitStatus: {}, |
| 265 | problemSummary: {}, |
| 266 | campaigns: [], |
| 267 | stack: stack.reports.map((report) => ({ |
| 268 | branch: report.branch, |
| 269 | pr: report.pr, |
| 270 | head: report.head, |
| 271 | currentHead: report.currentHead || null, |
| 272 | readiness: report.status, |
| 273 | report: report.path, |
| 274 | })), |
| 275 | }, |
| 276 | verification: [ |
| 277 | 'Run `npm run stack:plan` and confirm the landing order matches the intended stack.', |
| 278 | 'Confirm each listed PR readiness report is current and has no blocked gates.', |
| 279 | 'After approval, mark or merge PRs in the listed order only.', |
| 280 | ], |
| 281 | postApprovalRunbook: stack.postApprovalRunbook, |
| 282 | }; |
| 283 | } |
| 284 | |
| 285 | function assessStack(projectRoot, options = {}) { |
| 286 | const root = path.resolve(projectRoot || process.cwd()); |