import assert from 'node:assert/strict'; import test from 'node:test'; import { buildImpactPlanTemplate, getUnpassedReviewFiles, getFeatureDirectory, getInvalidFeatureEvidence, getRequiredFeatureFiles, validateFeatureId, } from './feature-record.mjs'; const VALID_FEATURE_REPORTS = new Map([ [ 'development-summary.md', `# 开发总结 ## 原始开发需求 建立可自动执行的质量门禁。 ## 需求与范围变化 范围未变化,仍只处理治理与请求脚手架。 ## 实际完成需求 完成独立总结和证据校验。 ## 修复缺陷 本次没有修复既有业务缺陷,只补足治理能力。 ## 未完成与延期项 AI Provider 延期到独立功能,不属于本次范围。 ## 关键提交 记录中文提交 SHA 与摘要。 ## 验证证据 记录测试、构建和 Review 结果。 `, ], [ 'design.md', `# 设计 ## 8. 安全策略与评估 说明信任边界和残余风险。 ## 9. 性能目标与评估 说明预算和验证强度。 `, ], [ 'test-plan.md', `# 测试 ## 安全验证 验证脱敏错误。 ## 性能验证 比较构建体积。 `, ], [ 'gitnexus/impact-plan.md', `# 影响 ## 直接与间接影响 列出消费者和执行流。 ## 跨模块同步修改 逐项判断契约、缓存和进程。 `, ], [ 'review/code-review.md', `# Review ## 安全与残余风险 错误信息经过脱敏,残余风险已记录。 ## 性能与资源影响 包体增量经过测量并可接受。 ## GitNexus 与同步修改 实际影响符合开发前计划。 最终结论:passed `, ], [ 'review/standards-review.md', `# 规范 Review ## 安全、性能与架构检查 逐项核对并符合规范。 ## GitNexus 与开发总结检查 前后影响和完成事实证据齐全。 最终结论:passed `, ], ]); test('接受带业务域和三位序号的功能编号', () => { assert.equal(validateFeatureId('FEAT-MO-001'), true); assert.equal(validateFeatureId('GOV-001'), true); }); test('拒绝无法追溯或格式含糊的功能编号', () => { assert.equal(validateFeatureId('market-overview'), false); assert.equal(validateFeatureId('FEAT-MO-1'), false); assert.equal(validateFeatureId('../FEAT-MO-001'), false); }); test('将功能编号和短名称解析到固定的功能档案目录', () => { assert.equal( getFeatureDirectory('/repo', 'FEAT-MO-001', 'market-overview'), '/repo/docs/features/FEAT-MO-001-market-overview', ); }); test('返回开发完成前必须存在的完整追溯文件清单', () => { assert.deepEqual(getRequiredFeatureFiles(), [ 'manifest.yaml', 'prd.md', 'requirements.md', 'design.md', 'implementation-plan.md', 'test-plan.md', 'development-summary.md', 'gitnexus/before.md', 'gitnexus/impact-plan.md', 'gitnexus/detected-changes.md', 'gitnexus/after.md', 'gitnexus/comparison.md', 'qa/test-results.md', 'qa/visual-qa.md', 'review/code-review.md', 'review/standards-review.md', 'release-notes.md', 'rollback.md', ]); }); test('只有两份 Review 都明确通过时才允许发布', () => { const reports = new Map([ ['review/code-review.md', '# 代码 Review\n\n最终结论:passed\n'], ['review/standards-review.md', '# 规范 Review\n\n状态:pending\n'], ]); assert.deepEqual(getUnpassedReviewFiles(reports), [ 'review/standards-review.md', ]); reports.set( 'review/standards-review.md', '# 规范 Review\n\n最终结论:passed\n', ); assert.deepEqual(getUnpassedReviewFiles(reports), []); }); test('Review 只有在最后一个非空行明确 passed 时才通过', () => { const reports = new Map([ [ 'review/code-review.md', '# 代码 Review\n\n最终结论:passed\n\n最终结论:changes_required\n', ], [ 'review/standards-review.md', '# 规范 Review\n\n最终结论:passed\n\n补充说明不能放在最终结论之后。\n', ], ]); assert.deepEqual(getUnpassedReviewFiles(reports), [ 'review/code-review.md', 'review/standards-review.md', ]); }); test('完整质量证据和带序号的二级标题可以通过门禁', () => { assert.deepEqual(getInvalidFeatureEvidence(VALID_FEATURE_REPORTS), []); }); test('一次列出缺失、空白和无解释占位的全部证据章节', () => { const reports = new Map(VALID_FEATURE_REPORTS); reports.set( 'development-summary.md', `# 开发总结 ## 原始开发需求 建立质量门禁。 ## 需求与范围变化 无 ## 实际完成需求 完成结构校验。 ## 修复缺陷 N/A ## 未完成与延期项 ## 关键提交 记录提交。 ## 验证证据 记录测试。 `, ); reports.set( 'test-plan.md', `# 测试 ## 安全验证 验证脱敏错误。 `, ); assert.deepEqual(getInvalidFeatureEvidence(reports), [ 'development-summary.md:需求与范围变化', 'development-summary.md:修复缺陷', 'development-summary.md:未完成与延期项', 'test-plan.md:性能验证', ]); }); test('带具体理由和边界的不适用说明不是空壳证据', () => { const reports = new Map(VALID_FEATURE_REPORTS); reports.set( 'test-plan.md', `# 测试 ## 安全验证 验证脱敏错误。 ## 性能验证 不适用:本功能不进入生产请求路径,边界仅为开发门禁。 `, ); assert.deepEqual(getInvalidFeatureEvidence(reports), []); }); test('开发前影响计划骨架要求记录风险、消费者和跨模块同步修改', () => { const template = buildImpactPlanTemplate('FEAT-MO-001'); assert.match(template, /功能编号:FEAT-MO-001/); assert.match(template, /^## 直接与间接影响$/m); assert.match(template, /query、context、upstream impact、消费者和执行流/); assert.match(template, /^## 跨模块同步修改$/m); assert.match(template, /HTTP\/OpenAPI、Web API、消息契约、数据库、Redis/); assert.match(template, /API、Scheduler、Worker、配置部署、测试和文档/); });