@@ -0,0 +1,557 @@
param (
[ string ] $LibraryRoot = " " ,
[ string ] $ArtifactRoot = " " ,
[ int ] $MinReviewRegistrations = 1 ,
[ switch ] $FailOnBlocked
)
$ErrorActionPreference = " Stop "
$repoRoot = ( Resolve-Path ( Join-Path $PSScriptRoot " .. " ) ) . Path
$timestamp = Get-Date -Format " yyyy-MM-ddTHH-mm-ss-fff "
if ( [ string ] :: IsNullOrWhiteSpace ( $LibraryRoot ) ) {
$LibraryRoot = Join-Path $repoRoot " project-docs\phase13-legal-knowledge-template-library "
}
if ( -not [ System.IO.Path ] :: IsPathRooted ( $LibraryRoot ) ) {
$LibraryRoot = Join-Path $repoRoot $LibraryRoot
}
if ( [ string ] :: IsNullOrWhiteSpace ( $ArtifactRoot ) ) {
$ArtifactRoot = Join-Path $repoRoot " project-docs\qa-artifacts\phase13-review-execution-gate "
}
if ( -not [ System.IO.Path ] :: IsPathRooted ( $ArtifactRoot ) ) {
$ArtifactRoot = Join-Path $repoRoot $ArtifactRoot
}
$artifactDir = Join-Path $ArtifactRoot $timestamp
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
$summaryJson = Join-Path $artifactDir " phase13-review-execution-gate-summary.json "
$summaryMd = Join-Path $artifactDir " phase13-review-execution-gate-summary.md "
$taskBoardCsv = Join-Path $artifactDir " review-task-board.csv "
$batchSummaryCsv = Join-Path $artifactDir " review-batch-summary.csv "
$blockersCsv = Join-Path $artifactDir " review-execution-blockers.csv "
$gitStatusPath = Join-Path $artifactDir " git-status.txt "
$checks = New-Object System . Collections . Generic . List [ object ]
function Add-Check {
param (
[ string ] $Key ,
[ string ] $Label ,
[ ValidateSet ( " pass " , " fail " , " warn " , " skip " , " blocked " ) ]
[ string ] $Status ,
[ string ] $Detail ,
[ bool ] $Required = $true ,
[ hashtable ] $Meta = @ { }
)
$checks . Add ( [ ordered ] @ {
key = $Key
label = $Label
status = $Status
required = $Required
detail = $Detail
meta = $Meta
} ) | Out-Null
}
function Save-Json {
param ( [ string ] $Path , [ object ] $Value )
$Value | ConvertTo-Json -Depth 40 | Set-Content -Encoding UTF8 -Path $Path
}
function Import-CsvRows {
param ( [ string ] $Path )
if ( -not ( Test-Path -LiteralPath $Path -PathType Leaf ) ) {
return @ ( )
}
return @ ( Import-Csv -LiteralPath $Path )
}
function Split-SemicolonValues {
param ( [ string ] $Value )
if ( [ string ] :: IsNullOrWhiteSpace ( $Value ) ) {
return @ ( )
}
return @ ( $Value -split " ; " | ForEach-Object { $_ . Trim ( ) } | Where-Object { $_ } )
}
function Test-RequiredFlag {
param ( [ string ] $Value )
if ( [ string ] :: IsNullOrWhiteSpace ( $Value ) ) {
return $false
}
return @ ( " true " , " yes " , " 1 " , " required " ) -contains $Value . ToLowerInvariant ( )
}
function Test-PendingReviewer {
param ( [ string ] $Value )
if ( [ string ] :: IsNullOrWhiteSpace ( $Value ) ) {
return $true
}
return @ ( " pending " , " 待法务复核 " , " 待业务复核 " , " 待法务和业务复核 " , " 待复核 " ) -contains $Value . Trim ( )
}
function Test-LibraryFilePath {
param ( [ string ] $RelativePath )
if ( [ string ] :: IsNullOrWhiteSpace ( $RelativePath ) ) {
return $false
}
return Test-Path -LiteralPath ( Join-Path $LibraryRoot ( $RelativePath -replace " / " , " \ " ) ) -PathType Leaf
}
function Convert-StatusText {
param ( [ string ] $Status )
switch ( $Status ) {
" PASS " { return " 通过 " }
" FAIL " { return " 未通过 " }
" BLOCKED " { return " 阻塞 " }
" WARN " { return " 通过但有警告 " }
default { return $Status }
}
}
function Convert-CheckStatusText {
param ( [ string ] $Status )
switch ( $Status ) {
" pass " { return " 通过 " }
" fail " { return " 失败 " }
" warn " { return " 警告 " }
" skip " { return " 跳过 " }
" blocked " { return " 阻塞 " }
default { return $Status }
}
}
function Get-AssetTypeText {
param ( [ string ] $AssetType )
switch ( $AssetType ) {
" legal_source " { return " 法源 " }
" knowledge_card " { return " 知识卡 " }
" review_rule " { return " 审核规则 " }
" clause " { return " 标准条款 " }
" contract_template " { return " 合同模板 " }
" review_golden_sample " { return " 审核黄金样本 " }
" generation_golden_sample " { return " 生成黄金样本 " }
" kb_qa_golden_sample " { return " 知识库问答样本 " }
default { return $AssetType }
}
}
function Get-ReviewLane {
param ( [ string ] $AssetType , [ string ] $ReviewerRole )
if ( $ReviewerRole -match " 业务 " ) {
return " 法务+业务 "
}
if ( $AssetType -eq " contract_template " -or $AssetType -eq " clause " -or $AssetType -eq " generation_golden_sample " ) {
return " 法务+业务 "
}
return " 法务 "
}
function Get-ReviewPriority {
param ( [ string ] $AssetType , [ object ] $Asset )
if ( $AssetType -eq " contract_template " ) {
return " P0 "
}
if ( $AssetType -eq " generation_golden_sample " -and $Asset . asset_id -match " GEN-SAMPLE-TPL- " ) {
return " P0 "
}
if ( $AssetType -eq " review_rule " -and $Asset . severity -eq " high " ) {
return " P0 "
}
if ( $AssetType -eq " clause " ) {
return " P0 "
}
if ( $AssetType -eq " legal_source " ) {
return " P1 "
}
if ( $AssetType -eq " knowledge_card " -or $AssetType -eq " review_rule " -or $AssetType -eq " review_golden_sample " ) {
return " P1 "
}
return " P2 "
}
function New-AssetMeta {
param (
[ string ] $AssetType ,
[ string ] $AssetId ,
[ string ] $AssetName ,
[ string ] $Status ,
[ string ] $ProductionReady ,
[ string ] $LegalReviewer ,
[ string ] $BusinessReviewer ,
[ string ] $Severity = " " ,
[ string ] $SourceIds = " "
)
return [ pscustomobject][ordered ] @ {
asset_type = $AssetType
asset_id = $AssetId
asset_name = $AssetName
status = $Status
production_ready = $ProductionReady
legal_reviewer = $LegalReviewer
business_reviewer = $BusinessReviewer
severity = $Severity
source_ids = $SourceIds
}
}
if ( -not ( Test-Path " $HOME \.Codex\skills\gstack\bin " ) ) {
Add-Check -Key " gstack " -Label " gstack 前置检查 " -Status " fail " -Detail " gstack 缺失 "
} else {
Add-Check -Key " gstack " -Label " gstack 前置检查 " -Status " pass " -Detail " GSTACK_OK "
}
$branch = ( git -C $repoRoot rev-parse - -abbrev -ref HEAD ) . Trim ( )
$commit = ( git -C $repoRoot rev-parse HEAD ) . Trim ( )
$remoteUrl = ( git -C $repoRoot remote get-url origin ) . Trim ( )
$gitStatus = git -C $repoRoot status - -short
( $gitStatus -join [ Environment ] :: NewLine ) | Set-Content -Encoding UTF8 -Path $gitStatusPath
if ( $remoteUrl -match " 192\.168\.3\.90 " ) {
Add-Check -Key " origin_remote " -Label " origin 远端仓库 " -Status " pass " -Detail " origin 指向 192.168.3.90 " -Meta @ { branch = $branch ; commit = $commit ; remote = $remoteUrl }
} else {
Add-Check -Key " origin_remote " -Label " origin 远端仓库 " -Status " fail " -Detail " origin 未指向 192.168.3.90 " -Meta @ { branch = $branch ; commit = $commit ; remote = $remoteUrl }
}
if ( $gitStatus ) {
Add-Check -Key " working_tree " -Label " 工作区状态 " -Status " warn " -Required $false -Detail " 工作区存在未提交改动;本次门禁按证据记录 " -Meta @ { artifact = $gitStatusPath }
} else {
Add-Check -Key " working_tree " -Label " 工作区状态 " -Status " pass " -Detail " 工作区干净 " -Meta @ { artifact = $gitStatusPath }
}
if ( Test-Path -LiteralPath $LibraryRoot -PathType Container ) {
Add-Check -Key " library_root " -Label " 阶段 13 资料包根目录 " -Status " pass " -Detail " 资料包根目录存在 " -Meta @ { root = $LibraryRoot }
} else {
Add-Check -Key " library_root " -Label " 阶段 13 资料包根目录 " -Status " fail " -Detail " 资料包根目录缺失 " -Meta @ { root = $LibraryRoot }
}
$inventoryRoot = Join-Path $LibraryRoot " inventories "
$reviewRegisterPath = Join-Path $inventoryRoot " expert-review-register.csv "
$legalSources = Import-CsvRows -Path ( Join-Path $inventoryRoot " legal-source-inventory.csv " )
$knowledgeCards = Import-CsvRows -Path ( Join-Path $inventoryRoot " legal-knowledge-card-inventory.csv " )
$reviewRules = Import-CsvRows -Path ( Join-Path $inventoryRoot " review-risk-rule-inventory.csv " )
$clauses = Import-CsvRows -Path ( Join-Path $inventoryRoot " clause-library-inventory.csv " )
$templates = Import-CsvRows -Path ( Join-Path $inventoryRoot " contract-template-inventory.csv " )
$reviewSamples = Import-CsvRows -Path ( Join-Path $inventoryRoot " review-golden-sample-inventory.csv " )
$generationSamples = Import-CsvRows -Path ( Join-Path $inventoryRoot " generation-golden-sample-inventory.csv " )
$kbQaSamples = Import-CsvRows -Path ( Join-Path $inventoryRoot " kb-qa-golden-sample-inventory.csv " )
$reviewRows = Import-CsvRows -Path $reviewRegisterPath
if ( $reviewRows . Count -ge $MinReviewRegistrations ) {
Add-Check -Key " review_register_minimum " -Label " 复核登记数量下限 " -Status " pass " -Detail " 复核登记数量= $( $reviewRows . Count ) ,最低要求= $MinReviewRegistrations "
} else {
Add-Check -Key " review_register_minimum " -Label " 复核登记数量下限 " -Status " fail " -Detail " 复核登记数量= $( $reviewRows . Count ) ,最低要求= $MinReviewRegistrations "
}
$assetMap = @ { }
foreach ( $row in $legalSources ) {
$assetMap [ " legal_source: $( $row . source_id ) " ] = New-AssetMeta -AssetType " legal_source " -AssetId $row . source_id -AssetName $row . title -Status $row . status -ProductionReady " " -LegalReviewer $row . legal_reviewer -BusinessReviewer " " -SourceIds $row . source_id
}
foreach ( $row in $knowledgeCards ) {
$assetMap [ " knowledge_card: $( $row . knowledge_card_id ) " ] = New-AssetMeta -AssetType " knowledge_card " -AssetId $row . knowledge_card_id -AssetName $row . title -Status $row . status -ProductionReady $row . production_ready -LegalReviewer $row . legal_reviewer -BusinessReviewer " " -SourceIds $row . source_ids
}
foreach ( $row in $reviewRules ) {
$assetMap [ " review_rule: $( $row . rule_id ) " ] = New-AssetMeta -AssetType " review_rule " -AssetId $row . rule_id -AssetName $row . risk_name -Status $row . status -ProductionReady $row . production_ready -LegalReviewer $row . legal_reviewer -BusinessReviewer " " -Severity $row . severity -SourceIds $row . source_ids
}
foreach ( $row in $clauses ) {
$assetMap [ " clause: $( $row . clause_id ) " ] = New-AssetMeta -AssetType " clause " -AssetId $row . clause_id -AssetName $row . clause_name -Status $row . status -ProductionReady $row . production_ready -LegalReviewer $row . legal_reviewer -BusinessReviewer $row . business_reviewer -SourceIds $row . source_ids
}
foreach ( $row in $templates ) {
$assetMap [ " contract_template: $( $row . template_id ) " ] = New-AssetMeta -AssetType " contract_template " -AssetId $row . template_id -AssetName $row . template_name -Status $row . status -ProductionReady $row . production_ready -LegalReviewer $row . legal_reviewer -BusinessReviewer $row . business_reviewer -SourceIds " "
}
foreach ( $row in $reviewSamples ) {
$assetMap [ " review_golden_sample: $( $row . sample_id ) " ] = New-AssetMeta -AssetType " review_golden_sample " -AssetId $row . sample_id -AssetName $row . contract_type -Status $row . status -ProductionReady $row . production_ready -LegalReviewer $row . legal_reviewer -BusinessReviewer " " -SourceIds $row . source_ids
}
foreach ( $row in $generationSamples ) {
$assetMap [ " generation_golden_sample: $( $row . sample_id ) " ] = New-AssetMeta -AssetType " generation_golden_sample " -AssetId $row . sample_id -AssetName $row . contract_type -Status $row . status -ProductionReady $row . production_ready -LegalReviewer $row . legal_reviewer -BusinessReviewer $row . business_reviewer -SourceIds $row . expected_source_citations
}
foreach ( $row in $kbQaSamples ) {
$assetMap [ " kb_qa_golden_sample: $( $row . sample_id ) " ] = New-AssetMeta -AssetType " kb_qa_golden_sample " -AssetId $row . sample_id -AssetName $row . question -Status $row . status -ProductionReady $row . production_ready -LegalReviewer $row . legal_reviewer -BusinessReviewer " " -SourceIds $row . expected_source_ids
}
$duplicateReviewIds = @ ( $reviewRows | Where-Object { -not [ string ] :: IsNullOrWhiteSpace ( $_ . review_id ) } | Group-Object review_id | Where-Object { $_ . Count -gt 1 } | Select-Object -ExpandProperty Name )
if ( $duplicateReviewIds . Count -eq 0 ) {
Add-Check -Key " review_id_unique " -Label " 复核任务编号唯一性 " -Status " pass " -Detail " 未发现重复复核任务编号 "
} else {
Add-Check -Key " review_id_unique " -Label " 复核任务编号唯一性 " -Status " fail " -Detail " 重复编号: $( $duplicateReviewIds -join ',' ) " -Meta @ { duplicates = $duplicateReviewIds }
}
$allowedReviewResults = @ ( " PENDING " , " CHANGES_REQUIRED " , " APPROVED_FOR_REVIEW_IMPORT " , " APPROVED_FOR_PRODUCTION " , " REJECTED " )
$allowedReviewStatus = @ ( " PENDING " , " IN_PROGRESS " , " CHANGES_REQUIRED " , " COMPLETED " , " REJECTED " )
$reviewRequiredMissing = @ ( )
$reviewStatusInvalid = @ ( )
$reviewUnknownAssets = @ ( )
$reviewEvidenceMissing = @ ( )
$approvedWithoutDate = @ ( )
$approvedWithoutHuman = @ ( )
$taskBoard = New-Object System . Collections . Generic . List [ object ]
foreach ( $row in $reviewRows ) {
$assetKey = " $( $row . asset_type ) : $( $row . asset_id ) "
$asset = if ( $assetMap . ContainsKey ( $assetKey ) ) { $assetMap [ $assetKey ] } else { $null }
if ( $null -eq $asset ) {
$reviewUnknownAssets + = $assetKey
}
$missing = New-Object System . Collections . Generic . List [ string ]
foreach ( $field in @ ( " review_id " , " asset_type " , " asset_id " , " reviewer " , " reviewer_role " , " review_result " , " required_changes " , " follow_up_owner " , " evidence_path " , " status " ) ) {
if ( [ string ] :: IsNullOrWhiteSpace ( $row . $field ) ) {
$missing . Add ( $field ) | Out-Null
}
}
if ( $missing . Count -gt 0 ) {
$reviewRequiredMissing + = " $( $row . review_id ) : $( $missing -join ';' ) "
}
if ( $allowedReviewResults -notcontains $row . review_result -or $allowedReviewStatus -notcontains $row . status ) {
$reviewStatusInvalid + = $row . review_id
}
if ( -not ( Test-LibraryFilePath -RelativePath $row . evidence_path ) ) {
$reviewEvidenceMissing + = $row . review_id
}
$isApproved = @ ( " APPROVED_FOR_REVIEW_IMPORT " , " APPROVED_FOR_PRODUCTION " ) -contains $row . review_result
$reviewerPending = Test-PendingReviewer -Value $row . reviewer
if ( $isApproved -and [ string ] :: IsNullOrWhiteSpace ( $row . review_date ) ) {
$approvedWithoutDate + = $row . review_id
}
if ( $isApproved -and $reviewerPending ) {
$approvedWithoutHuman + = $row . review_id
}
$lane = Get-ReviewLane -AssetType $row . asset_type -ReviewerRole $row . reviewer_role
$priority = Get-ReviewPriority -AssetType $row . asset_type -Asset $asset
$blockers = New-Object System . Collections . Generic . List [ string ]
if ( $reviewerPending ) { $blockers . Add ( " reviewer_not_assigned " ) | Out-Null }
if ( $row . review_result -eq " PENDING " -or $row . status -eq " PENDING " ) { $blockers . Add ( " review_pending " ) | Out-Null }
if ( $null -eq $asset ) { $blockers . Add ( " asset_not_found " ) | Out-Null }
if ( -not ( Test-LibraryFilePath -RelativePath $row . evidence_path ) ) { $blockers . Add ( " evidence_missing " ) | Out-Null }
$taskBoard . Add ( [ pscustomobject][ordered ] @ {
priority = $priority
review_lane = $lane
asset_type = $row . asset_type
asset_type_text = Get-AssetTypeText -AssetType $row . asset_type
asset_id = $row . asset_id
asset_name = if ( $asset ) { $asset . asset_name } else { " " }
asset_status = if ( $asset ) { $asset . status } else { " " }
production_ready = if ( $asset ) { $asset . production_ready } else { " " }
review_id = $row . review_id
reviewer = $row . reviewer
reviewer_role = $row . reviewer_role
review_result = $row . review_result
review_status = $row . status
follow_up_owner = $row . follow_up_owner
required_changes = $row . required_changes
evidence_path = $row . evidence_path
blocker_count = $blockers . Count
blockers = ( $blockers -join " ; " )
} ) | Out-Null
}
$pendingRows = @ ( $taskBoard | Where-Object { $_ . review_result -eq " PENDING " -or $_ . review_status -eq " PENDING " } )
$unassignedRows = @ ( $taskBoard | Where-Object { Test-PendingReviewer -Value $_ . reviewer } )
$blockedRows = @ ( $taskBoard | Where-Object { $_ . blocker_count -gt 0 } )
$completedRows = @ ( $taskBoard | Where-Object { @ ( " APPROVED_FOR_REVIEW_IMPORT " , " APPROVED_FOR_PRODUCTION " , " REJECTED " ) -contains $_ . review_result -and $_ . review_status -in @ ( " COMPLETED " , " REJECTED " ) } )
if ( $reviewRequiredMissing . Count -eq 0 ) {
Add-Check -Key " review_required_fields " -Label " 复核任务必填字段 " -Status " pass " -Detail " 复核任务必填字段均已填写 "
} else {
Add-Check -Key " review_required_fields " -Label " 复核任务必填字段 " -Status " fail " -Detail " 以下复核任务缺少必填字段: $( $reviewRequiredMissing -join ',' ) " -Meta @ { rows = $reviewRequiredMissing }
}
if ( $reviewStatusInvalid . Count -eq 0 ) {
Add-Check -Key " review_status_values " -Label " 复核结果和状态枚举 " -Status " pass " -Detail " 复核结果和状态均在允许范围内 "
} else {
Add-Check -Key " review_status_values " -Label " 复核结果和状态枚举 " -Status " fail " -Detail " 以下复核任务状态无效: $( $reviewStatusInvalid -join ',' ) " -Meta @ { rows = $reviewStatusInvalid }
}
if ( $reviewUnknownAssets . Count -eq 0 ) {
Add-Check -Key " review_asset_traceability " -Label " 复核任务资产可追溯 " -Status " pass " -Detail " 复核任务均绑定已登记资产 "
} else {
Add-Check -Key " review_asset_traceability " -Label " 复核任务资产可追溯 " -Status " fail " -Detail " 以下复核任务绑定未知资产: $( $reviewUnknownAssets -join ',' ) " -Meta @ { rows = $reviewUnknownAssets }
}
if ( $reviewEvidenceMissing . Count -eq 0 ) {
Add-Check -Key " review_evidence_files " -Label " 复核证据模板 " -Status " pass " -Detail " 复核证据模板均存在 "
} else {
Add-Check -Key " review_evidence_files " -Label " 复核证据模板 " -Status " fail " -Detail " 以下复核任务缺少证据模板: $( $reviewEvidenceMissing -join ',' ) " -Meta @ { rows = $reviewEvidenceMissing }
}
if ( $approvedWithoutDate . Count -eq 0 -and $approvedWithoutHuman . Count -eq 0 ) {
Add-Check -Key " approved_review_integrity " -Label " 已批准复核完整性 " -Status " pass " -Detail " 未发现缺少日期或真实复核人的已批准任务 "
} else {
Add-Check -Key " approved_review_integrity " -Label " 已批准复核完整性 " -Status " fail " -Detail " 缺少日期: $( $approvedWithoutDate -join ',' ) ;缺少真实复核人: $( $approvedWithoutHuman -join ',' ) " -Meta @ { without_date = $approvedWithoutDate ; without_human = $approvedWithoutHuman }
}
if ( $unassignedRows . Count -eq 0 ) {
Add-Check -Key " review_assignment " -Label " 复核人分配 " -Status " pass " -Detail " 所有复核任务均已分配真实复核人 "
} else {
Add-Check -Key " review_assignment " -Label " 复核人分配 " -Status " blocked " -Detail " 仍有 $( $unassignedRows . Count ) 个复核任务未分配真实复核人 " -Meta @ { count = $unassignedRows . Count }
}
if ( $pendingRows . Count -eq 0 ) {
Add-Check -Key " review_execution " -Label " 复核执行完成度 " -Status " pass " -Detail " 所有复核任务均已完成或拒绝 "
} else {
Add-Check -Key " review_execution " -Label " 复核执行完成度 " -Status " blocked " -Detail " 仍有 $( $pendingRows . Count ) 个复核任务处于 PENDING "
}
$firstWaveAssetTypes = @ ( " contract_template " , " clause " , " review_rule " , " generation_golden_sample " )
$firstWaveRows = @ ( $taskBoard | Where-Object { $firstWaveAssetTypes -contains $_ . asset_type } )
$firstWavePending = @ ( $firstWaveRows | Where-Object { $_ . blocker_count -gt 0 } )
if ( $firstWaveRows . Count -gt 0 -and $firstWavePending . Count -eq 0 ) {
Add-Check -Key " first_wave_review " -Label " 第一批模板相关复核 " -Status " pass " -Detail " 模板、条款、规则和生成样本复核均已完成 "
} elseif ( $firstWaveRows . Count -eq 0 ) {
Add-Check -Key " first_wave_review " -Label " 第一批模板相关复核 " -Status " fail " -Detail " 未找到模板相关复核任务 "
} else {
Add-Check -Key " first_wave_review " -Label " 第一批模板相关复核 " -Status " blocked " -Detail " 第一批模板相关复核仍有 $( $firstWavePending . Count ) 个阻塞项 "
}
$taskBoard |
Sort-Object priority , review_lane , asset_type , asset_id |
Export-Csv -LiteralPath $taskBoardCsv -NoTypeInformation -Encoding UTF8
$blockedRows |
Sort-Object priority , review_lane , asset_type , asset_id |
Export-Csv -LiteralPath $blockersCsv -NoTypeInformation -Encoding UTF8
$batchSummary = @ (
$taskBoard |
Group-Object priority , review_lane , asset_type |
ForEach-Object {
$groupRows = @ ( $_ . Group )
[ pscustomobject][ordered ] @ {
priority = [ string ] $groupRows [ 0 ] . priority
review_lane = [ string ] $groupRows [ 0 ] . review_lane
asset_type = [ string ] $groupRows [ 0 ] . asset_type
asset_type_text = [ string ] $groupRows [ 0 ] . asset_type_text
total = $groupRows . Count
blocked = @ ( $groupRows | Where-Object { $_ . blocker_count -gt 0 } ) . Count
pending = @ ( $groupRows | Where-Object { $_ . review_result -eq " PENDING " -or $_ . review_status -eq " PENDING " } ) . Count
completed = @ ( $groupRows | Where-Object { @ ( " APPROVED_FOR_REVIEW_IMPORT " , " APPROVED_FOR_PRODUCTION " , " REJECTED " ) -contains $_ . review_result } ) . Count
}
}
)
$batchSummary |
Sort-Object priority , review_lane , asset_type |
Export-Csv -LiteralPath $batchSummaryCsv -NoTypeInformation -Encoding UTF8
$failedRequired = @ ( $checks | Where-Object { $_ . required -and $_ . status -eq " fail " } ) . Count
$blockedRequired = @ ( $checks | Where-Object { $_ . required -and $_ . status -eq " blocked " } ) . Count
$warnings = @ ( $checks | Where-Object { $_ . status -eq " warn " } ) . Count
$overallStatus = if ( $failedRequired -gt 0 ) {
" FAIL "
} elseif ( $blockedRequired -gt 0 ) {
" BLOCKED "
} elseif ( $warnings -gt 0 ) {
" WARN "
} else {
" PASS "
}
$metrics = [ ordered ] @ {
review_registrations = $reviewRows . Count
task_board_rows = $taskBoard . Count
pending_reviews = $pendingRows . Count
unassigned_reviews = $unassignedRows . Count
blocked_reviews = $blockedRows . Count
completed_reviews = $completedRows . Count
first_wave_reviews = $firstWaveRows . Count
first_wave_blocked = $firstWavePending . Count
legal_lane_reviews = @ ( $taskBoard | Where-Object { $_ . review_lane -eq " 法务 " } ) . Count
legal_business_lane_reviews = @ ( $taskBoard | Where-Object { $_ . review_lane -eq " 法务+业务 " } ) . Count
p0_reviews = @ ( $taskBoard | Where-Object { $_ . priority -eq " P0 " } ) . Count
p1_reviews = @ ( $taskBoard | Where-Object { $_ . priority -eq " P1 " } ) . Count
p2_reviews = @ ( $taskBoard | Where-Object { $_ . priority -eq " P2 " } ) . Count
}
$summary = [ ordered ] @ {
name = " phase13-review-execution-gate "
status = $overallStatus
generated_at = ( Get-Date ) . ToUniversalTime ( ) . ToString ( " o " )
branch = $branch
commit = $commit
remote = $remoteUrl
artifact_dir = $artifactDir
library_root = $LibraryRoot
checks = $checks . ToArray ( )
metrics = $metrics
artifacts = [ ordered ] @ {
summary_json = $summaryJson
summary_md = $summaryMd
task_board = $taskBoardCsv
batch_summary = $batchSummaryCsv
blockers = $blockersCsv
git_status = $gitStatusPath
}
totals = [ ordered ] @ {
total_checks = $checks . Count
failed_required = $failedRequired
blocked_required = $blockedRequired
warnings = $warnings
}
}
Save-Json -Path $summaryJson -Value $summary
$statusText = Convert-StatusText -Status $overallStatus
$md = New-Object System . Collections . Generic . List [ string ]
$md . Add ( " # 阶段 13 法务与业务复核执行门禁 " ) | Out-Null
$md . Add ( " " ) | Out-Null
$md . Add ( " - 门禁状态: $statusText ( $overallStatus ) " ) | Out-Null
$md . Add ( " - 生成时间: $( $summary . generated_at ) " ) | Out-Null
$md . Add ( " - 分支: $branch " ) | Out-Null
$md . Add ( " - 提交: $commit " ) | Out-Null
$md . Add ( " - 资料包: $LibraryRoot " ) | Out-Null
$md . Add ( " - 产物目录: $artifactDir " ) | Out-Null
$md . Add ( " " ) | Out-Null
$md . Add ( " ## 统计 " ) | Out-Null
$md . Add ( " " ) | Out-Null
$md . Add ( " | 指标 | 数量 | " ) | Out-Null
$md . Add ( " |---|---:| " ) | Out-Null
$md . Add ( " | 复核登记 | $( $metrics . review_registrations ) | " ) | Out-Null
$md . Add ( " | 任务看板行数 | $( $metrics . task_board_rows ) | " ) | Out-Null
$md . Add ( " | 待执行复核 | $( $metrics . pending_reviews ) | " ) | Out-Null
$md . Add ( " | 未分配真实复核人 | $( $metrics . unassigned_reviews ) | " ) | Out-Null
$md . Add ( " | 阻塞复核任务 | $( $metrics . blocked_reviews ) | " ) | Out-Null
$md . Add ( " | 已完成复核 | $( $metrics . completed_reviews ) | " ) | Out-Null
$md . Add ( " | 第一批模板相关复核 | $( $metrics . first_wave_reviews ) | " ) | Out-Null
$md . Add ( " | 第一批模板相关阻塞 | $( $metrics . first_wave_blocked ) | " ) | Out-Null
$md . Add ( " | P0 复核任务 | $( $metrics . p0_reviews ) | " ) | Out-Null
$md . Add ( " | P1 复核任务 | $( $metrics . p1_reviews ) | " ) | Out-Null
$md . Add ( " | P2 复核任务 | $( $metrics . p2_reviews ) | " ) | Out-Null
$md . Add ( " " ) | Out-Null
$md . Add ( " ## 检查项 " ) | Out-Null
$md . Add ( " " ) | Out-Null
$md . Add ( " | 检查项 | 必需 | 状态 | 说明 | " ) | Out-Null
$md . Add ( " |---|---:|---|---| " ) | Out-Null
foreach ( $check in $checks ) {
$requiredText = if ( $check . required ) { " 是 " } else { " 否 " }
$checkStatus = Convert-CheckStatusText -Status $check . status
$detail = ( [ string ] $check . detail ) . Replace ( " | " , " / " )
$md . Add ( " | $( $check . label ) | $requiredText | $checkStatus | $detail | " ) | Out-Null
}
$md . Add ( " " ) | Out-Null
$md . Add ( " ## 当前结论 " ) | Out-Null
$md . Add ( " " ) | Out-Null
if ( $overallStatus -eq " BLOCKED " ) {
$md . Add ( " 复核执行门禁已建立,但真实复核尚未完成。下一步需要分配真实法务和业务复核人,并把任务从 PENDING 推进到 APPROVED 或 REJECTED。 " ) | Out-Null
} elseif ( $overallStatus -eq " PASS " ) {
$md . Add ( " 复核执行门禁已通过,可以进入 Review 或 Production 入库前的发布门禁。 " ) | Out-Null
} else {
$md . Add ( " 复核执行门禁未完全通过,请查看任务看板和阻塞清单。 " ) | Out-Null
}
$md . Add ( " " ) | Out-Null
$md . Add ( " 本地产物: " ) | Out-Null
$md . Add ( " " ) | Out-Null
$md . Add ( '- `phase13-review-execution-gate-summary.json`' ) | Out-Null
$md . Add ( '- `phase13-review-execution-gate-summary.md`' ) | Out-Null
$md . Add ( '- `review-task-board.csv`' ) | Out-Null
$md . Add ( '- `review-batch-summary.csv`' ) | Out-Null
$md . Add ( '- `review-execution-blockers.csv`' ) | Out-Null
$md | Set-Content -Encoding UTF8 -Path $summaryMd
Write-Host " PHASE13_REVIEW_EXECUTION_GATE_ $overallStatus $artifactDir "
if ( $overallStatus -eq " FAIL " ) {
throw " 阶段 13 法务与业务复核执行门禁未通过。查看: $summaryMd "
}
if ( $overallStatus -eq " BLOCKED " -and $FailOnBlocked ) {
throw " 阶段 13 法务与业务复核执行门禁被阻塞。查看: $summaryMd "
}