807 lines
40 KiB
PowerShell
807 lines
40 KiB
PowerShell
$ErrorActionPreference = 'Stop'
|
||
|
||
$root = Split-Path -Parent $PSScriptRoot
|
||
$currentDocuments = @(
|
||
'docs/项目当前总览.md',
|
||
'docs/家谱项目全量治理设计.md',
|
||
'docs/家谱项目全量治理实施计划.md',
|
||
'docs/视觉资产与构建基线.md',
|
||
'docs/接口与页面映射总表.md'
|
||
)
|
||
|
||
$combined = ''
|
||
$documentContents = @{}
|
||
foreach ($relativePath in $currentDocuments) {
|
||
$absolutePath = Join-Path $root $relativePath
|
||
if (-not (Test-Path -LiteralPath $absolutePath -PathType Leaf)) {
|
||
throw "缺少当前权威文档:$relativePath"
|
||
}
|
||
$content = Get-Content -Raw -Encoding UTF8 -LiteralPath $absolutePath
|
||
$documentContents[$relativePath] = $content
|
||
$combined += "`n" + $content
|
||
}
|
||
|
||
foreach ($token in @('TO' + 'DO', 'TB' + 'D', 'FIX' + 'ME', 'PLACE' + 'HOLDER')) {
|
||
if ($combined -match "(?i)\b$([regex]::Escape($token))\b") {
|
||
throw "当前权威文档仍含未完成占位标记:$token"
|
||
}
|
||
}
|
||
|
||
# 当前进度只由项目总览表述;其余权威文档不得把已完成的阶段 0 或已收口的书面设计写成进行中。
|
||
$designDocument = $documentContents['docs/家谱项目全量治理设计.md']
|
||
$planDocument = $documentContents['docs/家谱项目全量治理实施计划.md']
|
||
$mappingDocument = $documentContents['docs/接口与页面映射总表.md']
|
||
$overviewDocument = $documentContents['docs/项目当前总览.md']
|
||
if ($designDocument -match '阶段 0 正在执行') {
|
||
throw '治理设计仍把已完成的阶段 0 写成进行中'
|
||
}
|
||
if ($mappingDocument -match '当前阶段:阶段 0——迁移现行页面与业务事实') {
|
||
throw '接口与页面映射总表仍把阶段 0 迁移写成当前任务'
|
||
}
|
||
if ($mappingDocument -notmatch '家谱工作区、G03 原子创建、M06 帮助、个人资料读写、通知读写、M10 服务端退出、M04 密码凭证与 M05 手机号换绑三人审查及 OpenAPI 红灯已完成' -or
|
||
$mappingDocument -notmatch 'T01、TAC、工作区、G03、帮助、profile、通知、logout、password 与 phone-change 后端接口门禁当前红灯') {
|
||
throw '接口与页面映射总表没有登记导航、TAC 客户端、后端红灯与 MuMu 边界'
|
||
}
|
||
if ($designDocument -notmatch '导航任务 1—10 的静态实施和零债务门禁已经完成' -or
|
||
$designDocument -notmatch '当前 SFC 分段词法扫描的迁移债务已经清零' -or
|
||
$designDocument -notmatch 'MIGRATION-DEBT=0') {
|
||
throw '治理设计没有同步导航静态零债务结果'
|
||
}
|
||
if ($mappingDocument -notmatch '实际 `64/64` 个 Vue 文件') {
|
||
throw '接口与页面映射总表没有同步退役通用页面后的响应式覆盖实数'
|
||
}
|
||
if ($overviewDocument -notmatch 'G01/G05、G03、M06、M01/M02/M03 个人资料读写、N01/N02/M01/G01 通知域、M10 退出域、M04 密码凭证域与 M05 换绑域已完成三人接口审查和失败门禁' -or
|
||
$overviewDocument -notmatch '继续审查下一个不依赖现有红灯的业务域') {
|
||
throw '项目总览没有写明领域上下文与 M07 完成后的当前精确实施入口'
|
||
}
|
||
if ($overviewDocument -notmatch 'API-T01-001') {
|
||
throw '项目总览没有登记 T01 后端接口门禁'
|
||
}
|
||
if ($overviewDocument -match '当前 MuMu[^\r\n]*在线') {
|
||
throw '项目总览不得把易漂移的 MuMu 在线状态写成长期当前事实'
|
||
}
|
||
if ($overviewDocument -notmatch '阶段 0 验收时的 MuMu 证据') {
|
||
throw '项目总览缺少带时间边界的 MuMu 验收证据'
|
||
}
|
||
$visualDocument = $documentContents['docs/视觉资产与构建基线.md']
|
||
if ($visualDocument -notmatch '`static/tac/` 当前共 5 个文件' -or
|
||
$visualDocument -notmatch '4 个后端提供的供应商文件' -or
|
||
$visualDocument -notmatch '`static/tac/js/jiapu-tac-adapter.js`' -or
|
||
$visualDocument -notmatch 'tests/auth-tac-integration-contract.ps1') {
|
||
throw '视觉资产基线没有登记 TAC 供应商资产、项目适配器及唯一校验入口'
|
||
}
|
||
if ($mappingDocument -notmatch 'A01、A04、A05 已接入同一个 `TacVerification`' -or
|
||
$mappingDocument -notmatch '不能把本地滑动成功冒充服务端验证' -or
|
||
$mappingDocument -notmatch '密码登录入口保持不可用') {
|
||
throw '接口页面映射没有准确登记 A01/A04/A05 的 TAC 客户端状态与密码登录硬关闭'
|
||
}
|
||
if ($mappingDocument -notmatch '任务 5 实施前' -or $mappingDocument -notmatch '这只是历史视觉基线' -or $mappingDocument -notmatch '任务 4—6 的当前代码仍须.*MuMu 流程矩阵') {
|
||
throw '接口页面映射把历史 MuMu 证据越界成了当前代码验收'
|
||
}
|
||
|
||
# 导航设计已经三人终审,文档合同必须保护其所有权、根语义、公开方法和阶段边界,不能只检查“有一份计划”。
|
||
foreach ($owner in @('utils/navigation-routes.js', 'utils/navigation.js')) {
|
||
if ($designDocument -notmatch [regex]::Escape($owner) -or $planDocument -notmatch [regex]::Escape($owner)) {
|
||
throw "导航设计或实施计划缺少唯一所有者:$owner"
|
||
}
|
||
if (-not (Test-Path -LiteralPath (Join-Path $root $owner) -PathType Leaf)) {
|
||
throw "导航唯一所有者尚未真实落地:$owner"
|
||
}
|
||
}
|
||
foreach ($semanticMethod in @('openPage', 'goBack', 'returnTo', 'finishPage', 'goRoot')) {
|
||
if ($designDocument -notmatch "(?m)^$([regex]::Escape($semanticMethod))\(") {
|
||
throw "导航设计缺少公开语义方法:$semanticMethod"
|
||
}
|
||
}
|
||
if ($planDocument -notmatch 'ROOT_ROUTE_KEYS = Object\.freeze\(\["A01", "G01", "F01", "M01"\]\)') {
|
||
throw '实施计划没有把四个根语义精确锁定为 A01/G01/F01/M01'
|
||
}
|
||
if ($planDocument -match 'ROOT_ROUTE_KEYS[^\r\n]*N01') {
|
||
throw 'N01 不是根页,不得进入 ROOT_ROUTE_KEYS'
|
||
}
|
||
if ($designDocument -notmatch 'G01、F01、M01.*自定义 `AppTabbar`' -or $designDocument -notmatch 'A01 是认证根页') {
|
||
throw '治理设计没有区分三个业务根页与 A01 认证根页'
|
||
}
|
||
if ($designDocument -notmatch '字段键精确为 `operation、entityId(可选)、refresh`' -or $designDocument -notmatch '不允许加入 `treeVersion、genealogyId、focusId、payload`') {
|
||
throw '一次性导航结果没有保持 operation/entityId/refresh 的唯一形状'
|
||
}
|
||
if ($designDocument -notmatch 'T03 原生页面实例只保留一个' -or $designDocument -notmatch '轨迹初始化为恰好 `\[initialPersonId\]`') {
|
||
throw '治理设计缺少 T03 单实例与初始成员轨迹合同'
|
||
}
|
||
if ($planDocument -notmatch '导航阶段允许只迁移 T01 的现有导航调用' -or $planDocument -notmatch '导航门禁通过前,不得实施 T01 新图合同') {
|
||
throw '导航与 T01 的阶段隔离边界不准确'
|
||
}
|
||
if ($planDocument -notmatch 'T03_CONTEXT_CONFLICT') {
|
||
throw '实施计划缺少 T03 跨家谱上下文冲突规则'
|
||
}
|
||
if ($planDocument -notmatch 'T03_STACK_CONFLICT') {
|
||
throw '实施计划缺少 T03 历史重复实例冲突规则'
|
||
}
|
||
if ($planDocument -notmatch 'returnTo\(routeKey, targetParams = \{\}\)' -or $planDocument -notmatch 'finishPage\(routeKey, targetParams, result\)') {
|
||
throw '实施计划没有锁定普通返回与唯一完成结果入口'
|
||
}
|
||
if ($planDocument -match 'returnTo\([^\)\r\n]*,\s*result\s*\)') {
|
||
throw '实施计划重新引入了 returnTo 携带流程结果的旧入口'
|
||
}
|
||
if ($planDocument -notmatch 'own enumerable data properties' -or $planDocument -notmatch 'WeakMap') {
|
||
throw '实施计划缺少严格数据属性快照或页面实例弱身份令牌合同'
|
||
}
|
||
$navigationFoundation = [regex]::Match(
|
||
$planDocument,
|
||
'(?s)### 任务 1:建立路由语义注册表.*?(?=### 任务 3:迁移共享页头、底栏并退役通用母版)'
|
||
).Value
|
||
if ([string]::IsNullOrWhiteSpace($navigationFoundation) -or $navigationFoundation -match '- \[ \]') {
|
||
throw '实施计划没有把已经验证的导航任务 1/2 精确标为完成'
|
||
}
|
||
if ($planDocument -notmatch 'M04 密码凭证任务 33、M05 手机号换绑任务 34 和 G03 原子创建任务 35 已完成三人审查及 OpenAPI 红灯' -or
|
||
$planDocument -notmatch '等待后端期间转向 G/F/R 下一域') {
|
||
throw '实施计划没有登记领域上下文与 M07 完成后的独立实施入口'
|
||
}
|
||
$taskThree = [regex]::Match(
|
||
$planDocument,
|
||
'(?s)### 任务 3:迁移共享页头、底栏并退役通用母版.*?(?=### 任务 4:迁移认证导航)'
|
||
).Value
|
||
if ([string]::IsNullOrWhiteSpace($taskThree) -or $taskThree -match '- \[ \] \*\*步骤 [123]:') {
|
||
throw '实施计划没有把任务 3 的静态实施步骤精确标为完成'
|
||
}
|
||
if ($taskThree -notmatch 'MuMu 复核待执行' -or $taskThree -notmatch '39') {
|
||
throw '实施计划没有明确任务 3 的 MuMu 边界或剩余迁移债务'
|
||
}
|
||
$taskFour = [regex]::Match(
|
||
$planDocument,
|
||
'(?s)### 任务 4:迁移认证导航.*?(?=### 任务 5:迁移 G 系列导航)'
|
||
).Value
|
||
if ([string]::IsNullOrWhiteSpace($taskFour) -or $taskFour -match '- \[ \] \*\*步骤 [123]:') {
|
||
throw '实施计划没有把任务 4 的静态实施步骤精确标为完成'
|
||
}
|
||
if ($taskFour -notmatch '- \[ \] \*\*步骤 4:在 MuMu 复核认证流程' -or $taskFour -notmatch '35') {
|
||
throw '实施计划没有明确任务 4 的 MuMu 边界或剩余迁移债务'
|
||
}
|
||
if ($taskFour -notmatch 'tests/navigation-flow-contract.ps1' -or $taskFour -match 'navigation-auth-flow-contract') {
|
||
throw '认证导航没有消费跨批次唯一流程合同'
|
||
}
|
||
$taskFive = [regex]::Match(
|
||
$planDocument,
|
||
'(?s)### 任务 5:迁移 G 系列导航.*?(?=### 任务 6:迁移 T 系列并实现 T03 单实例轨迹)'
|
||
).Value
|
||
if ([string]::IsNullOrWhiteSpace($taskFive) -or $taskFive -match '- \[ \] \*\*步骤 [1234]:') {
|
||
throw '实施计划没有把任务 5 的静态实施步骤精确标为完成'
|
||
}
|
||
if ($taskFive -notmatch '- \[ \] \*\*步骤 5:完成 G 系列 MuMu 矩阵' -or $taskFive -notmatch '29 个文件') {
|
||
throw '实施计划没有明确任务 5 的 MuMu 边界或剩余迁移债务'
|
||
}
|
||
if ($taskFive -match 'finishPage\("G05"' -or $taskFive -match 'finishPage\("G09"') {
|
||
throw '实施计划重新引入了 G 系列本地预览的伪后端完成结果'
|
||
}
|
||
if ($taskFive -notmatch '未知值失败关闭' -or $taskFive -notmatch '每批 50 行渲染') {
|
||
throw '实施计划没有登记 G11/G12 的最终静态合同'
|
||
}
|
||
$taskSix = [regex]::Match(
|
||
$planDocument,
|
||
'(?s)### 任务 6:迁移 T 系列并实现 T03 单实例轨迹.*?(?=### 任务 7:迁移 F 系列导航)'
|
||
).Value
|
||
if ([string]::IsNullOrWhiteSpace($taskSix) -or $taskSix -match '- \[ \] \*\*步骤 [123]:') {
|
||
throw '实施计划没有把任务 6 的静态实施步骤精确标为完成'
|
||
}
|
||
if ($taskSix -notmatch '- \[ \] \*\*步骤 4:完成 T 系列 MuMu 矩阵' -or $taskSix -notmatch '22 个页面/表单组件文件') {
|
||
throw '实施计划没有明确任务 6 的 MuMu 边界或剩余迁移债务'
|
||
}
|
||
if ($taskSix -match 'finishPage\(' -or $taskSix -match 'relative-created|member-updated|relationship-updated') {
|
||
throw '实施计划在 T 系列本地预览阶段伪造了服务端写成功结果'
|
||
}
|
||
foreach ($taskSixContract in @(
|
||
'本地预览',
|
||
'尚未提交服务器',
|
||
'`goBack()`',
|
||
'宿主页路由身份',
|
||
'页内活动成员',
|
||
'listTreeMemberFixtures(genealogyId)',
|
||
'findTreeMemberFixture(genealogyId, personId)',
|
||
'深拷贝快照',
|
||
'跨谱隔离',
|
||
'失败关闭',
|
||
'tests/tree-member-fixture-runtime-smoke.js',
|
||
'PowerShell `122/122`',
|
||
'Node 语法 `35/35`',
|
||
'纯 Node `7/7`'
|
||
)) {
|
||
if ($taskSix -notmatch [regex]::Escape($taskSixContract)) {
|
||
throw "实施计划缺少任务 6 最终合同:$taskSixContract"
|
||
}
|
||
}
|
||
$taskSeven = [regex]::Match(
|
||
$planDocument,
|
||
'(?s)### 任务 7:迁移 F 系列导航.*?(?=### 任务 8:迁移 R 系列导航)'
|
||
).Value
|
||
if ($planDocument -match 'assert\.deepEqual\(consumeNavigationResult\("F04"\),\s*\{\s*operation:\s*"article-created"') {
|
||
throw '实施计划仍保留已经废止的 F04 伪写结果示例'
|
||
}
|
||
if ([string]::IsNullOrWhiteSpace($taskSeven) -or $taskSeven -match '- \[ \] \*\*步骤 [12345]:') {
|
||
throw '实施计划没有把任务 7 的静态实施步骤精确标为完成'
|
||
}
|
||
if ($taskSeven -notmatch '- \[ \] \*\*步骤 6:完成 F 系列 MuMu 矩阵' -or $taskSeven -notmatch '13 个页面/表单组件文件') {
|
||
throw '实施计划没有明确任务 7 的 MuMu 边界或剩余迁移债务'
|
||
}
|
||
foreach ($taskSevenContract in @(
|
||
'F01—F10',
|
||
'跨谱',
|
||
'WRITE_UNAVAILABLE',
|
||
'feed-created/article-created/article-updated/media-uploaded',
|
||
'PowerShell `122/122`',
|
||
'Node 语法 `36/36`',
|
||
'纯 Node `8/8`',
|
||
'MIGRATION-DEBT=13'
|
||
)) {
|
||
if ($taskSeven -notmatch [regex]::Escape($taskSevenContract)) {
|
||
throw "实施计划缺少任务 7 最终合同:$taskSevenContract"
|
||
}
|
||
}
|
||
$taskEight = [regex]::Match(
|
||
$planDocument,
|
||
'(?s)### 任务 8:迁移 R 系列导航.*?(?=### 任务 9:)'
|
||
).Value
|
||
foreach ($taskEightContract in @(
|
||
'R03/R04 归属 `relative-records`',
|
||
'`giftId` 全量退役为 `relativeId`',
|
||
'`ritualId` 全量退役为 `ceremonyId`',
|
||
'R09 必须保持硬关闭',
|
||
'resultOperations',
|
||
'本地预览,尚未提交服务器',
|
||
'只读复合身份 owner',
|
||
'MuMu'
|
||
)) {
|
||
if ($taskEight -notmatch [regex]::Escape($taskEightContract)) {
|
||
throw "实施计划缺少任务 8 三方审查结论:$taskEightContract"
|
||
}
|
||
}
|
||
if ([string]::IsNullOrWhiteSpace($taskEight) -or $taskEight -match '- \[ \] \*\*步骤 [123456]:') {
|
||
throw '实施计划没有把任务 8 的静态实施步骤精确标为完成'
|
||
}
|
||
if ($taskEight -notmatch '- \[ \] \*\*步骤 7:完成 R 系列 MuMu 矩阵' -or $taskEight -notmatch 'MIGRATION-DEBT=6') {
|
||
throw '实施计划没有明确任务 8 的 MuMu 边界或剩余迁移债务'
|
||
}
|
||
foreach ($taskEightVerification in @('PowerShell `122/122`', 'Node 语法 `37/37`', '纯 Node `9/9`')) {
|
||
if ($taskEight -notmatch [regex]::Escape($taskEightVerification)) {
|
||
throw "实施计划缺少任务 8 最新验证:$taskEightVerification"
|
||
}
|
||
}
|
||
$taskNine = [regex]::Match(
|
||
$planDocument,
|
||
'(?s)### 任务 9:迁移 N/M 系列与安全通知目标.*?(?=### 任务 10:关闭导航阶段)'
|
||
).Value
|
||
if ([string]::IsNullOrWhiteSpace($taskNine) -or $taskNine -match '- \[ \] \*\*步骤 [1234]:') {
|
||
throw '实施计划没有把任务 9 静态实施步骤精确标为完成'
|
||
}
|
||
if ($taskNine -notmatch '- \[ \] \*\*步骤 5:完成 N/M 系列 MuMu 矩阵' -or $taskNine -notmatch 'MIGRATION-DEBT=1') {
|
||
throw '实施计划没有明确任务 9 的 MuMu 边界或最后一项导航债务'
|
||
}
|
||
$taskTen = [regex]::Match(
|
||
$planDocument,
|
||
'(?s)### 任务 10:关闭导航阶段.*?(?=---\s*## 第二阶段:T01 大规模世系树)'
|
||
).Value
|
||
if ([string]::IsNullOrWhiteSpace($taskTen) -or $taskTen -match '- \[ \] \*\*步骤 [12]:') {
|
||
throw '实施计划没有把任务 10 的静态门禁步骤精确标为完成'
|
||
}
|
||
if ($taskTen -notmatch '- \[ \] \*\*步骤 3:执行 NAV-MUMU-01 至 NAV-MUMU-08' -or
|
||
$taskTen -notmatch 'MIGRATION-DEBT=0' -or
|
||
$taskTen -notmatch 'TreeMemberForm\.vue') {
|
||
throw '实施计划没有记录导航零债务、孤儿删除或 MuMu 待验边界'
|
||
}
|
||
$taskEleven = [regex]::Match(
|
||
$planDocument,
|
||
'(?s)### 任务 11:向后端提交规范图合同并建立接口门禁.*?(?=### 任务 12:实现新图的规范化与严格校验)'
|
||
).Value
|
||
if ([string]::IsNullOrWhiteSpace($taskEleven) -or
|
||
$taskEleven -match '- \[ \] \*\*步骤 [12]:' -or
|
||
$taskEleven -notmatch '- \[ \] \*\*步骤 [34]:') {
|
||
throw '实施计划没有精确登记 T01 门禁已落地、后端双导出仍待提供'
|
||
}
|
||
foreach ($taskElevenFact in @('LINEAGE-OPENAPI-CONTRACT BLOCKED', 'JSON/YAML 各缺四条操作和三个固定根模型', '平铺 query parameters 无法由 OpenAPI 静态证明')) {
|
||
if ($taskEleven -notmatch [regex]::Escape($taskElevenFact)) {
|
||
throw "任务 11 缺少门禁分层事实:$taskElevenFact"
|
||
}
|
||
}
|
||
if ($mappingDocument -notmatch '### 2\.12 R 系列线上接口与静态迁移边界') {
|
||
throw '接口与页面映射总表缺少 R 系列三方接口审查账本'
|
||
}
|
||
$taskEighteen = [regex]::Match(
|
||
$planDocument,
|
||
'(?s)### 任务 18:接入新接口、mock 和跨页变更版本.*?(?=### 任务 19:完成 T01 MuMu、性能与三人终审)'
|
||
).Value
|
||
foreach ($taskEighteenContract in @(
|
||
'pages/tree/t05-edit-member.vue',
|
||
'appApi.updateLineagePerson(genealogyId, memberId, payload, treeVersion)',
|
||
'T04/T05/T06 完成回流',
|
||
'genealogyId/personId/memberId',
|
||
'personId=A',
|
||
'memberId=B',
|
||
'hostPersonId',
|
||
'editedMemberId',
|
||
'禁止 `memberId || personId` 双读',
|
||
'parentParamMap` 仍只负责无历史时构造父页',
|
||
'同步收紧 `tests/navigation-flow-contract.ps1`'
|
||
)) {
|
||
if ($taskEighteen -notmatch [regex]::Escape($taskEighteenContract)) {
|
||
throw "任务 18 缺少 T05 双身份或原子合同迁移约束:$taskEighteenContract"
|
||
}
|
||
}
|
||
foreach ($retiredOwner in @('components/ModulePage.vue', 'data/page-catalog.js')) {
|
||
if ($combined -match [regex]::Escape($retiredOwner)) {
|
||
throw "当前权威文档不得把退役通用页面重新登记为 owner:$retiredOwner"
|
||
}
|
||
}
|
||
if ($combined -notmatch 'tests/retired-module-page-contract.ps1') {
|
||
throw '当前权威文档缺少通用母版退役防回归合同'
|
||
}
|
||
|
||
$navigationSource = Get-Content -Raw -Encoding UTF8 -LiteralPath (Join-Path $root 'utils/navigation.js')
|
||
$actualNavigationExports = @(
|
||
[regex]::Matches($navigationSource, '(?m)^export const ([A-Za-z][A-Za-z0-9]*)') |
|
||
ForEach-Object { $_.Groups[1].Value } |
|
||
Sort-Object
|
||
)
|
||
$expectedNavigationExports = @(
|
||
'buildRouteUrl',
|
||
'consumeNavigationResult',
|
||
'finishPage',
|
||
'goBack',
|
||
'goRoot',
|
||
'handleBackPress',
|
||
'openNoticeTarget',
|
||
'openPage',
|
||
'resolveBackAction',
|
||
'returnTo',
|
||
'runBackGuard'
|
||
) | Sort-Object
|
||
if (($actualNavigationExports -join ',') -ne ($expectedNavigationExports -join ',')) {
|
||
throw "导航网关公开导出面漂移:$($actualNavigationExports -join ', ')"
|
||
}
|
||
if ($navigationSource -match '(?m)^export\s+(const|function)\s+replaceStep\b' -or $designDocument -match '(?m)^replaceStep\(') {
|
||
throw '当前没有替换边,不得恢复公开 replaceStep 入口'
|
||
}
|
||
if ($mappingDocument -match '将在阶段 1.*确定' -or $mappingDocument -match '阶段 1 依赖') {
|
||
throw '接口映射总表仍把已收口导航或未来业务错误绑定为阶段 1 待定项'
|
||
}
|
||
if ($overviewDocument -notmatch '当前物理库存为 `140` 个 PowerShell 合同、`47` 个 Node 文件' -or
|
||
$overviewDocument -notmatch 'PowerShell `126/140` 通过' -or
|
||
$overviewDocument -notmatch 'Node 语法 `47/47` 通过' -or
|
||
$overviewDocument -notmatch '纯 Node 冒烟 `19/19` 通过' -or
|
||
$overviewDocument -notmatch 'Vue 脚本模块语法 `64/64` 通过') {
|
||
throw '项目总览没有登记当前测试库存与最新全量验证证据'
|
||
}
|
||
foreach ($completedDomainFact in @(
|
||
'appApi.submitFeedback',
|
||
'/genealogy/app/feedback',
|
||
'WRITE_UNAVAILABLE',
|
||
'feedbackContent',
|
||
'撤权',
|
||
'禁止静默',
|
||
'uncertain'
|
||
)) {
|
||
if ($overviewDocument -notmatch [regex]::Escape($completedDomainFact) -and
|
||
$mappingDocument -notmatch [regex]::Escape($completedDomainFact) -and
|
||
$planDocument -notmatch [regex]::Escape($completedDomainFact)) {
|
||
throw "当前文档没有登记领域上下文或 M07 完成事实:$completedDomainFact"
|
||
}
|
||
}
|
||
foreach ($workspaceGateFact in @(
|
||
'API-GENEALOGY-WORKSPACE-001',
|
||
'genealogy-workspace-openapi-contract.ps1',
|
||
'RListAppGenealogyVo',
|
||
'RAppGenealogyVo',
|
||
'canView',
|
||
'GENEALOGY-WORKSPACE-OPENAPI-CONTRACT BLOCKED'
|
||
)) {
|
||
if ($overviewDocument -notmatch [regex]::Escape($workspaceGateFact) -and
|
||
$mappingDocument -notmatch [regex]::Escape($workspaceGateFact) -and
|
||
$planDocument -notmatch [regex]::Escape($workspaceGateFact)) {
|
||
throw "当前文档没有登记家谱工作区门禁事实:$workspaceGateFact"
|
||
}
|
||
}
|
||
foreach ($helpGateFact in @(
|
||
'API-M06-001',
|
||
'help-center-openapi-contract.ps1',
|
||
'RListHelpArticleVo',
|
||
'HelpArticleVo',
|
||
'list-only',
|
||
'HELP-CENTER-OPENAPI-CONTRACT BLOCKED'
|
||
)) {
|
||
if ($overviewDocument -notmatch [regex]::Escape($helpGateFact) -and
|
||
$mappingDocument -notmatch [regex]::Escape($helpGateFact) -and
|
||
$planDocument -notmatch [regex]::Escape($helpGateFact)) {
|
||
throw "当前文档没有登记 M06 帮助门禁事实:$helpGateFact"
|
||
}
|
||
}
|
||
foreach ($profileGateFact in @(
|
||
'API-PROFILE-READ-001',
|
||
'profile-openapi-contract.ps1',
|
||
'RAppProfileVo',
|
||
'AppProfileVo',
|
||
'maskedPhone',
|
||
'PROFILE-OPENAPI-CONTRACT BLOCKED'
|
||
)) {
|
||
if ($overviewDocument -notmatch [regex]::Escape($profileGateFact) -and
|
||
$mappingDocument -notmatch [regex]::Escape($profileGateFact) -and
|
||
$planDocument -notmatch [regex]::Escape($profileGateFact)) {
|
||
throw "当前文档没有登记个人资料读取门禁事实:$profileGateFact"
|
||
}
|
||
}
|
||
foreach ($profileUpdateGateFact in @(
|
||
'API-PROFILE-UPDATE-001',
|
||
'profile-update-openapi-contract.ps1',
|
||
'AppProfileMergeUpdateBody',
|
||
'profileVersion',
|
||
'If-Match',
|
||
'PROFILE_VERSION_CHANGED',
|
||
'PROFILE-UPDATE-OPENAPI-CONTRACT BLOCKED'
|
||
)) {
|
||
if ($overviewDocument -notmatch [regex]::Escape($profileUpdateGateFact) -and
|
||
$mappingDocument -notmatch [regex]::Escape($profileUpdateGateFact) -and
|
||
$planDocument -notmatch [regex]::Escape($profileUpdateGateFact)) {
|
||
throw "当前文档没有登记个人资料写入门禁事实:$profileUpdateGateFact"
|
||
}
|
||
}
|
||
foreach ($notificationGateFact in @(
|
||
'API-NOTIFICATION-READ-001',
|
||
'API-NOTIFICATION-STATE-001',
|
||
'notification-read-openapi-contract.ps1',
|
||
'notification-read-state-openapi-contract.ps1',
|
||
'RListNotificationVo',
|
||
'RNotificationUnreadCount',
|
||
'generation+ordinal',
|
||
'NOTIFICATION-READ-OPENAPI-CONTRACT BLOCKED',
|
||
'NOTIFICATION-READ-STATE-OPENAPI-CONTRACT BLOCKED'
|
||
)) {
|
||
if ($overviewDocument -notmatch [regex]::Escape($notificationGateFact) -and
|
||
$mappingDocument -notmatch [regex]::Escape($notificationGateFact) -and
|
||
$planDocument -notmatch [regex]::Escape($notificationGateFact)) {
|
||
throw "当前文档没有登记通知读取或已读写入门禁事实:$notificationGateFact"
|
||
}
|
||
}
|
||
foreach ($logoutGateFact in @(
|
||
'API-LOGOUT-001',
|
||
'logout-openapi-contract.ps1',
|
||
'RLogoutRejected',
|
||
'TOKEN_CLIENT_MISMATCH',
|
||
'LOGOUT-OPENAPI-CONTRACT BLOCKED',
|
||
'logoutCoordinator'
|
||
)) {
|
||
if ($overviewDocument -notmatch [regex]::Escape($logoutGateFact) -and
|
||
$mappingDocument -notmatch [regex]::Escape($logoutGateFact) -and
|
||
$planDocument -notmatch [regex]::Escape($logoutGateFact)) {
|
||
throw "当前文档没有登记 M10 服务端退出门禁事实:$logoutGateFact"
|
||
}
|
||
}
|
||
foreach ($passwordChangeGateFact in @(
|
||
'API-PASSWORD-001',
|
||
'API-PASSWORD-005',
|
||
'password-change-openapi-contract.ps1',
|
||
'CurrentPasswordSecret',
|
||
'NewPasswordSecret',
|
||
'RPasswordChangeRejected',
|
||
'CREDENTIAL_VERSION_CONFLICT',
|
||
'credentialChangeInFlight',
|
||
'PASSWORD-CHANGE-OPENAPI-CONTRACT BLOCKED'
|
||
)) {
|
||
if ($overviewDocument -notmatch [regex]::Escape($passwordChangeGateFact) -and
|
||
$mappingDocument -notmatch [regex]::Escape($passwordChangeGateFact) -and
|
||
$planDocument -notmatch [regex]::Escape($passwordChangeGateFact)) {
|
||
throw "当前文档没有登记 M04 密码凭证门禁事实:$passwordChangeGateFact"
|
||
}
|
||
}
|
||
foreach ($phoneChangeGateFact in @(
|
||
'API-PHONE-001',
|
||
'API-PHONE-005',
|
||
'phone-change-openapi-contract.ps1',
|
||
'PhoneChangeSmsCodeBody',
|
||
'NewBoundPhone',
|
||
'SmsCodeSecret',
|
||
'RPhoneChangeRejected',
|
||
'STEP_UP_UNAVAILABLE',
|
||
'PHONE-CHANGE-OPENAPI-CONTRACT BLOCKED'
|
||
)) {
|
||
if ($overviewDocument -notmatch [regex]::Escape($phoneChangeGateFact) -and
|
||
$mappingDocument -notmatch [regex]::Escape($phoneChangeGateFact) -and
|
||
$planDocument -notmatch [regex]::Escape($phoneChangeGateFact)) {
|
||
throw "当前文档没有登记 M05 手机号换绑门禁事实:$phoneChangeGateFact"
|
||
}
|
||
}
|
||
foreach ($g03BootstrapGateFact in @(
|
||
'API-G03-001',
|
||
'API-G03-005',
|
||
'g03-bootstrap-openapi-contract.ps1',
|
||
'AppGenealogyBootstrapBody',
|
||
'GenealogyBootstrapOperationKey',
|
||
'GenealogyAccessPreset',
|
||
'GenealogyRegionCode',
|
||
'FAILED_NO_COMMIT',
|
||
'x-bootstrap-root-editable-fields',
|
||
'x-state-transitions',
|
||
'x-domain-effects=NONE',
|
||
'任意精度',
|
||
'G03-BOOTSTRAP-OPENAPI-CONTRACT BLOCKED',
|
||
'g03-bootstrap-client-release-gate.ps1',
|
||
'G03-BOOTSTRAP-CLIENT-RELEASE BLOCKED',
|
||
'openapi-yaml-json-parity-runtime-smoke.js',
|
||
'/genealogy/app/region/search',
|
||
'GET 必须纯读',
|
||
'fatal/quarantined',
|
||
'家谱工作区读取门禁',
|
||
'If-Match、版本/CAS',
|
||
'receipt→`/mine` cache→context→G05'
|
||
)) {
|
||
if ($overviewDocument -notmatch [regex]::Escape($g03BootstrapGateFact) -and
|
||
$designDocument -notmatch [regex]::Escape($g03BootstrapGateFact) -and
|
||
$mappingDocument -notmatch [regex]::Escape($g03BootstrapGateFact) -and
|
||
$planDocument -notmatch [regex]::Escape($g03BootstrapGateFact)) {
|
||
throw "当前文档没有登记 G03 原子创建门禁事实:$g03BootstrapGateFact"
|
||
}
|
||
}
|
||
foreach ($liveOpenApiFact in @('https://backend-api.ddxcjp.cn/', '3.1.0', '722', '858', '507', '/captcha/challenge', 'validToken')) {
|
||
if ($overviewDocument -notmatch [regex]::Escape($liveOpenApiFact) -or $mappingDocument -notmatch [regex]::Escape($liveOpenApiFact)) {
|
||
throw "项目总览或接口映射缺少新线上 OpenAPI 事实:$liveOpenApiFact"
|
||
}
|
||
}
|
||
if ($mappingDocument -notmatch '密码登录体尚无票据字段' -or $mappingDocument -notmatch '没有任何 `/genealogy/app/v2/') {
|
||
throw '接口映射没有锁定 TAC 密码登录或 T01 v2 的线上硬缺口'
|
||
}
|
||
foreach ($lineageGateFact in @('LINEAGE-OPENAPI-CONTRACT BLOCKED', 'LineageGraphWindow/LineageOverview/LineageLocator')) {
|
||
if ($overviewDocument -notmatch [regex]::Escape($lineageGateFact) -and $mappingDocument -notmatch [regex]::Escape($lineageGateFact) -and $planDocument -notmatch [regex]::Escape($lineageGateFact)) {
|
||
throw "当前文档没有登记 T01 门禁事实:$lineageGateFact"
|
||
}
|
||
}
|
||
|
||
foreach ($authGateFact in @(
|
||
'AUTH-TAC-OPENAPI-CONTRACT BLOCKED',
|
||
'ANDROID-AUTH-ACCESSIBILITY-RELEASE BLOCKED',
|
||
'API-AUTH-TAC-001',
|
||
'API-AUTH-TAC-002',
|
||
'API-AUTH-TAC-003',
|
||
'API-AUTH-TAC-004',
|
||
'runtimeConfig.mode',
|
||
'mock',
|
||
'15 秒',
|
||
'APP_SMS_LOGIN',
|
||
'APP_REGISTER',
|
||
'APP_FORGOT_PASSWORD'
|
||
)) {
|
||
if ($overviewDocument -notmatch [regex]::Escape($authGateFact) -and $mappingDocument -notmatch [regex]::Escape($authGateFact) -and $planDocument -notmatch [regex]::Escape($authGateFact)) {
|
||
throw "当前文档没有登记认证门禁事实:$authGateFact"
|
||
}
|
||
}
|
||
if ($combined -notmatch '同一验证中心' -or
|
||
$combined -notmatch '不得.*无障碍.*绕过' -or
|
||
$combined -notmatch '非交互风控' -or
|
||
$combined -notmatch '已安全绑定设备' -or
|
||
$combined -notmatch '人工.*兜底' -or
|
||
$combined -notmatch 'POC') {
|
||
throw '当前文档没有锁定 TAC 可访问替代路径的统一票据、安全边界和待验证状态'
|
||
}
|
||
|
||
# A04 线上响应已经迁移,注册成功仍必须建立会话并进入 G01,不能保留旧令牌字段或“注册后再登录”终点。
|
||
if ($mappingDocument -notmatch '成功建立登录态并进入 G01' -or $mappingDocument -notmatch 'RAppLoginVo' -or $mappingDocument -notmatch 'AppLoginVo' -or $mappingDocument -notmatch 'access_token') {
|
||
throw 'A04 文档没有同时锁定线上登录响应与建立登录态后进入 G01'
|
||
}
|
||
if ($combined -match 'token/accessToken/tokenValue') { throw '当前权威文档仍把旧登录令牌字段写成目标合同' }
|
||
if ($designDocument -notmatch '正式终点不是“注册后再登录”' -or $planDocument -notmatch '不能先回 A01 再让用户重复登录') {
|
||
throw '设计或计划没有明确排除注册后再次登录的并行终点'
|
||
}
|
||
|
||
# T01 后端合同、固定 MuMu 配置和人工验收规模已经收口;旧字段只能出现在“必须拒绝”的文字中,不能再出现在目标 JSON 形状。
|
||
foreach ($canonicalField in @('schemaVersion', 'treeVersion', 'state', 'entryPersonIds', 'genealogyRootPersonIds', 'redactedGenealogyRootCount', 'rootVisibility', 'pathCompleteness', 'ancestorPathSegments', 'returnedNodeCount', 'partnerRelationship', 'relationshipKind', 'parentRole', 'LINEAGE_QUERY_INVALID', 'LINEAGE_FOCUS_NOT_AVAILABLE', 'RELATIONSHIP_PATCH_EMPTY', 'projectLineageGraph', 'sceneVersion')) {
|
||
if ($designDocument -notmatch [regex]::Escape($canonicalField) -and $planDocument -notmatch [regex]::Escape($canonicalField)) {
|
||
throw "T01 设计与计划缺少规范字段或阶段:$canonicalField"
|
||
}
|
||
}
|
||
if ($planDocument -match '"partnerIds"\s*:' -or $planDocument -match '"rootPersonIds"\s*:' -or $planDocument -match '"rootReason"\s*:' -or $planDocument -match '"schema"\s*:' -or $planDocument -match '"tree"\s*:') {
|
||
throw 'T01 目标 JSON 示例仍在定义已经废弃的旧字段'
|
||
}
|
||
foreach ($emptyContract in @('"state": "EMPTY"', '"focusPersonId": null', '"entryPersonIds": []', '"generationRange": null', '合法的 `state=EMPTY` 空谱', '`state=POPULATED`')) {
|
||
if (-not $planDocument.Contains($emptyContract)) {
|
||
throw "T01 计划缺少 EMPTY/POPULATED 判别合同:$emptyContract"
|
||
}
|
||
}
|
||
if ($planDocument -notmatch 'POPULATED 分支要求 nodes 非空、`focusPersonId` 引用其中一个 VISIBLE 节点') {
|
||
throw 'T01 POPULATED 仍允许把 REDACTED 节点作为焦点'
|
||
}
|
||
if ($designDocument -match '无主根时显示合同异常' -or $designDocument -notmatch '只有 `state=POPULATED` 缺少有效焦点或窗口入口时才显示合同异常') {
|
||
throw 'T01 仍把合法 EMPTY 误判为无主根合同异常'
|
||
}
|
||
foreach ($entryInvariant in @('没有 primary 入边的节点集合', '每个入口在当前窗口恰有零条 primary 入边', '每个非入口恰有一条 primary 入边', '`entryPersonIds` 引用不存在人物', '列表内人物 `entryReason=null`', '列表外人物 `entryReason!=null`', '入口仍有 primary 入边')) {
|
||
if (-not $planDocument.Contains($entryInvariant) -and -not $designDocument.Contains($entryInvariant)) {
|
||
throw "T01 窗口入口与 primary 森林不变量缺失:$entryInvariant"
|
||
}
|
||
}
|
||
if ($planDocument -notmatch 'VisibleLineagePerson.*精确包含' -or $planDocument -notmatch 'RedactedLineagePerson.*只允许' -or $planDocument -notmatch '不得用于 FOCUS、locator、搜索或写接口') {
|
||
throw 'T01 人物模型没有按 visibility 严格区分可见字段与匿名字段'
|
||
}
|
||
if ($planDocument -notmatch 'REDACTED 节点只显示通用隐私说明' -or $planDocument -notmatch 'focusPersonId.*targetPersonId.*稳定 VISIBLE 人物 ID' -or $planDocument -notmatch 'focusable=false、targetPersonId=null') {
|
||
throw 'T01 仍可能把 REDACTED opaque ID 用作焦点或聚合跳转目标'
|
||
}
|
||
if ($planDocument -notmatch 'redacted:\{treeVersion\}:\{token\}' -or $planDocument -notmatch '不得以 `redacted:` 开头的稳定人物 ID') {
|
||
throw 'T01 没有提供可执行的稳定人物 ID 与匿名 ID 边界'
|
||
}
|
||
if ($planDocument -notmatch 'protectedPathPersonIds/protectedPathFamilyUnitIds' -or $planDocument -notmatch '任意时刻每个家庭的真实子女上限始终为 5') {
|
||
throw 'T01 投影没有同时保护焦点祖先路径和固定五人渲染上限'
|
||
}
|
||
if ($planDocument -notmatch 'relationshipKind.*discriminator.*oneOf' -or $planDocument -notmatch 'PARTNER 分支只允许 `relationType/status`') {
|
||
throw 'T01 关系修改没有形成按不可变 relationshipKind 区分的唯一请求合同'
|
||
}
|
||
if ($planDocument -notmatch '至少提交一个可修改字段' -or $planDocument -notmatch '未提交的可修改字段保持原值' -or $planDocument -notmatch '422 RELATIONSHIP_PATCH_EMPTY') {
|
||
throw 'T01 关系 PATCH 没有拒绝空更新或没有定义省略字段语义'
|
||
}
|
||
foreach ($enumContract in @('EMPTY/POPULATED', 'MALE/FEMALE/UNKNOWN', 'VISIBLE/REDACTED', 'GENEALOGY_ROOT/WINDOW_CUT/DISCONNECTED_COMPONENT', 'ANCHOR/PARTNER', 'MARRIAGE/PARTNERSHIP/UNKNOWN', 'ACTIVE/ENDED/UNKNOWN')) {
|
||
if ($planDocument -notmatch [regex]::Escape($enumContract)) {
|
||
throw "T01 OpenAPI 计划缺少完整枚举:$enumContract"
|
||
}
|
||
}
|
||
if ($planDocument -notmatch '规范化最坏复杂度明确为 `O\(N log N \+ E\)`' -or $planDocument -notmatch '投影只接受已经规范化且校验通过的图' -or $planDocument -notmatch '投影阶段保持 `O\(N\+E\)`') {
|
||
throw 'T01 规范化排序与线性投影的复杂度边界不清晰'
|
||
}
|
||
if (-not $planDocument.Contains('Scene 根对象精确为 `{ sceneVersion, treeVersion, focusPersonId, bounds, items }`') -or $planDocument -notmatch '相同 `sceneVersion` 对应不同序列化 payload.*失败' -or $planDocument -notmatch 'scene\.sceneVersion') {
|
||
throw 'T01 Scene 根模型、版本所有权或版本碰撞规则不完整'
|
||
}
|
||
if ($planDocument -notmatch '瞬时 `selectedId` 不属于 Scene payload' -or $planDocument -notmatch '只改变 `selectedId` 时 Scene payload 与 `scene\.sceneVersion` 不变') {
|
||
throw 'T01 瞬时选中态仍可能制造同 sceneVersion 的不同 Scene payload'
|
||
}
|
||
foreach ($overviewInvariant in @('overview` 只接受必填查询参数 `treeVersion`', 'genealogyPersonCount=0、genealogyRootPersonIds=[]、redactedGenealogyRootCount=0、generationRange=null、buckets=[]', 'genealogyRootPersonIds.length + redactedGenealogyRootCount >= 1', '三类计数总和等于 `genealogyPersonCount`', '隐私根只计入 `redactedGenealogyRootCount`', 'getLineageOverview(genealogyId, treeVersion)')) {
|
||
if (-not $planDocument.Contains($overviewInvariant) -and -not $designDocument.Contains($overviewInvariant)) {
|
||
throw "T01 全谱概览的版本、空谱、计数或隐私根不变量缺失:$overviewInvariant"
|
||
}
|
||
}
|
||
foreach ($locatorInvariant in @('根可见性与路径完整性独立校验', 'pathCompleteness=REDACTED_GAPS', 'ancestorPathSegments', '可见根+REDACTED_GAPS', '隐私根+REDACTED_GAPS', '可见根路径不从 `rootPersonId` 开始', 'REDACTED 段含人物 ID', 'validateLineageLocator')) {
|
||
if (-not $planDocument.Contains($locatorInvariant) -and -not $designDocument.Contains($locatorInvariant)) {
|
||
throw "T01 locator 的隐私根或路径完整性分支缺失:$locatorInvariant"
|
||
}
|
||
}
|
||
foreach ($runtimeSemantic in @('tests/lineage-overview-locator-runtime-smoke.js', 'validateLineageOverview', 'bucket 三类计数与 `genealogyPersonCount` 不等', '隐私根 POPULATED', '响应 `treeVersion` 与请求不一致')) {
|
||
if (-not $planDocument.Contains($runtimeSemantic)) {
|
||
throw "T01 overview 跨字段语义缺少运行时负例门禁:$runtimeSemantic"
|
||
}
|
||
}
|
||
if ($designDocument -match '焦点、版本和展开状态' -or $planDocument -match '共享焦点、版本、展开状态') {
|
||
throw 'T01 文档仍残留已删除的页内聚合展开状态'
|
||
}
|
||
if ($planDocument -notmatch '/genealogy/app/v2/genealogies/' -or $planDocument -match '若旧 `/lineage/tree`') {
|
||
throw 'T01 后端路径没有固定为明确 v2 合同'
|
||
}
|
||
if ($planDocument -notmatch 'emulator-5554 / 720×1280 / 320dpi' -or $designDocument -notmatch '10、50、100、300 代') {
|
||
throw 'T01 文档没有锁定当前 MuMu 配置和 10/50/100/300 代人工定位矩阵'
|
||
}
|
||
|
||
$retiredEntrypoints = @(
|
||
'design-pipeline/manifests/a01.json',
|
||
'design-pipeline/manifests/a01-scroll-skins-v3.json',
|
||
'design-pipeline/scripts/build-a01.mjs',
|
||
'tests/a01-code-pipeline-contract.ps1',
|
||
'docs/design/assets/a01-vnext/source/A01-layered-source-v1.psd',
|
||
'build:a01',
|
||
'validate:auth-runtime-assets',
|
||
'design-pipeline/manifests/g01-background-candidates.json',
|
||
'design-pipeline/manifests/module-page-backgrounds.json',
|
||
'design-pipeline/scripts/build-module-page-backgrounds.mjs',
|
||
'design-pipeline/scripts/build_module_page_backgrounds.py',
|
||
'design-pipeline/scripts/build-g01-empty-frame.mjs',
|
||
'design-pipeline/tests/test_build_g01_backgrounds.py',
|
||
'build:g01-background-candidates',
|
||
'build:module-page-backgrounds',
|
||
'build:g01-empty-frame'
|
||
)
|
||
foreach ($entrypoint in $retiredEntrypoints) {
|
||
if ($combined.Contains($entrypoint)) {
|
||
throw "当前权威文档仍引用已退役入口:$entrypoint"
|
||
}
|
||
}
|
||
|
||
foreach ($owner in @(
|
||
'design-pipeline/manifests/runtime-assets.json',
|
||
'design-pipeline/manifests/application-runtime-assets.json',
|
||
'design-pipeline/manifests/shared-scroll-skins-v3.json',
|
||
'design-pipeline/manifests/page-backgrounds-v3.json',
|
||
'design-pipeline/manifests/g01-state-frame-v3.json',
|
||
'tests/runtime-assets-contract.ps1',
|
||
'tests/retired-asset-removal-contract.ps1',
|
||
'tests/mumu-visual-acceptance-boundary-contract.ps1',
|
||
'npm.cmd run validate:runtime-assets',
|
||
'npm.cmd run build:page-backgrounds',
|
||
'npm.cmd run build:g01-state-frame',
|
||
'docs/接口与页面映射总表.md'
|
||
)) {
|
||
if (-not $combined.Contains($owner)) {
|
||
throw "当前权威文档缺少现行所有者:$owner"
|
||
}
|
||
}
|
||
|
||
# 当前文档写出的正式 owner 与验证入口必须真实存在;文档不得指向已删除脚本制造“可恢复”的假象。
|
||
foreach ($relativePath in @(
|
||
'components/GenealogyPageBackground.vue',
|
||
'components/ModulePageBackground.vue',
|
||
'design-pipeline/manifests/application-runtime-assets.json',
|
||
'design-pipeline/manifests/page-backgrounds-v3.json',
|
||
'design-pipeline/manifests/g01-state-frame-v3.json',
|
||
'design-pipeline/scripts/build-raster-assets.mjs',
|
||
'design-pipeline/scripts/build_raster_assets.py',
|
||
'tests/runtime-assets-contract.ps1',
|
||
'tests/retired-asset-removal-contract.ps1',
|
||
'tests/global-heritage-visual-system-contract.ps1'
|
||
)) {
|
||
if (-not (Test-Path -LiteralPath (Join-Path $root $relativePath) -PathType Leaf)) {
|
||
throw "当前正式 owner 或验证入口不存在:$relativePath"
|
||
}
|
||
}
|
||
|
||
foreach ($referenceOnly in @(
|
||
'docs/design/assets/a01-vnext/A01-shared-scroll-skins-with-dialog-approved.png',
|
||
'docs/design/assets/a01-vnext/A01-shared-skin-family-option-2-selected.png'
|
||
)) {
|
||
if (-not (Test-Path -LiteralPath (Join-Path $root $referenceOnly) -PathType Leaf)) {
|
||
throw "缺少用户批准的视觉锚点:$referenceOnly"
|
||
}
|
||
if (-not $combined.Contains($referenceOnly)) {
|
||
throw "当前文档没有登记 reference-only 视觉锚点:$referenceOnly"
|
||
}
|
||
}
|
||
|
||
$pipelinePackage = Get-Content -Raw -Encoding UTF8 -LiteralPath (Join-Path $root 'design-pipeline/package.json') | ConvertFrom-Json
|
||
if ($pipelinePackage.scripts.'build:page-backgrounds' -ne 'node scripts/build-raster-assets.mjs design-pipeline/manifests/page-backgrounds-v3.json') {
|
||
throw 'design-pipeline 缺少长页面背景 schema v3 重建命令'
|
||
}
|
||
if ($pipelinePackage.scripts.'build:g01-state-frame' -ne 'node scripts/build-raster-assets.mjs design-pipeline/manifests/g01-state-frame-v3.json') {
|
||
throw 'design-pipeline 缺少 G01 状态边框 schema v3 重建命令'
|
||
}
|
||
if ($pipelinePackage.dependencies -and $pipelinePackage.dependencies.sharp) {
|
||
throw '通用 Pillow 构建器生效后不得重新引入 Sharp 单用途依赖'
|
||
}
|
||
|
||
# 阶段 0 已结束,文档体系只允许五个长期入口;临时清单、旧规格、旧计划、交接和设计记录不得重新出现。
|
||
$temporaryChecklist = Join-Path $root 'docs/项目基线清理清单.md'
|
||
if (Test-Path -LiteralPath $temporaryChecklist -PathType Leaf) {
|
||
throw '阶段 0 已完成,临时清理清单必须删除'
|
||
}
|
||
|
||
$allowedMarkdown = @($currentDocuments)
|
||
$actualMarkdown = @(
|
||
Get-ChildItem -LiteralPath (Join-Path $root 'docs') -Recurse -File -Filter '*.md' |
|
||
ForEach-Object { $_.FullName.Substring($root.Length + 1).Replace('\', '/') }
|
||
)
|
||
$unexpectedMarkdown = @($actualMarkdown | Where-Object { $_ -notin $allowedMarkdown })
|
||
if ($unexpectedMarkdown.Count -gt 0) {
|
||
throw "仍存在当前体系之外的旧 Markdown:$($unexpectedMarkdown -join ', ')"
|
||
}
|
||
|
||
foreach ($retiredRootDocument in @('design-qa.md', '记录.md')) {
|
||
if (Test-Path -LiteralPath (Join-Path $root $retiredRootDocument) -PathType Leaf) {
|
||
throw "仍存在已退役的根目录文档:$retiredRootDocument"
|
||
}
|
||
}
|
||
|
||
# 旧 H5/静态设计图不能再次成为长期视觉证据;目录在 fresh checkout 中可以不存在。
|
||
$screenDirectory = Join-Path $root 'docs/design/screens'
|
||
if (Test-Path -LiteralPath $screenDirectory -PathType Container) {
|
||
$retiredScreens = @(Get-ChildItem -LiteralPath $screenDirectory -Recurse -File)
|
||
if ($retiredScreens.Count -gt 0) {
|
||
throw "仍存在已退役的长期截图:$($retiredScreens.Count) 张"
|
||
}
|
||
}
|
||
|
||
foreach ($retiredTest in @(
|
||
'tests/a01-design-record-audit.ps1',
|
||
'tests/g06-screenshot-consistency.ps1',
|
||
'tests/handoff-documentation-contract.ps1',
|
||
'tests/repository-handoff-size-contract.ps1'
|
||
)) {
|
||
if (Test-Path -LiteralPath (Join-Path $root $retiredTest) -PathType Leaf) {
|
||
throw "仍存在只保护旧文档或截图的测试:$retiredTest"
|
||
}
|
||
}
|
||
|
||
$sddDirectory = Join-Path $root '.superpowers/sdd'
|
||
if (Test-Path -LiteralPath $sddDirectory -PathType Container) {
|
||
$sddFiles = @(Get-ChildItem -LiteralPath $sddDirectory -Recurse -File)
|
||
if ($sddFiles.Count -gt 0) {
|
||
throw "仍存在已完成任务的内部 SDD 过程文件:$($sddFiles.Count) 个"
|
||
}
|
||
}
|
||
|
||
Write-Output 'CURRENT-DOCUMENTATION-CONTRACT PASS'
|