import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; const root = process.cwd(); const read = (file) => fs.readFileSync(path.join(root, file), 'utf8'); const failures = []; const scope = read('MVP_SCOPE.md'); const requirements = read('docs/REQUIREMENTS.md'); const readme = read('README.md'); const roadmap = read('ROADMAP.md'); const traceability = read('docs/MVP_TRACEABILITY.md'); const requireText = (file, text, label) => { if (!read(file).includes(text)) failures.push(`${file}: missing ${label}`); }; requireText('MVP_SCOPE.md', 'The initial MVP has no AI dependency.', 'approved no-AI boundary'); requireText('docs/REQUIREMENTS.md', '`MVP_SCOPE.md` is the approved v0.1.0 release boundary.', 'scope authority'); requireText('docs/REQUIREMENTS.md', '## 12. Resolved MVP Decisions', 'resolved-decision section'); requireText('docs/MVP_TRACEABILITY.md', '## Release-critical requirements', 'requirements matrix'); requireText('docs/MVP_TRACEABILITY.md', '## Explicit post-MVP classifications', 'post-MVP classification'); for (const [file, text, pattern] of [ ['README.md', readme, /AI Assistance.*IBM Bob|watsonx can generate/i], ['docs/REQUIREMENTS.md', requirements, /IBM Bob \/ watsonx should assist|Use IBM Bob\/watsonx to generate/i], ['docs/REQUIREMENTS.md', requirements, /^## 12\. Open Questions$/m], ['docs/REQUIREMENTS.md', requirements, /^\* Timeout setting$/m], ['docs/REQUIREMENTS.md', requirements, /^\* Expected response format$/m], ]) { if (pattern.test(text)) failures.push(`${file}: forbidden stale MVP claim matches ${pattern}`); } if (/^- \[ \]/m.test(scope)) failures.push('MVP_SCOPE.md: an accepted workflow checkbox is unchecked'); if (!roadmap.includes('| [Slice 7](SLICE7.md) | MVP scope decision and requirement governance | Complete | None |')) { failures.push('ROADMAP.md: Slice 7 is not recorded as complete with no remaining gate'); } const linkedFiles = ['README.md', 'MVP_SCOPE.md', 'ROADMAP.md', 'SLICE7.md', 'docs/REQUIREMENTS.md', 'docs/ARCHITECTURE.md', 'docs/MVP_TRACEABILITY.md']; for (const file of linkedFiles) { for (const match of read(file).matchAll(/\[[^\]]+\]\(([^)]+)\)/g)) { const target = match[1].split('#')[0]; if (!target || /^[a-z]+:/i.test(target)) continue; if (!fs.existsSync(path.resolve(root, path.dirname(file), target))) { failures.push(`${file}: unresolved relative link ${target}`); } } } for (let id = 1; id <= 19; id += 1) { if (!traceability.includes(`| R${id} |`)) failures.push(`docs/MVP_TRACEABILITY.md: missing R${id}`); } if (failures.length > 0) { console.error(failures.join('\n')); process.exit(1); } console.log('MVP governance check passed: scope, R1-R19 traceability, classifications, and links are consistent.');