docs: add phase 13 first wave review packet
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
8. 法律专家和业务负责人复核登记规范。
|
||||
9. 法务复核知识库导入脚本。
|
||||
10. 法务与业务复核执行门禁和任务看板。
|
||||
11. 首批 P0 复核包、复核分配模板、复核结果回填模板和 DryRun 回填门禁。
|
||||
|
||||
重要规则:
|
||||
|
||||
@@ -38,4 +39,6 @@
|
||||
11. 阶段 13.8 第一批中文合同模板已建立,共 5 个,覆盖采购、通用服务、SaaS、技术开发和商业保密协议,全部保持 `LEGAL_REVIEW` 且 `production_ready=false`。
|
||||
12. 阶段 13.8 专家复核登记已建立,共 82 条,覆盖全部待复核法源、知识卡、规则、条款、模板和样本。
|
||||
13. 阶段 13.9 法务与业务复核执行门禁已建立,可生成复核任务看板、批次统计和阻塞清单。
|
||||
14. 生产发布仍需等待真实法务复核和业务复核完成;当前阻塞项为 82 条复核任务均未分配真实复核人且仍处于 `PENDING`。
|
||||
14. 阶段 13.10 首批 P0 复核包就绪门禁已建立,可生成 22 个 P0 复核任务的分配模板、结果回填模板和单项复核表。
|
||||
15. 阶段 13.10 复核结果回填门禁已建立,默认 DryRun 不写入登记表,真实回填必须显式传入 `-AllowWrite` 且通过完整性校验。
|
||||
16. 生产发布仍需等待真实法务复核和业务复核完成;当前阻塞项为 82 条复核任务均未分配真实复核人且仍处于 `PENDING`。
|
||||
|
||||
@@ -408,3 +408,5 @@ project-docs/phase13-legal-knowledge-template-library/
|
||||
当前第八批执行结果:`合同模板库补齐 + 法务复核登记` 已落地。第一批中文合同模板新增 `5` 个,覆盖采购、通用服务、SaaS、技术开发和商业保密协议;合同生成黄金样本增至 `13` 个;专家复核登记增至 `82` 条并覆盖全部待复核资产。资料门禁、黄金样本评测和入库校验均可通过,状态仍为 `PASS_WITH_LEGAL_REVIEW_REQUIRED`,因为资料尚未完成法务和业务复核。
|
||||
|
||||
当前第九批执行结果:`法务与业务复核执行门禁` 已落地。新增 `scripts/run-phase13-review-execution-gate.ps1`,可基于 `expert-review-register.csv` 生成复核任务看板、批次统计、阻塞清单和门禁摘要;生产试点发布脚本已串联该门禁。当前门禁状态为 `BLOCKED`,因为 `82` 条复核任务均未分配真实复核人且仍处于 `PENDING`;第一批模板相关 `34` 条复核任务全部阻塞。下一步必须由真实法务和业务负责人完成复核后,才能把资产从 `LEGAL_REVIEW` 推进到 `APPROVED` 或 `PUBLISHED`。
|
||||
|
||||
当前第十批执行结果:`首批 P0 复核包就绪门禁` 已落地。新增 `scripts/run-phase13-first-wave-review-readiness.ps1`,默认选择 P0 任务并生成复核分配模板、复核结果回填模板、首批复核清单和逐项复核表。当前首批范围为 `22` 个任务:`5` 个合同模板、`5` 个模板生成样本、`8` 个标准条款和 `4` 条高风险审核规则。新增 `scripts/apply-phase13-review-results.ps1`,用于对真实复核结果进行 DryRun 校验和受保护回填;默认不写入登记表,只有显式传入 `-AllowWrite` 且没有阻塞项时才会更新复核登记。当前两个门禁均为 `BLOCKED`,因为尚未提供真实法务复核人、真实业务复核人、复核截止日期和真实复核结果;这保证了资料包可以交付给人执行,但不能把未复核资料伪装成已批准资产。
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
# AI合同平台 阶段 13 法律知识库与合同模板库建设闭环第十批执行记录(2026-05-01)
|
||||
|
||||
## 一、阶段结论
|
||||
|
||||
本批次完成 `真实复核执行与首批资产批准准备` 闭环,当前状态为 `BLOCKED_ON_REAL_REVIEW_INPUT`。
|
||||
|
||||
第九批已经能证明 82 条复核任务未完成并阻断生产发布。本批次继续推进到可交付给人的执行层:生成首批 P0 复核包、复核分配模板、复核结果回填模板、逐项复核表,并新增复核结果 DryRun 回填门禁。
|
||||
|
||||
本批次没有伪造任何法务或业务复核结论,也没有把任何资产改成 `APPROVED` 或 `production_ready=true`。当前阻塞原因是缺少真实法务复核人、真实业务复核人、复核截止日期和真实复核结果。
|
||||
|
||||
## 二、本批次交付
|
||||
|
||||
新增或更新资料:
|
||||
|
||||
1. `scripts/run-phase13-first-wave-review-readiness.ps1`。
|
||||
2. `scripts/apply-phase13-review-results.ps1`。
|
||||
3. `scripts/run-legal-knowledge-library-gate.ps1`,将首批复核包门禁和复核结果回填门禁纳入脚本存在性检查和敏感信息扫描。
|
||||
4. `project-docs/phase13-legal-knowledge-template-library/README.md`。
|
||||
5. `project-docs/阶段13-法律知识库与合同模板库建设闭环方案-2026-05-01.md`。
|
||||
|
||||
首批复核包产物:
|
||||
|
||||
1. `phase13-first-wave-review-readiness-summary.json`。
|
||||
2. `phase13-first-wave-review-readiness-summary.md`。
|
||||
3. `review-assignment-template.csv`。
|
||||
4. `review-result-import-template.csv`。
|
||||
5. `selected-review-tasks.csv`。
|
||||
6. `first-wave-review-checklist.md`。
|
||||
7. `review-forms/`,共 22 份逐项复核表。
|
||||
|
||||
复核结果回填产物:
|
||||
|
||||
1. `phase13-review-result-apply-summary.json`。
|
||||
2. `phase13-review-result-apply-summary.md`。
|
||||
3. `review-result-apply-plan.csv`。
|
||||
4. `review-result-apply-blockers.csv`。
|
||||
|
||||
## 三、首批复核范围
|
||||
|
||||
本批次默认选择 P0 任务作为首批复核范围:
|
||||
|
||||
| 类型 | 数量 |
|
||||
|---|---:|
|
||||
| P0 复核任务 | 22 |
|
||||
| 合同模板 | 5 |
|
||||
| 模板生成样本 | 5 |
|
||||
| 标准条款 | 8 |
|
||||
| 高风险审核规则 | 4 |
|
||||
| 法务通道任务 | 4 |
|
||||
| 法务+业务通道任务 | 18 |
|
||||
|
||||
这批任务覆盖合同生成上线最危险、最关键的资产:模板正文、模板生成样本、标准条款和高风险审核规则。
|
||||
|
||||
## 四、自动化验证结果
|
||||
|
||||
首批复核就绪门禁:
|
||||
|
||||
```text
|
||||
PHASE13_FIRST_WAVE_REVIEW_READINESS_BLOCKED D:\ai-contract-platform\project-docs\qa-artifacts\phase13-first-wave-review-readiness\2026-05-01T23-39-45-907
|
||||
```
|
||||
|
||||
复核结果回填门禁:
|
||||
|
||||
```text
|
||||
PHASE13_REVIEW_RESULT_APPLY_BLOCKED D:\ai-contract-platform\project-docs\qa-artifacts\phase13-review-result-apply\2026-05-01T23-39-54-328
|
||||
```
|
||||
|
||||
复核执行门禁:
|
||||
|
||||
```text
|
||||
PHASE13_REVIEW_EXECUTION_GATE_BLOCKED D:\ai-contract-platform\project-docs\qa-artifacts\phase13-review-execution-gate\2026-05-01T23-40-01-637
|
||||
```
|
||||
|
||||
资料门禁:
|
||||
|
||||
```text
|
||||
PHASE13_LEGAL_KNOWLEDGE_LIBRARY_GATE_PASS_WITH_LEGAL_REVIEW_REQUIRED D:\ai-contract-platform\project-docs\qa-artifacts\phase13-legal-knowledge-library-gate\2026-05-01T23-40-12-848
|
||||
```
|
||||
|
||||
生产试点发布门禁:
|
||||
|
||||
```text
|
||||
PHASE13_PRODUCTION_PILOT_RELEASE_BLOCKED D:\ai-contract-platform\project-docs\qa-artifacts\phase13-production-pilot-release\2026-05-01T23-40-21-970
|
||||
```
|
||||
|
||||
生产试点发布门禁内部子流程:
|
||||
|
||||
```text
|
||||
ingestion_closed_loop=PASS_WITH_LEGAL_REVIEW_REQUIRED D:\ai-contract-platform\project-docs\qa-artifacts\phase13-ingestion-closed-loop\2026-05-01T23-40-24-242
|
||||
review_execution_gate=BLOCKED D:\ai-contract-platform\project-docs\qa-artifacts\phase13-review-execution-gate\2026-05-01T23-40-32-905
|
||||
```
|
||||
|
||||
## 五、关键统计
|
||||
|
||||
| 指标 | 结果 |
|
||||
|---|---:|
|
||||
| 首批复核任务 | 22 |
|
||||
| 缺少真实法务复核人任务 | 22 |
|
||||
| 缺少真实业务复核人任务 | 18 |
|
||||
| 是否缺少复核截止日期 | 是 |
|
||||
| 复核结果回填行 | 22 |
|
||||
| 回填阻塞项 | 22 |
|
||||
| 仍待复核 | 22 |
|
||||
| 可进入试点复核库 | 0 |
|
||||
| 可进入生产发布 | 0 |
|
||||
| 发布候选资产 | 82 |
|
||||
| 可发布资产 | 0 |
|
||||
| 发布阻塞资产 | 82 |
|
||||
|
||||
## 六、安全边界
|
||||
|
||||
本批次新增的回填脚本默认 `DryRun`:
|
||||
|
||||
1. 不写入 `expert-review-register.csv`。
|
||||
2. 不修改任何资产清单中的 `status`。
|
||||
3. 不修改任何资产清单中的 `production_ready`。
|
||||
4. 发现 `PENDING`、缺少真实复核人、缺少复核日期、缺少证据文件或未获生产批准却设置 `production_ready=true` 时,回填门禁保持 `BLOCKED`。
|
||||
5. 只有显式传入 `-AllowWrite` 且无阻塞项时,才允许回填复核登记。
|
||||
|
||||
## 七、下一步
|
||||
|
||||
下一步需要真实外部输入:
|
||||
|
||||
1. 指定真实法务复核人。
|
||||
2. 指定真实业务复核人。
|
||||
3. 指定首批 P0 复核截止日期。
|
||||
4. 由复核人按 `review-forms/` 完成 22 项复核。
|
||||
5. 在 `review-result-import-template.csv` 中回填复核日期、复核结论、修改要求和证据路径。
|
||||
6. 使用 `scripts/apply-phase13-review-results.ps1` 先 DryRun 校验,再在确认无阻塞后显式使用 `-AllowWrite` 回填登记。
|
||||
7. 重新运行复核执行门禁和生产试点发布门禁。
|
||||
|
||||
阶段 13 第十批当前状态:`BLOCKED_ON_REAL_REVIEW_INPUT`。
|
||||
@@ -0,0 +1,449 @@
|
||||
param(
|
||||
[string]$ReviewResultsPath = "",
|
||||
[string]$LibraryRoot = "",
|
||||
[string]$ArtifactRoot = "",
|
||||
[switch]$AllowWrite,
|
||||
[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-result-apply"
|
||||
}
|
||||
if (-not [System.IO.Path]::IsPathRooted($ArtifactRoot)) {
|
||||
$ArtifactRoot = Join-Path $repoRoot $ArtifactRoot
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ReviewResultsPath)) {
|
||||
$packetRoot = Join-Path $repoRoot "project-docs\qa-artifacts\phase13-first-wave-review-readiness"
|
||||
$latestPacket = Get-ChildItem -LiteralPath $packetRoot -Directory -ErrorAction SilentlyContinue |
|
||||
Sort-Object Name -Descending |
|
||||
Select-Object -First 1
|
||||
if ($latestPacket) {
|
||||
$ReviewResultsPath = Join-Path $latestPacket.FullName "review-result-import-template.csv"
|
||||
}
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($ReviewResultsPath) -and -not [System.IO.Path]::IsPathRooted($ReviewResultsPath)) {
|
||||
$ReviewResultsPath = Join-Path $repoRoot $ReviewResultsPath
|
||||
}
|
||||
|
||||
$artifactDir = Join-Path $ArtifactRoot $timestamp
|
||||
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
|
||||
|
||||
$summaryJson = Join-Path $artifactDir "phase13-review-result-apply-summary.json"
|
||||
$summaryMd = Join-Path $artifactDir "phase13-review-result-apply-summary.md"
|
||||
$applyPlanCsv = Join-Path $artifactDir "review-result-apply-plan.csv"
|
||||
$blockersCsv = Join-Path $artifactDir "review-result-apply-blockers.csv"
|
||||
$gitStatusPath = Join-Path $artifactDir "git-status.txt"
|
||||
|
||||
$checks = New-Object System.Collections.Generic.List[object]
|
||||
$applyPlan = New-Object System.Collections.Generic.List[object]
|
||||
$blockers = 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 Get-FieldValue {
|
||||
param([object]$Row, [string]$Name)
|
||||
if ($null -eq $Row) {
|
||||
return ""
|
||||
}
|
||||
$property = $Row.PSObject.Properties[$Name]
|
||||
if ($null -eq $property -or $null -eq $property.Value) {
|
||||
return ""
|
||||
}
|
||||
return [string]$property.Value
|
||||
}
|
||||
|
||||
function Test-PendingReviewer {
|
||||
param([string]$Value)
|
||||
if ([string]::IsNullOrWhiteSpace($Value)) {
|
||||
return $true
|
||||
}
|
||||
return @("pending", "待法务复核", "待业务复核", "待法务和业务复核", "待复核") -contains $Value.Trim()
|
||||
}
|
||||
|
||||
function Test-Truthy {
|
||||
param([string]$Value)
|
||||
if ([string]::IsNullOrWhiteSpace($Value)) {
|
||||
return $false
|
||||
}
|
||||
return @("true", "yes", "1", "required") -contains $Value.ToLowerInvariant()
|
||||
}
|
||||
|
||||
function Test-LibraryFilePath {
|
||||
param([string]$RelativePath)
|
||||
if ([string]::IsNullOrWhiteSpace($RelativePath)) {
|
||||
return $false
|
||||
}
|
||||
if ([System.IO.Path]::IsPathRooted($RelativePath)) {
|
||||
return Test-Path -LiteralPath $RelativePath -PathType Leaf
|
||||
}
|
||||
return Test-Path -LiteralPath (Join-Path $LibraryRoot ($RelativePath -replace "/", "\")) -PathType Leaf
|
||||
}
|
||||
|
||||
function Convert-OverallStatusText {
|
||||
param([string]$Status)
|
||||
switch ($Status) {
|
||||
"PASS" { return "通过" }
|
||||
"WARN" { return "通过但有警告" }
|
||||
"FAIL" { return "未通过" }
|
||||
"BLOCKED" { 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 Add-Blocker {
|
||||
param([string]$ReviewId, [string]$Reason, [string]$Detail)
|
||||
$blockers.Add([pscustomobject][ordered]@{
|
||||
review_id = $ReviewId
|
||||
reason = $Reason
|
||||
detail = $Detail
|
||||
}) | Out-Null
|
||||
}
|
||||
|
||||
function Export-Register {
|
||||
param([object[]]$Rows, [string]$Path)
|
||||
$Rows | Export-Csv -LiteralPath $Path -NoTypeInformation -Encoding UTF8
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($ReviewResultsPath) -and (Test-Path -LiteralPath $ReviewResultsPath -PathType Leaf)) {
|
||||
Add-Check -Key "review_results_file" -Label "复核结果文件" -Status "pass" -Detail "复核结果文件存在" -Meta @{ path = $ReviewResultsPath }
|
||||
} else {
|
||||
Add-Check -Key "review_results_file" -Label "复核结果文件" -Status "fail" -Detail "复核结果文件不存在" -Meta @{ path = $ReviewResultsPath }
|
||||
}
|
||||
|
||||
$inventoryRoot = Join-Path $LibraryRoot "inventories"
|
||||
$reviewRegisterPath = Join-Path $inventoryRoot "expert-review-register.csv"
|
||||
$reviewRows = Import-CsvRows -Path $reviewRegisterPath
|
||||
$resultRows = Import-CsvRows -Path $ReviewResultsPath
|
||||
|
||||
$registerMap = @{}
|
||||
foreach ($row in $reviewRows) {
|
||||
$registerMap[$row.review_id] = $row
|
||||
}
|
||||
|
||||
$allowedResults = @("PENDING", "CHANGES_REQUIRED", "APPROVED_FOR_REVIEW_IMPORT", "APPROVED_FOR_PRODUCTION", "REJECTED")
|
||||
$allowedStatuses = @("PENDING", "IN_PROGRESS", "CHANGES_REQUIRED", "COMPLETED", "REJECTED")
|
||||
|
||||
$pendingCount = 0
|
||||
$approvedForReviewCount = 0
|
||||
$approvedForProductionCount = 0
|
||||
$changesRequiredCount = 0
|
||||
$rejectedCount = 0
|
||||
|
||||
foreach ($row in $resultRows) {
|
||||
$reviewId = Get-FieldValue -Row $row -Name "review_id"
|
||||
$assetType = Get-FieldValue -Row $row -Name "asset_type"
|
||||
$assetId = Get-FieldValue -Row $row -Name "asset_id"
|
||||
$reviewer = Get-FieldValue -Row $row -Name "reviewer"
|
||||
$reviewDate = Get-FieldValue -Row $row -Name "review_date"
|
||||
$reviewResult = Get-FieldValue -Row $row -Name "review_result"
|
||||
$reviewStatus = Get-FieldValue -Row $row -Name "status"
|
||||
$evidencePath = Get-FieldValue -Row $row -Name "evidence_path"
|
||||
$assetStatusAfterReview = Get-FieldValue -Row $row -Name "asset_status_after_review"
|
||||
$productionReadyAfterReview = Get-FieldValue -Row $row -Name "production_ready_after_review"
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($reviewId)) {
|
||||
Add-Blocker -ReviewId "" -Reason "missing_review_id" -Detail "复核结果行缺少 review_id"
|
||||
continue
|
||||
}
|
||||
if (-not $registerMap.ContainsKey($reviewId)) {
|
||||
Add-Blocker -ReviewId $reviewId -Reason "unknown_review_id" -Detail "复核任务不在 expert-review-register.csv 中"
|
||||
continue
|
||||
}
|
||||
|
||||
$registerRow = $registerMap[$reviewId]
|
||||
if ($assetType -ne $registerRow.asset_type -or $assetId -ne $registerRow.asset_id) {
|
||||
Add-Blocker -ReviewId $reviewId -Reason "asset_mismatch" -Detail "复核结果资产与登记表不一致"
|
||||
}
|
||||
if ($allowedResults -notcontains $reviewResult) {
|
||||
Add-Blocker -ReviewId $reviewId -Reason "invalid_review_result" -Detail "复核结果枚举非法:$reviewResult"
|
||||
}
|
||||
if ($allowedStatuses -notcontains $reviewStatus) {
|
||||
Add-Blocker -ReviewId $reviewId -Reason "invalid_status" -Detail "复核状态枚举非法:$reviewStatus"
|
||||
}
|
||||
|
||||
$isApproved = @("APPROVED_FOR_REVIEW_IMPORT", "APPROVED_FOR_PRODUCTION") -contains $reviewResult
|
||||
if ($reviewResult -eq "PENDING" -or $reviewStatus -eq "PENDING") {
|
||||
$pendingCount += 1
|
||||
Add-Blocker -ReviewId $reviewId -Reason "pending_review" -Detail "复核结果仍为 PENDING"
|
||||
} elseif ($reviewResult -eq "CHANGES_REQUIRED") {
|
||||
$changesRequiredCount += 1
|
||||
} elseif ($reviewResult -eq "REJECTED") {
|
||||
$rejectedCount += 1
|
||||
} elseif ($reviewResult -eq "APPROVED_FOR_REVIEW_IMPORT") {
|
||||
$approvedForReviewCount += 1
|
||||
} elseif ($reviewResult -eq "APPROVED_FOR_PRODUCTION") {
|
||||
$approvedForProductionCount += 1
|
||||
}
|
||||
|
||||
if ($isApproved -and (Test-PendingReviewer -Value $reviewer)) {
|
||||
Add-Blocker -ReviewId $reviewId -Reason "approved_without_human_reviewer" -Detail "已批准结果缺少真实复核人"
|
||||
}
|
||||
if ($isApproved -and [string]::IsNullOrWhiteSpace($reviewDate)) {
|
||||
Add-Blocker -ReviewId $reviewId -Reason "approved_without_review_date" -Detail "已批准结果缺少复核日期"
|
||||
}
|
||||
if ($isApproved -and -not (Test-LibraryFilePath -RelativePath $evidencePath)) {
|
||||
Add-Blocker -ReviewId $reviewId -Reason "approved_without_evidence" -Detail "已批准结果缺少可追溯证据文件"
|
||||
}
|
||||
if ((Test-Truthy -Value $productionReadyAfterReview) -and $reviewResult -ne "APPROVED_FOR_PRODUCTION") {
|
||||
Add-Blocker -ReviewId $reviewId -Reason "production_ready_without_production_approval" -Detail "未获得生产批准却尝试设置 production_ready=true"
|
||||
}
|
||||
if (@("APPROVED", "PUBLISHED", "LEGAL_REVIEW", "DRAFT", "DEPRECATED", "") -notcontains $assetStatusAfterReview) {
|
||||
Add-Blocker -ReviewId $reviewId -Reason "invalid_asset_status_after_review" -Detail "资产复核后状态非法:$assetStatusAfterReview"
|
||||
}
|
||||
|
||||
$applyAction = "no_write_dry_run"
|
||||
if ($AllowWrite) {
|
||||
$applyAction = if ($reviewResult -eq "PENDING") { "skip_pending" } else { "update_review_register" }
|
||||
}
|
||||
|
||||
$applyPlan.Add([pscustomobject][ordered]@{
|
||||
review_id = $reviewId
|
||||
asset_type = $assetType
|
||||
asset_id = $assetId
|
||||
reviewer = $reviewer
|
||||
review_date = $reviewDate
|
||||
review_result = $reviewResult
|
||||
status = $reviewStatus
|
||||
asset_status_after_review = $assetStatusAfterReview
|
||||
production_ready_after_review = $productionReadyAfterReview
|
||||
action = $applyAction
|
||||
}) | Out-Null
|
||||
}
|
||||
|
||||
if ($resultRows.Count -gt 0) {
|
||||
Add-Check -Key "review_result_rows" -Label "复核结果行数" -Status "pass" -Detail "复核结果行数=$($resultRows.Count)"
|
||||
} else {
|
||||
Add-Check -Key "review_result_rows" -Label "复核结果行数" -Status "fail" -Detail "复核结果为空"
|
||||
}
|
||||
|
||||
if ($blockers.Count -eq 0) {
|
||||
Add-Check -Key "review_result_integrity" -Label "复核结果完整性" -Status "pass" -Detail "复核结果完整性通过"
|
||||
} else {
|
||||
Add-Check -Key "review_result_integrity" -Label "复核结果完整性" -Status "blocked" -Detail "发现 $($blockers.Count) 个阻塞项" -Meta @{ blockers = $blockers }
|
||||
}
|
||||
|
||||
if ($AllowWrite) {
|
||||
if ($blockers.Count -eq 0) {
|
||||
foreach ($row in $resultRows) {
|
||||
$reviewId = Get-FieldValue -Row $row -Name "review_id"
|
||||
$registerRow = $registerMap[$reviewId]
|
||||
$registerRow.reviewer = Get-FieldValue -Row $row -Name "reviewer"
|
||||
$registerRow.reviewer_role = Get-FieldValue -Row $row -Name "reviewer_role"
|
||||
$registerRow.review_date = Get-FieldValue -Row $row -Name "review_date"
|
||||
$registerRow.review_result = Get-FieldValue -Row $row -Name "review_result"
|
||||
$registerRow.required_changes = Get-FieldValue -Row $row -Name "required_changes"
|
||||
$registerRow.follow_up_owner = Get-FieldValue -Row $row -Name "follow_up_owner"
|
||||
$registerRow.evidence_path = Get-FieldValue -Row $row -Name "evidence_path"
|
||||
$registerRow.status = Get-FieldValue -Row $row -Name "status"
|
||||
$registerRow.notes = Get-FieldValue -Row $row -Name "notes"
|
||||
}
|
||||
Export-Register -Rows $reviewRows -Path $reviewRegisterPath
|
||||
Add-Check -Key "write_guard" -Label "复核结果写入保护" -Status "pass" -Detail "已显式允许写入,复核登记表已更新" -Meta @{ register = $reviewRegisterPath }
|
||||
} else {
|
||||
Add-Check -Key "write_guard" -Label "复核结果写入保护" -Status "blocked" -Detail "存在阻塞项,拒绝写入复核登记表"
|
||||
}
|
||||
} else {
|
||||
Add-Check -Key "write_guard" -Label "复核结果写入保护" -Status "skip" -Required $false -Detail "默认 DryRun,不写入复核登记表;真实回填需显式传入 -AllowWrite"
|
||||
}
|
||||
|
||||
$applyPlan | Export-Csv -LiteralPath $applyPlanCsv -NoTypeInformation -Encoding UTF8
|
||||
$blockers | Export-Csv -LiteralPath $blockersCsv -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]@{
|
||||
result_rows = $resultRows.Count
|
||||
apply_plan_rows = $applyPlan.Count
|
||||
blockers = $blockers.Count
|
||||
pending_reviews = $pendingCount
|
||||
approved_for_review_import = $approvedForReviewCount
|
||||
approved_for_production = $approvedForProductionCount
|
||||
changes_required = $changesRequiredCount
|
||||
rejected = $rejectedCount
|
||||
allow_write = [bool]$AllowWrite
|
||||
}
|
||||
|
||||
$summary = [ordered]@{
|
||||
name = "phase13-review-result-apply"
|
||||
status = $overallStatus
|
||||
generated_at = (Get-Date).ToUniversalTime().ToString("o")
|
||||
branch = $branch
|
||||
commit = $commit
|
||||
remote = $remoteUrl
|
||||
library_root = $LibraryRoot
|
||||
review_results_path = $ReviewResultsPath
|
||||
checks = $checks.ToArray()
|
||||
metrics = $metrics
|
||||
artifacts = [ordered]@{
|
||||
summary_json = $summaryJson
|
||||
summary_md = $summaryMd
|
||||
apply_plan = $applyPlanCsv
|
||||
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
|
||||
|
||||
$md = New-Object System.Collections.Generic.List[string]
|
||||
$md.Add("# 阶段 13 复核结果回填门禁") | Out-Null
|
||||
$md.Add("") | Out-Null
|
||||
$md.Add("- 门禁状态:$(Convert-OverallStatusText -Status $overallStatus)($overallStatus)") | Out-Null
|
||||
$md.Add("- 生成时间:$($summary.generated_at)") | Out-Null
|
||||
$md.Add("- 分支:$branch") | Out-Null
|
||||
$md.Add("- 提交:$commit") | Out-Null
|
||||
$md.Add("- 复核结果文件:$ReviewResultsPath") | Out-Null
|
||||
$md.Add("- 写入模式:$([bool]$AllowWrite)") | 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.result_rows) |") | Out-Null
|
||||
$md.Add("| 回填计划行 | $($metrics.apply_plan_rows) |") | Out-Null
|
||||
$md.Add("| 阻塞项 | $($metrics.blockers) |") | Out-Null
|
||||
$md.Add("| 仍待复核 | $($metrics.pending_reviews) |") | Out-Null
|
||||
$md.Add("| 可进入试点复核库 | $($metrics.approved_for_review_import) |") | Out-Null
|
||||
$md.Add("| 可进入生产发布 | $($metrics.approved_for_production) |") | Out-Null
|
||||
$md.Add("| 需修改后复核 | $($metrics.changes_required) |") | Out-Null
|
||||
$md.Add("| 已拒绝 | $($metrics.rejected) |") | 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 { "否" }
|
||||
$md.Add("| $($check.label) | $requiredText | $(Convert-CheckStatusText -Status $check.status) | $($check.detail) |") | Out-Null
|
||||
}
|
||||
$md.Add("") | Out-Null
|
||||
$md.Add("## 当前结论") | Out-Null
|
||||
$md.Add("") | Out-Null
|
||||
if ($overallStatus -eq "BLOCKED") {
|
||||
$md.Add("复核结果回填被正确阻塞。当前结果中仍有 PENDING 或缺少真实复核证据的任务。") | Out-Null
|
||||
} elseif ($overallStatus -eq "PASS") {
|
||||
$md.Add("复核结果已通过回填门禁。若本次使用 DryRun,可在复核证据确认后显式使用 -AllowWrite 回填登记表。") | Out-Null
|
||||
} else {
|
||||
$md.Add("复核结果回填未完全通过,请查看阻塞清单。") | Out-Null
|
||||
}
|
||||
$md.Add("") | Out-Null
|
||||
$md.Add("本地产物:") | Out-Null
|
||||
$md.Add("") | Out-Null
|
||||
$md.Add("- phase13-review-result-apply-summary.json") | Out-Null
|
||||
$md.Add("- phase13-review-result-apply-summary.md") | Out-Null
|
||||
$md.Add("- review-result-apply-plan.csv") | Out-Null
|
||||
$md.Add("- review-result-apply-blockers.csv") | Out-Null
|
||||
$md | Set-Content -Encoding UTF8 -Path $summaryMd
|
||||
|
||||
Write-Host "PHASE13_REVIEW_RESULT_APPLY_$overallStatus $artifactDir"
|
||||
if ($overallStatus -eq "FAIL") {
|
||||
throw "阶段 13 复核结果回填门禁未通过。查看:$summaryMd"
|
||||
}
|
||||
if ($overallStatus -eq "BLOCKED" -and $FailOnBlocked) {
|
||||
throw "阶段 13 复核结果回填门禁被阻塞。查看:$summaryMd"
|
||||
}
|
||||
@@ -285,6 +285,14 @@ function Test-SecretLeak {
|
||||
if (Test-Path -LiteralPath $reviewExecutionScriptPath -PathType Leaf) {
|
||||
$files += Get-Item -LiteralPath $reviewExecutionScriptPath
|
||||
}
|
||||
$firstWaveReviewScriptPath = Join-Path $repoRoot "scripts\run-phase13-first-wave-review-readiness.ps1"
|
||||
if (Test-Path -LiteralPath $firstWaveReviewScriptPath -PathType Leaf) {
|
||||
$files += Get-Item -LiteralPath $firstWaveReviewScriptPath
|
||||
}
|
||||
$reviewResultApplyScriptPath = Join-Path $repoRoot "scripts\apply-phase13-review-results.ps1"
|
||||
if (Test-Path -LiteralPath $reviewResultApplyScriptPath -PathType Leaf) {
|
||||
$files += Get-Item -LiteralPath $reviewResultApplyScriptPath
|
||||
}
|
||||
foreach ($file in $files) {
|
||||
$match = Select-String -LiteralPath $file.FullName -Pattern $adminPasswordLiteral -List -ErrorAction SilentlyContinue
|
||||
if ($match) {
|
||||
@@ -356,7 +364,9 @@ $commonDocFiles = @(
|
||||
"scripts\run-phase13-golden-eval.ps1",
|
||||
"scripts\run-phase13-ingestion-closed-loop.ps1",
|
||||
"scripts\run-phase13-production-pilot-release.ps1",
|
||||
"scripts\run-phase13-review-execution-gate.ps1"
|
||||
"scripts\run-phase13-review-execution-gate.ps1",
|
||||
"scripts\run-phase13-first-wave-review-readiness.ps1",
|
||||
"scripts\apply-phase13-review-results.ps1"
|
||||
)
|
||||
foreach ($docPath in $commonDocFiles) {
|
||||
$absoluteDocPath = Join-Path $repoRoot $docPath
|
||||
|
||||
@@ -0,0 +1,660 @@
|
||||
param(
|
||||
[string]$LibraryRoot = "",
|
||||
[string]$ArtifactRoot = "",
|
||||
[ValidateSet("P0", "FirstWave", "All")]
|
||||
[string]$Scope = "P0",
|
||||
[string]$LegalReviewer = "",
|
||||
[string]$BusinessReviewer = "",
|
||||
[string]$DueDate = "",
|
||||
[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-first-wave-review-readiness"
|
||||
}
|
||||
if (-not [System.IO.Path]::IsPathRooted($ArtifactRoot)) {
|
||||
$ArtifactRoot = Join-Path $repoRoot $ArtifactRoot
|
||||
}
|
||||
|
||||
$artifactDir = Join-Path $ArtifactRoot $timestamp
|
||||
$formsDir = Join-Path $artifactDir "review-forms"
|
||||
New-Item -ItemType Directory -Force -Path $formsDir | Out-Null
|
||||
|
||||
$summaryJson = Join-Path $artifactDir "phase13-first-wave-review-readiness-summary.json"
|
||||
$summaryMd = Join-Path $artifactDir "phase13-first-wave-review-readiness-summary.md"
|
||||
$assignmentCsv = Join-Path $artifactDir "review-assignment-template.csv"
|
||||
$resultImportCsv = Join-Path $artifactDir "review-result-import-template.csv"
|
||||
$selectedTasksCsv = Join-Path $artifactDir "selected-review-tasks.csv"
|
||||
$checklistMd = Join-Path $artifactDir "first-wave-review-checklist.md"
|
||||
$gitStatusPath = Join-Path $artifactDir "git-status.txt"
|
||||
|
||||
$checks = New-Object System.Collections.Generic.List[object]
|
||||
$selectedTasks = New-Object System.Collections.Generic.List[object]
|
||||
$assignmentRows = New-Object System.Collections.Generic.List[object]
|
||||
$resultRows = 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 Get-FieldValue {
|
||||
param([object]$Row, [string]$Name)
|
||||
if ($null -eq $Row) {
|
||||
return ""
|
||||
}
|
||||
$property = $Row.PSObject.Properties[$Name]
|
||||
if ($null -eq $property -or $null -eq $property.Value) {
|
||||
return ""
|
||||
}
|
||||
return [string]$property.Value
|
||||
}
|
||||
|
||||
function Test-PendingReviewer {
|
||||
param([string]$Value)
|
||||
if ([string]::IsNullOrWhiteSpace($Value)) {
|
||||
return $true
|
||||
}
|
||||
return @("pending", "待法务复核", "待业务复核", "待法务和业务复核", "待复核") -contains $Value.Trim()
|
||||
}
|
||||
|
||||
function Test-RequiredFlag {
|
||||
param([string]$Value)
|
||||
if ([string]::IsNullOrWhiteSpace($Value)) {
|
||||
return $false
|
||||
}
|
||||
return @("true", "yes", "1", "required") -contains $Value.ToLowerInvariant()
|
||||
}
|
||||
|
||||
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-OverallStatusText {
|
||||
param([string]$Status)
|
||||
switch ($Status) {
|
||||
"PASS" { return "通过" }
|
||||
"WARN" { return "通过但有警告" }
|
||||
"FAIL" { return "未通过" }
|
||||
"BLOCKED" { 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 = "",
|
||||
[string]$PrimaryPath = "",
|
||||
[string]$SecondaryPath = ""
|
||||
)
|
||||
|
||||
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
|
||||
primary_path = $PrimaryPath
|
||||
secondary_path = $SecondaryPath
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ProposedReviewerValue {
|
||||
param([string]$Lane, [string]$CurrentReviewer)
|
||||
if ($Lane -eq "法务+业务" -and -not [string]::IsNullOrWhiteSpace($LegalReviewer) -and -not [string]::IsNullOrWhiteSpace($BusinessReviewer)) {
|
||||
return "$LegalReviewer;$BusinessReviewer"
|
||||
}
|
||||
if ($Lane -eq "法务" -and -not [string]::IsNullOrWhiteSpace($LegalReviewer)) {
|
||||
return $LegalReviewer
|
||||
}
|
||||
return $CurrentReviewer
|
||||
}
|
||||
|
||||
function Write-ReviewForm {
|
||||
param([object]$Task, [string]$Path)
|
||||
$lines = New-Object System.Collections.Generic.List[string]
|
||||
$lines.Add("# 阶段 13 首批复核表:$($Task.review_id)") | Out-Null
|
||||
$lines.Add("") | Out-Null
|
||||
$lines.Add("## 复核对象") | Out-Null
|
||||
$lines.Add("") | Out-Null
|
||||
$lines.Add("| 字段 | 值 |") | Out-Null
|
||||
$lines.Add("|---|---|") | Out-Null
|
||||
$lines.Add("| 任务编号 | `$($Task.review_id)` |") | Out-Null
|
||||
$lines.Add("| 资产类型 | $($Task.asset_type_text) |") | Out-Null
|
||||
$lines.Add("| 资产编号 | `$($Task.asset_id)` |") | Out-Null
|
||||
$lines.Add("| 资产名称 | $($Task.asset_name) |") | Out-Null
|
||||
$lines.Add("| 优先级 | $($Task.priority) |") | Out-Null
|
||||
$lines.Add("| 复核通道 | $($Task.review_lane) |") | Out-Null
|
||||
$lines.Add("| 法律依据 | `$($Task.source_ids)` |") | Out-Null
|
||||
$lines.Add("| 主文件 | `$($Task.primary_path)` |") | Out-Null
|
||||
$lines.Add("| 辅助文件 | `$($Task.secondary_path)` |") | Out-Null
|
||||
$lines.Add("") | Out-Null
|
||||
$lines.Add("## 法务复核") | Out-Null
|
||||
$lines.Add("") | Out-Null
|
||||
$lines.Add("1. 法源编号、条款引用、适用范围和风险提示是否准确。") | Out-Null
|
||||
$lines.Add("2. 结论是否存在脱离中国现行法律依据的表述。") | Out-Null
|
||||
$lines.Add("3. 高风险事项是否保留人工复核或专项复核提示。") | Out-Null
|
||||
$lines.Add("4. 不得在模板或样本中固化未经业务确认的金额、比例、机构、地点、期限或权属安排。") | Out-Null
|
||||
$lines.Add("") | Out-Null
|
||||
$lines.Add("## 业务复核") | Out-Null
|
||||
$lines.Add("") | Out-Null
|
||||
$lines.Add("1. 变量、默认值、枚举值和必填项是否符合真实业务。") | Out-Null
|
||||
$lines.Add("2. 付款、交付、验收、服务级别、数据范围、责任上限和知识产权安排是否需要业务确认。") | Out-Null
|
||||
$lines.Add("3. 模板或样本是否适合试点业务使用,是否需要按部门、产品线或合同类型拆分版本。") | Out-Null
|
||||
$lines.Add("") | Out-Null
|
||||
$lines.Add("## 复核结论填写区") | Out-Null
|
||||
$lines.Add("") | Out-Null
|
||||
$lines.Add("| 字段 | 填写 |") | Out-Null
|
||||
$lines.Add("|---|---|") | Out-Null
|
||||
$lines.Add("| 复核人 | |") | Out-Null
|
||||
$lines.Add("| 复核日期 | |") | Out-Null
|
||||
$lines.Add("| 复核结果 | PENDING / CHANGES_REQUIRED / APPROVED_FOR_REVIEW_IMPORT / APPROVED_FOR_PRODUCTION / REJECTED |") | Out-Null
|
||||
$lines.Add("| 修改要求 | |") | Out-Null
|
||||
$lines.Add("| 是否允许进入试点复核库 | 是 / 否 |") | Out-Null
|
||||
$lines.Add("| 是否允许进入生产发布 | 是 / 否 |") | Out-Null
|
||||
$lines.Add("| 复核依据和说明 | |") | Out-Null
|
||||
$lines | Set-Content -Encoding UTF8 -Path $Path
|
||||
}
|
||||
|
||||
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"
|
||||
$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 (Join-Path $inventoryRoot "expert-review-register.csv")
|
||||
|
||||
$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 -PrimaryPath $row.retrieval_hint
|
||||
}
|
||||
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 -PrimaryPath $row.summary_path
|
||||
}
|
||||
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 -PrimaryPath $row.modification_suggestion_path -SecondaryPath $row.review_sample_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 -PrimaryPath $row.clause_path -SecondaryPath $row.risk_notes_path
|
||||
}
|
||||
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 $row.clause_ids -PrimaryPath $row.template_path -SecondaryPath $row.variable_schema_path
|
||||
}
|
||||
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 "" -Severity $row.expected_severity -SourceIds $row.source_ids -PrimaryPath $row.input_sample_path -SecondaryPath $row.expected_findings_path
|
||||
}
|
||||
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 -PrimaryPath $row.variable_payload_path -SecondaryPath $row.expected_document_path
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
foreach ($row in $reviewRows) {
|
||||
$assetKey = "$($row.asset_type):$($row.asset_id)"
|
||||
if (-not $assetMap.ContainsKey($assetKey)) {
|
||||
continue
|
||||
}
|
||||
$asset = $assetMap[$assetKey]
|
||||
$priority = Get-ReviewPriority -AssetType $row.asset_type -Asset $asset
|
||||
$lane = Get-ReviewLane -AssetType $row.asset_type -ReviewerRole $row.reviewer_role
|
||||
$include = $false
|
||||
if ($Scope -eq "All") {
|
||||
$include = $true
|
||||
} elseif ($Scope -eq "FirstWave") {
|
||||
$include = @("contract_template", "clause", "review_rule", "generation_golden_sample") -contains $row.asset_type
|
||||
} elseif ($Scope -eq "P0") {
|
||||
$include = ($priority -eq "P0")
|
||||
}
|
||||
if (-not $include) {
|
||||
continue
|
||||
}
|
||||
|
||||
$formRelativePath = "review-forms/$($row.review_id).md"
|
||||
$formPath = Join-Path $artifactDir ($formRelativePath -replace "/", "\")
|
||||
$proposedReviewer = Get-ProposedReviewerValue -Lane $lane -CurrentReviewer $row.reviewer
|
||||
$requiresBusiness = ($lane -eq "法务+业务")
|
||||
|
||||
$task = [pscustomobject][ordered]@{
|
||||
priority = $priority
|
||||
review_lane = $lane
|
||||
review_id = $row.review_id
|
||||
asset_type = $row.asset_type
|
||||
asset_type_text = Get-AssetTypeText -AssetType $row.asset_type
|
||||
asset_id = $row.asset_id
|
||||
asset_name = $asset.asset_name
|
||||
asset_status = $asset.status
|
||||
production_ready = $asset.production_ready
|
||||
source_ids = $asset.source_ids
|
||||
severity = $asset.severity
|
||||
primary_path = $asset.primary_path
|
||||
primary_path_exists = if ([string]::IsNullOrWhiteSpace($asset.primary_path)) { $false } else { Test-LibraryFilePath -RelativePath $asset.primary_path }
|
||||
secondary_path = $asset.secondary_path
|
||||
current_reviewer = $row.reviewer
|
||||
proposed_reviewer = $proposedReviewer
|
||||
proposed_legal_reviewer = $LegalReviewer
|
||||
proposed_business_reviewer = if ($requiresBusiness) { $BusinessReviewer } else { "" }
|
||||
due_date = $DueDate
|
||||
review_result = $row.review_result
|
||||
review_status = $row.status
|
||||
required_changes = $row.required_changes
|
||||
follow_up_owner = $row.follow_up_owner
|
||||
evidence_path = $row.evidence_path
|
||||
evidence_form_path = $formRelativePath
|
||||
notes = $row.notes
|
||||
}
|
||||
|
||||
$selectedTasks.Add($task) | Out-Null
|
||||
Write-ReviewForm -Task $task -Path $formPath
|
||||
|
||||
$assignmentRows.Add([pscustomobject][ordered]@{
|
||||
priority = $task.priority
|
||||
review_lane = $task.review_lane
|
||||
review_id = $task.review_id
|
||||
asset_type = $task.asset_type
|
||||
asset_type_text = $task.asset_type_text
|
||||
asset_id = $task.asset_id
|
||||
asset_name = $task.asset_name
|
||||
current_reviewer = $task.current_reviewer
|
||||
proposed_legal_reviewer = $task.proposed_legal_reviewer
|
||||
proposed_business_reviewer = $task.proposed_business_reviewer
|
||||
due_date = $task.due_date
|
||||
review_result = $task.review_result
|
||||
status = $task.review_status
|
||||
required_changes = $task.required_changes
|
||||
follow_up_owner = $task.follow_up_owner
|
||||
evidence_form_path = $task.evidence_form_path
|
||||
source_ids = $task.source_ids
|
||||
primary_path = $task.primary_path
|
||||
secondary_path = $task.secondary_path
|
||||
notes = $task.notes
|
||||
}) | Out-Null
|
||||
|
||||
$resultRows.Add([pscustomobject][ordered]@{
|
||||
review_id = $task.review_id
|
||||
asset_type = $task.asset_type
|
||||
asset_id = $task.asset_id
|
||||
reviewer = $task.proposed_reviewer
|
||||
reviewer_role = $task.review_lane
|
||||
review_date = ""
|
||||
review_result = "PENDING"
|
||||
status = "PENDING"
|
||||
required_changes = ""
|
||||
follow_up_owner = $task.follow_up_owner
|
||||
evidence_path = $task.evidence_form_path
|
||||
asset_status_after_review = $task.asset_status
|
||||
production_ready_after_review = $task.production_ready
|
||||
notes = "由真实法务或业务复核后填写;未复核不得改为 APPROVED 或 production_ready=true"
|
||||
}) | Out-Null
|
||||
}
|
||||
|
||||
$selectedTasks |
|
||||
Sort-Object priority, review_lane, asset_type, asset_id |
|
||||
Export-Csv -LiteralPath $selectedTasksCsv -NoTypeInformation -Encoding UTF8
|
||||
$assignmentRows |
|
||||
Sort-Object priority, review_lane, asset_type, asset_id |
|
||||
Export-Csv -LiteralPath $assignmentCsv -NoTypeInformation -Encoding UTF8
|
||||
$resultRows |
|
||||
Sort-Object review_id |
|
||||
Export-Csv -LiteralPath $resultImportCsv -NoTypeInformation -Encoding UTF8
|
||||
|
||||
$missingTaskPaths = @($selectedTasks | Where-Object { -not $_.primary_path_exists } | ForEach-Object { $_.review_id })
|
||||
$missingLegalReviewer = @($selectedTasks | Where-Object { [string]::IsNullOrWhiteSpace($LegalReviewer) -or (Test-PendingReviewer -Value $LegalReviewer) } | ForEach-Object { $_.review_id })
|
||||
$missingBusinessReviewer = @($selectedTasks | Where-Object { $_.review_lane -eq "法务+业务" -and ([string]::IsNullOrWhiteSpace($BusinessReviewer) -or (Test-PendingReviewer -Value $BusinessReviewer)) } | ForEach-Object { $_.review_id })
|
||||
$missingDueDate = [string]::IsNullOrWhiteSpace($DueDate)
|
||||
$invalidDueDate = $false
|
||||
if (-not $missingDueDate) {
|
||||
$parsedDueDate = [datetime]::MinValue
|
||||
$invalidDueDate = -not [datetime]::TryParse($DueDate, [ref]$parsedDueDate)
|
||||
}
|
||||
|
||||
if ($selectedTasks.Count -gt 0) {
|
||||
Add-Check -Key "selected_tasks" -Label "首批复核任务选择" -Status "pass" -Detail "已选择 $($selectedTasks.Count) 个 $Scope 复核任务"
|
||||
} else {
|
||||
Add-Check -Key "selected_tasks" -Label "首批复核任务选择" -Status "fail" -Detail "未选择到可复核任务"
|
||||
}
|
||||
if ($missingTaskPaths.Count -eq 0) {
|
||||
Add-Check -Key "asset_file_paths" -Label "复核资产文件路径" -Status "pass" -Detail "所选复核任务均有可追溯主文件"
|
||||
} else {
|
||||
Add-Check -Key "asset_file_paths" -Label "复核资产文件路径" -Status "fail" -Detail "以下复核任务主文件缺失或不可验证:$($missingTaskPaths -join ',')" -Meta @{ rows = $missingTaskPaths }
|
||||
}
|
||||
if ($missingLegalReviewer.Count -eq 0) {
|
||||
Add-Check -Key "legal_reviewer_assignment" -Label "法务复核人分配" -Status "pass" -Detail "首批复核任务已分配真实法务复核人"
|
||||
} else {
|
||||
Add-Check -Key "legal_reviewer_assignment" -Label "法务复核人分配" -Status "blocked" -Detail "缺少真实法务复核人:$($missingLegalReviewer.Count) 个任务" -Meta @{ rows = $missingLegalReviewer }
|
||||
}
|
||||
if ($missingBusinessReviewer.Count -eq 0) {
|
||||
Add-Check -Key "business_reviewer_assignment" -Label "业务复核人分配" -Status "pass" -Detail "需要业务复核的任务已分配真实业务复核人"
|
||||
} else {
|
||||
Add-Check -Key "business_reviewer_assignment" -Label "业务复核人分配" -Status "blocked" -Detail "缺少真实业务复核人:$($missingBusinessReviewer.Count) 个任务" -Meta @{ rows = $missingBusinessReviewer }
|
||||
}
|
||||
if (-not $missingDueDate -and -not $invalidDueDate) {
|
||||
Add-Check -Key "review_due_date" -Label "复核截止日期" -Status "pass" -Detail "首批复核截止日期=$DueDate"
|
||||
} elseif ($invalidDueDate) {
|
||||
Add-Check -Key "review_due_date" -Label "复核截止日期" -Status "fail" -Detail "复核截止日期无法解析:$DueDate"
|
||||
} else {
|
||||
Add-Check -Key "review_due_date" -Label "复核截止日期" -Status "blocked" -Detail "未设置首批复核截止日期"
|
||||
}
|
||||
|
||||
$p0Tasks = @($selectedTasks | Where-Object { $_.priority -eq "P0" })
|
||||
$templateTasks = @($selectedTasks | Where-Object { $_.asset_type -eq "contract_template" })
|
||||
$templateSampleTasks = @($selectedTasks | Where-Object { $_.asset_type -eq "generation_golden_sample" -and $_.asset_id -match "GEN-SAMPLE-TPL-" })
|
||||
$clauseTasks = @($selectedTasks | Where-Object { $_.asset_type -eq "clause" })
|
||||
$highRiskRuleTasks = @($selectedTasks | Where-Object { $_.asset_type -eq "review_rule" -and $_.severity -eq "high" })
|
||||
$legalBusinessTasks = @($selectedTasks | Where-Object { $_.review_lane -eq "法务+业务" })
|
||||
$legalOnlyTasks = @($selectedTasks | Where-Object { $_.review_lane -eq "法务" })
|
||||
|
||||
$checklist = New-Object System.Collections.Generic.List[string]
|
||||
$checklist.Add("# 阶段 13 首批 P0 复核包") | Out-Null
|
||||
$checklist.Add("") | Out-Null
|
||||
$checklist.Add("本复核包由 scripts/run-phase13-first-wave-review-readiness.ps1 生成,用于把首批 P0 资料交给真实法务和业务负责人复核。") | Out-Null
|
||||
$checklist.Add("") | Out-Null
|
||||
$checklist.Add("## 使用顺序") | Out-Null
|
||||
$checklist.Add("") | Out-Null
|
||||
$checklist.Add("1. 在 review-assignment-template.csv 中确认复核人和截止日期。") | Out-Null
|
||||
$checklist.Add("2. 按 review-forms/ 中的单项复核表完成法务和业务意见。") | Out-Null
|
||||
$checklist.Add("3. 在 review-result-import-template.csv 中填写复核日期、复核结果、修改要求和证据路径。") | Out-Null
|
||||
$checklist.Add("4. 只有真实复核完成后,才允许把相应资产推进到 APPROVED 或 production_ready=true。") | Out-Null
|
||||
$checklist.Add("5. 回填后重新运行复核执行门禁和生产试点发布门禁。") | Out-Null
|
||||
$checklist.Add("") | Out-Null
|
||||
$checklist.Add("## 自动化复核命令") | Out-Null
|
||||
$checklist.Add("") | Out-Null
|
||||
$checklist.Add('```powershell') | Out-Null
|
||||
$checklist.Add("powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-phase13-first-wave-review-readiness.ps1 -LegalReviewer <法务姓名> -BusinessReviewer <业务姓名> -DueDate <YYYY-MM-DD>") | Out-Null
|
||||
$checklist.Add("powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-phase13-review-execution-gate.ps1") | Out-Null
|
||||
$checklist.Add("powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-phase13-production-pilot-release.ps1") | Out-Null
|
||||
$checklist.Add('```') | Out-Null
|
||||
$checklist.Add("") | Out-Null
|
||||
$checklist.Add("## 首批范围") | Out-Null
|
||||
$checklist.Add("") | Out-Null
|
||||
$checklist.Add("| 类型 | 数量 |") | Out-Null
|
||||
$checklist.Add("|---|---:|") | Out-Null
|
||||
$checklist.Add("| P0 任务 | $($p0Tasks.Count) |") | Out-Null
|
||||
$checklist.Add("| 合同模板 | $($templateTasks.Count) |") | Out-Null
|
||||
$checklist.Add("| 模板生成样本 | $($templateSampleTasks.Count) |") | Out-Null
|
||||
$checklist.Add("| 标准条款 | $($clauseTasks.Count) |") | Out-Null
|
||||
$checklist.Add("| 高风险审核规则 | $($highRiskRuleTasks.Count) |") | Out-Null
|
||||
$checklist | Set-Content -Encoding UTF8 -Path $checklistMd
|
||||
|
||||
$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]@{
|
||||
scope = $Scope
|
||||
selected_tasks = $selectedTasks.Count
|
||||
p0_tasks = $p0Tasks.Count
|
||||
template_tasks = $templateTasks.Count
|
||||
template_generation_sample_tasks = $templateSampleTasks.Count
|
||||
clause_tasks = $clauseTasks.Count
|
||||
high_risk_review_rule_tasks = $highRiskRuleTasks.Count
|
||||
legal_only_tasks = $legalOnlyTasks.Count
|
||||
legal_business_tasks = $legalBusinessTasks.Count
|
||||
missing_legal_reviewer_tasks = $missingLegalReviewer.Count
|
||||
missing_business_reviewer_tasks = $missingBusinessReviewer.Count
|
||||
due_date_missing = $missingDueDate
|
||||
due_date_invalid = $invalidDueDate
|
||||
}
|
||||
|
||||
$summary = [ordered]@{
|
||||
name = "phase13-first-wave-review-readiness"
|
||||
status = $overallStatus
|
||||
generated_at = (Get-Date).ToUniversalTime().ToString("o")
|
||||
branch = $branch
|
||||
commit = $commit
|
||||
remote = $remoteUrl
|
||||
library_root = $LibraryRoot
|
||||
scope = $Scope
|
||||
checks = $checks.ToArray()
|
||||
metrics = $metrics
|
||||
artifacts = [ordered]@{
|
||||
summary_json = $summaryJson
|
||||
summary_md = $summaryMd
|
||||
assignment_template = $assignmentCsv
|
||||
result_import_template = $resultImportCsv
|
||||
selected_tasks = $selectedTasksCsv
|
||||
checklist = $checklistMd
|
||||
review_forms_dir = $formsDir
|
||||
git_status = $gitStatusPath
|
||||
}
|
||||
totals = [ordered]@{
|
||||
total_checks = $checks.Count
|
||||
failed_required = $failedRequired
|
||||
blocked_required = $blockedRequired
|
||||
warnings = $warnings
|
||||
}
|
||||
}
|
||||
Save-Json -Path $summaryJson -Value $summary
|
||||
|
||||
$md = New-Object System.Collections.Generic.List[string]
|
||||
$md.Add("# 阶段 13 首批复核就绪门禁") | Out-Null
|
||||
$md.Add("") | Out-Null
|
||||
$md.Add("- 门禁状态:$(Convert-OverallStatusText -Status $overallStatus)($overallStatus)") | Out-Null
|
||||
$md.Add("- 生成时间:$($summary.generated_at)") | Out-Null
|
||||
$md.Add("- 分支:$branch") | Out-Null
|
||||
$md.Add("- 提交:$commit") | Out-Null
|
||||
$md.Add("- 复核范围:$Scope") | 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.selected_tasks) |") | Out-Null
|
||||
$md.Add("| P0 任务 | $($metrics.p0_tasks) |") | Out-Null
|
||||
$md.Add("| 合同模板 | $($metrics.template_tasks) |") | Out-Null
|
||||
$md.Add("| 模板生成样本 | $($metrics.template_generation_sample_tasks) |") | Out-Null
|
||||
$md.Add("| 标准条款 | $($metrics.clause_tasks) |") | Out-Null
|
||||
$md.Add("| 高风险审核规则 | $($metrics.high_risk_review_rule_tasks) |") | Out-Null
|
||||
$md.Add("| 法务通道任务 | $($metrics.legal_only_tasks) |") | Out-Null
|
||||
$md.Add("| 法务+业务通道任务 | $($metrics.legal_business_tasks) |") | Out-Null
|
||||
$md.Add("| 缺少真实法务复核人任务 | $($metrics.missing_legal_reviewer_tasks) |") | Out-Null
|
||||
$md.Add("| 缺少真实业务复核人任务 | $($metrics.missing_business_reviewer_tasks) |") | 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 { "否" }
|
||||
$md.Add("| $($check.label) | $requiredText | $(Convert-CheckStatusText -Status $check.status) | $($check.detail) |") | Out-Null
|
||||
}
|
||||
$md.Add("") | Out-Null
|
||||
$md.Add("## 当前结论") | Out-Null
|
||||
$md.Add("") | Out-Null
|
||||
if ($overallStatus -eq "BLOCKED") {
|
||||
$md.Add("首批复核包已生成,但尚未分配真实法务/业务复核人或截止日期,不能视为复核执行完成。") | Out-Null
|
||||
} elseif ($overallStatus -eq "PASS") {
|
||||
$md.Add("首批复核包已具备执行条件,可以交给真实法务和业务负责人进行复核。") | Out-Null
|
||||
} else {
|
||||
$md.Add("首批复核包未完全就绪,请查看检查项和产物。") | Out-Null
|
||||
}
|
||||
$md.Add("") | Out-Null
|
||||
$md.Add("本地产物:") | Out-Null
|
||||
$md.Add("") | Out-Null
|
||||
$md.Add("- phase13-first-wave-review-readiness-summary.json") | Out-Null
|
||||
$md.Add("- phase13-first-wave-review-readiness-summary.md") | Out-Null
|
||||
$md.Add("- review-assignment-template.csv") | Out-Null
|
||||
$md.Add("- review-result-import-template.csv") | Out-Null
|
||||
$md.Add("- selected-review-tasks.csv") | Out-Null
|
||||
$md.Add("- first-wave-review-checklist.md") | Out-Null
|
||||
$md.Add("- review-forms/") | Out-Null
|
||||
$md | Set-Content -Encoding UTF8 -Path $summaryMd
|
||||
|
||||
Write-Host "PHASE13_FIRST_WAVE_REVIEW_READINESS_$overallStatus $artifactDir"
|
||||
if ($overallStatus -eq "FAIL") {
|
||||
throw "阶段 13 首批复核就绪门禁未通过。查看:$summaryMd"
|
||||
}
|
||||
if ($overallStatus -eq "BLOCKED" -and $FailOnBlocked) {
|
||||
throw "阶段 13 首批复核就绪门禁被阻塞。查看:$summaryMd"
|
||||
}
|
||||
Reference in New Issue
Block a user