import { execFileSync } from 'node:child_process'; import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import { getFeatureDirectory, getRequiredFeatureFiles, validateFeatureId, } from './feature-record.mjs'; const [featureId, slug] = process.argv.slice(2); const repositoryRoot = process.cwd(); if (!featureId || !slug || !validateFeatureId(featureId)) { throw new Error('用法:npm run governance:finish -- <功能编号> <英文短名称>'); } const featureDirectory = getFeatureDirectory(repositoryRoot, featureId, slug); if (!existsSync(featureDirectory)) { throw new Error(`功能档案不存在:${featureDirectory}`); } function run(command, args) { return execFileSync(command, args, { cwd: repositoryRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], }).trim(); } /** * 完成门禁先验证仓库自身的治理测试,再刷新 GitNexus。业务项目接入后, * 根目录 verify 脚本会继续串联前端和后端测试,而不需要改变本门禁入口。 */ run('npm', ['run', 'test:governance']); run('npx', ['gitnexus', 'analyze']); const status = run('npx', ['gitnexus', 'status']); const diffSummary = run('git', ['diff', '--stat']) || '(没有未提交差异)'; const worktreeSummary = run('git', ['status', '--short']) || '(工作区干净)'; const afterReport = path.join(featureDirectory, 'gitnexus', 'after.md'); if (!existsSync(afterReport) || readFileSync(afterReport, 'utf8').trim().length === 0) { writeFileSync( afterReport, `# GitNexus 开发后基线\n\n` + `## GitNexus 状态\n\n\`\`\`text\n${status}\n\`\`\`\n\n` + `## Git 已跟踪变更摘要\n\n\`\`\`text\n${diffSummary}\n\`\`\`\n\n` + `## Git 工作区摘要\n\n\`\`\`text\n${worktreeSummary}\n\`\`\`\n`, 'utf8', ); } const missingFiles = getRequiredFeatureFiles().filter((relativeFile) => { const file = path.join(featureDirectory, relativeFile); return !existsSync(file) || readFileSync(file, 'utf8').trim().length === 0; }); if (missingFiles.length > 0) { throw new Error(`完成门禁未通过,缺少或为空:\n- ${missingFiles.join('\n- ')}`); } process.stdout.write( `基础门禁已通过。提交前仍须人工确认 GitNexus 影响报告、业务测试、视觉 QA 和回退说明。\n`, );