- value-scan: read-only breadth inventory of mechanisms in delivered code, stopping at the human selection gate - value-dig: depth write-up of chosen points (feature list, design review, refactor plan) with templates and mechanical checkers - Add skill-workbench design doc for the pair
269 lines
14 KiB
PowerShell
269 lines
14 KiB
PowerShell
<#
|
||
value-scan / value-dig 产物机械校验(替代人工核对)
|
||
------------------------------------------------------------------
|
||
用法:
|
||
# S1 候选清单
|
||
powershell -ExecutionPolicy Bypass -File check.ps1 -Mode scan `
|
||
-Product <产物路径> -Template <skills>/value-scan/assets/候选清单模板.md
|
||
|
||
# S4 功能点清单
|
||
powershell -ExecutionPolicy Bypass -File check.ps1 -Mode dig `
|
||
-Product <产物路径> -Template <skills>/value-dig/assets/深度模板/功能点清单模板.md
|
||
|
||
# S4 设计思路与取舍 / 改造方案(整篇级文档,只跑通用校验)
|
||
powershell -ExecutionPolicy Bypass -File check.ps1 -Mode doc -Product <path> -Template <path>
|
||
|
||
退出码:0 = 无 FAIL;1 = 有 FAIL
|
||
说明:FAIL = 必错;WARN = 需人判断
|
||
#>
|
||
param(
|
||
[Parameter(Mandatory = $true)]
|
||
[ValidateSet('scan', 'dig', 'doc')]
|
||
[string]$Mode,
|
||
|
||
[Parameter(Mandatory = $true)]
|
||
[string]$Product,
|
||
|
||
[string]$Template
|
||
)
|
||
|
||
$ErrorActionPreference = 'Stop'
|
||
|
||
$script:fail = New-Object System.Collections.Generic.List[string]
|
||
$script:warn = New-Object System.Collections.Generic.List[string]
|
||
$script:pass = New-Object System.Collections.Generic.List[string]
|
||
function Fail($m) { $script:fail.Add($m) }
|
||
function Warn($m) { $script:warn.Add($m) }
|
||
function Pass($m) { $script:pass.Add($m) }
|
||
|
||
$prodPath = (Resolve-Path -LiteralPath $Product).Path
|
||
$text = [System.IO.File]::ReadAllText($prodPath, [System.Text.Encoding]::UTF8)
|
||
if ($text.Length -gt 0 -and [int][char]$text[0] -eq 0xFEFF) { $text = $text.Substring(1) }
|
||
$lines = $text -split "`r?`n"
|
||
|
||
# ---------------------------------------------------------------- 围栏代码块
|
||
$fences = @()
|
||
$inFence = $false; $fStart = 0; $fLang = ''
|
||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||
if ($lines[$i] -match '^\s*```') {
|
||
if (-not $inFence) {
|
||
$inFence = $true; $fStart = $i; $fLang = ($lines[$i] -replace '^\s*```', '').Trim()
|
||
}
|
||
else {
|
||
$inFence = $false
|
||
$fences += [pscustomobject]@{ Start = $fStart; End = $i; Lang = $fLang; Body = ($i - $fStart - 1) }
|
||
}
|
||
}
|
||
}
|
||
if ($inFence) { Fail "存在未闭合的代码围栏(起始行 $($fStart + 1))" }
|
||
|
||
# ---------------------------------------------------------------- 通用 1:引号逐行配对
|
||
$oddQuoteLines = @()
|
||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||
if ((([regex]::Matches($lines[$i], '"')).Count % 2) -ne 0) { $oddQuoteLines += ($i + 1) }
|
||
}
|
||
if ($oddQuoteLines.Count -eq 0) { Pass '引号逐行配对' }
|
||
else { Fail ("引号未配对的行: " + ($oddQuoteLines -join ', ')) }
|
||
|
||
# ---------------------------------------------------------------- 通用 2:mermaid 合法性
|
||
$mermaidFences = @($fences | Where-Object { $_.Lang -eq 'mermaid' })
|
||
$mmBad = 0
|
||
foreach ($f in $mermaidFences) {
|
||
$body = ($lines[($f.Start + 1)..($f.End - 1)] -join "`n")
|
||
$firstLine = (($body -split "`r?`n") | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1)
|
||
$endCount = ([regex]::Matches($body, '(?m)^\s*end\s*$')).Count
|
||
if ($firstLine -match 'sequenceDiagram') {
|
||
# 时序图:end 收的是 alt/opt/loop/par/critical/break/rect
|
||
$blk = ([regex]::Matches($body, '(?m)^\s*(alt|opt|loop|par|critical|break|rect)\b')).Count
|
||
if ($blk -ne $endCount) { Fail "mermaid 时序图(第 $($f.Start + 1) 行起)alt/opt/loop 等 $blk 个但 end=$endCount"; $mmBad++ }
|
||
}
|
||
else {
|
||
$sg = ([regex]::Matches($body, '(?m)^\s*subgraph\s')).Count
|
||
if ($sg -ne $endCount) { Fail "mermaid 流程图(第 $($f.Start + 1) 行起)subgraph=$sg 但 end=$endCount"; $mmBad++ }
|
||
if ($body -match '(?m)^\s*subgraph\s+\S+\s*\[' -and $body -notmatch '(?m)^\s*subgraph\s+\S+\s*\["') {
|
||
Fail "mermaid(第 $($f.Start + 1) 行起)subgraph 缺引号标题,必须写 subgraph id[`"标题`"]"; $mmBad++
|
||
}
|
||
if ($body -match '(?m)^\s*subgraph\s+\S+\s*\[\(') {
|
||
Fail "mermaid(第 $($f.Start + 1) 行起)用了 subgraph xxx[(...)],会解析失败"; $mmBad++
|
||
}
|
||
}
|
||
}
|
||
if ($mermaidFences.Count -gt 0 -and $mmBad -eq 0) { Pass "mermaid 块 $($mermaidFences.Count) 个通过" }
|
||
|
||
# ---------------------------------------------------------------- 通用 3:表格列数一致
|
||
$ti = 0
|
||
while ($ti -lt $lines.Count) {
|
||
if ($lines[$ti] -match '^\s*\|') {
|
||
$blk = @(); $bs = $ti
|
||
while ($ti -lt $lines.Count -and $lines[$ti] -match '^\s*\|') { $blk += $lines[$ti]; $ti++ }
|
||
$counts = @($blk | ForEach-Object { ([regex]::Matches($_, '\|')).Count } | Sort-Object -Unique)
|
||
if ($counts.Count -gt 1) { Fail "表格列数不一致(第 $($bs + 1) 行起):pipe 数 = $($counts -join '/')" }
|
||
}
|
||
else { $ti++ }
|
||
}
|
||
|
||
# ---------------------------------------------------------------- 通用 3b:表格不得缩进(嵌套在列表内的表多数渲染器不显示)
|
||
$indentedTable = @()
|
||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||
if ($lines[$i] -match '^[ \t]+\|') { $indentedTable += ($i + 1) }
|
||
}
|
||
if ($indentedTable.Count -eq 0) { Pass '表格均顶格(无嵌套缩进表)' }
|
||
else { Fail ('表格存在缩进(第 ' + ($indentedTable -join ', ') + ' 行起)——嵌套在列表里的表多数渲染器不显示,必须顶格') }
|
||
|
||
# ---------------------------------------------------------------- 通用 4:章节完整性(模板必备节 ⊆ 产出节)
|
||
if ($Template) {
|
||
$tPath = (Resolve-Path -LiteralPath $Template).Path
|
||
$tText = [System.IO.File]::ReadAllText($tPath, [System.Text.Encoding]::UTF8)
|
||
if ($tText.Length -gt 0 -and [int][char]$tText[0] -eq 0xFEFF) { $tText = $tText.Substring(1) }
|
||
$tLines = $tText -split "`r?`n"
|
||
$tHeads = $tLines | Where-Object { $_ -match '^\s*#+\s+\S' }
|
||
$missingLiteral = @(); $missingPlaceholder = @()
|
||
foreach ($h in $tHeads) {
|
||
$t = ($h -replace '^\s*#+\s+', '') -replace '\s+$', ''
|
||
$hasPh = $t.Contains([string][char]0x27E8)
|
||
$sentinel = '@@PH@@'
|
||
$phRx = [string][char]0x27E8 + '[^' + [string][char]0x27E9 + ']*' + [string][char]0x27E9
|
||
$rx = [regex]::Escape([regex]::Replace($t, $phRx, $sentinel)).Replace($sentinel, '.*')
|
||
$hit = $false
|
||
foreach ($pl in $lines) { if ($pl -match ('^\s*#+\s+' + $rx + '\s*$')) { $hit = $true; break } }
|
||
if (-not $hit) {
|
||
if ($hasPh) { $missingPlaceholder += $t } else { $missingLiteral += $t }
|
||
}
|
||
}
|
||
if ($missingLiteral.Count -eq 0) { Pass '章节完整性:模板必备节全部存在' }
|
||
else { Fail ('缺章节(字面量,必错): ' + ($missingLiteral -join ' | ')) }
|
||
if ($missingPlaceholder.Count -gt 0) { Warn ('示例性章节未匹配(可能条数不同,需人判): ' + ($missingPlaceholder -join ' | ')) }
|
||
}
|
||
|
||
# ---------------------------------------------------------------- 定位辅助
|
||
function Get-HeadIndex($pattern) {
|
||
for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i] -match $pattern) { return $i } }
|
||
return -1
|
||
}
|
||
function Get-NextHeadIndex($from) {
|
||
for ($i = $from + 1; $i -lt $lines.Count; $i++) { if ($lines[$i] -match '^\s*#+\s+\S') { return $i } }
|
||
return $lines.Count
|
||
}
|
||
|
||
# ---------------------------------------------------------------- scan 专属
|
||
if ($Mode -eq 'scan') {
|
||
# 1) 禁止围栏代码块(mermaid 除外)
|
||
$nonMermaid = @($fences | Where-Object { $_.Lang -ne 'mermaid' })
|
||
if ($nonMermaid.Count -eq 0) { Pass '深度闸门:S1 无围栏代码块(仅 mermaid)' }
|
||
else { Fail ("S1 不允许围栏代码块,发现 $($nonMermaid.Count) 个(第 " + (($nonMermaid | ForEach-Object { $_.Start + 1 }) -join ', ') + ' 行起)') }
|
||
|
||
# 2) 锚点格式:路径#方法名(不带行号,每条最多 2 个)
|
||
$badAnchor = @()
|
||
$anchorCount = 0
|
||
foreach ($l in $lines) {
|
||
if ($l -match '^\s*\*\*锚点\*\*\s*[::]\s*(.+?)\s*$') {
|
||
$anchorCount++
|
||
$v = $Matches[1]
|
||
$items = @($v -split '[、,,]' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
|
||
if ($items.Count -gt 2) { $badAnchor += ("锚点超过 2 个($($items.Count) 个): " + $l.Trim()) }
|
||
foreach ($it in $items) {
|
||
$a = $it.Trim().Trim('`')
|
||
if ($a -match ':\d') { $badAnchor += ("S1 锚点不写行号 -> $a") }
|
||
elseif ($a -notmatch '\w[/\\]\w') { $badAnchor += ("S1 锚点须为 路径#方法名(机制级可到类名) -> $a") }
|
||
}
|
||
}
|
||
}
|
||
if ($anchorCount -eq 0) { Warn '未找到 **锚点** 行(若产物为空则忽略)' }
|
||
elseif ($badAnchor.Count -eq 0) { Pass "锚点格式合规($anchorCount 处,路径#方法名)" }
|
||
else { Fail ('锚点格式不合规: ' + ($badAnchor -join ' ; ')) }
|
||
|
||
# 3) 闸门状态节
|
||
if ((Get-HeadIndex '^\s*#+\s*闸门状态') -ge 0) { Pass '有「闸门状态」节(S2 门控有证据)' }
|
||
else { Fail '缺「闸门状态」节 —— S2 门控没有证据' }
|
||
|
||
# 4) 候选条数(提示性:超 12 才 FAIL,无下限硬卡)
|
||
$starCount = ($lines | Where-Object { $_ -match '^\s*#+\s*★' }).Count
|
||
if ($starCount -le 12) { Pass "候选条数 $starCount(≤12)" }
|
||
else { Fail "候选条数 $starCount 超过 12(颗粒度掉到实现层,需重并)" }
|
||
if ($starCount -lt 5 -and $starCount -gt 0) { Warn "候选条数 $starCount 少于 5——先确认是否采样不到位,而非真没有" }
|
||
}
|
||
|
||
# ---------------------------------------------------------------- dig 专属
|
||
if ($Mode -eq 'dig') {
|
||
$skelIdx = Get-HeadIndex '^\s*#+\s*主干调用骨架'
|
||
$skelEnd = if ($skelIdx -ge 0) { Get-NextHeadIndex $skelIdx } else { -1 }
|
||
if ($skelIdx -ge 0) { Pass '有「主干调用骨架」节' }
|
||
else { Warn '未找到「主干调用骨架」节(仅 S4 ① 功能点清单必需)' }
|
||
|
||
# 1) 局部代码块 ≤10 行(骨架块豁免)
|
||
$exempt = 0
|
||
foreach ($f in $fences) {
|
||
if ($f.Lang -eq 'mermaid') { continue }
|
||
$isSkel = ($skelIdx -ge 0 -and $f.Start -gt $skelIdx -and $f.Start -lt $skelEnd)
|
||
if ($isSkel) { $exempt++; continue }
|
||
if ($f.Body -gt 10) { Warn "局部代码块超过 10 行(第 $($f.Start + 1) 行起,$($f.Body) 行)——行数非硬限,请人判断是关键片段还是源码摘录" }
|
||
}
|
||
Pass ("局部代码块行数检查完成(豁免骨架块 $exempt 个)")
|
||
|
||
# 2) 骨架每行必须带方式标记
|
||
if ($skelIdx -ge 0) {
|
||
$skelFence = @($fences | Where-Object { $_.Start -gt $skelIdx -and $_.Start -lt $skelEnd }) | Select-Object -First 1
|
||
if (-not $skelFence) { Warn '骨架节里没有代码块' }
|
||
else {
|
||
$missTag = @()
|
||
foreach ($bl in ($lines[($skelFence.Start + 1)..($skelFence.End - 1)])) {
|
||
if ([string]::IsNullOrWhiteSpace($bl)) { continue }
|
||
if ($bl -match '^\s*//') { continue } # 分段注释行
|
||
if ($bl -match ':\s*$') { continue } # 块头行(xxx(): ),标"谁"不标"怎么做"
|
||
if ($bl -notmatch '[(\[]') { continue } # 连接词行(↓ 三路汇合)
|
||
if ($bl -notmatch '//') { $missTag += $bl.Trim() } # 其余动作行必须有方式标记
|
||
}
|
||
if ($missTag.Count -eq 0) { Pass '骨架每行都带方式标记' }
|
||
else { Fail "骨架有 $($missTag.Count) 行缺方式标记: " + (($missTag | Select-Object -First 3) -join ' ; ') }
|
||
}
|
||
}
|
||
|
||
# 3) 证据须含 行号
|
||
if ($text -notmatch '\.(java|xml|yaml|yml|sql):\d+') { Fail '未找到任何 `文件:行号` 证据(本阶段必须有可核对的锚点)' }
|
||
else { Pass '存在 `文件:行号` 形式的证据' }
|
||
}
|
||
|
||
# ---------------------------------------------------------------- doc 专属
|
||
if ($Mode -eq 'doc') {
|
||
# 改造方案识别:含「现状问题登记」节(设计思路与取舍模板不含此节)
|
||
$isPlan = (Get-HeadIndex '^\s*#+\s*0\.\s*现状问题登记') -ge 0
|
||
if ($isPlan) {
|
||
# 1) 必有非 mermaid 代码片段(伪代码 / SQL)
|
||
$codeFences = @($fences | Where-Object { $_.Lang -ne 'mermaid' })
|
||
if ($codeFences.Count -ge 1) { Pass "改造方案含关键片段 $($codeFences.Count) 个" }
|
||
else { Fail '改造方案缺「关键片段」代码块——必须"照着能讲代码"(伪代码/SQL 均可,只禁大段源码摘录)' }
|
||
|
||
# 2) 片段应覆盖"骨架 + 改造点",只给一块多半只在开头充数
|
||
if ($codeFences.Count -ge 2) { Pass "关键片段 $($codeFences.Count) 个(骨架 + 改造点)" }
|
||
elseif ($codeFences.Count -eq 1) { Warn '只有 1 个关键片段——可能只有链路骨架,各改造点未给伪代码,需人判' }
|
||
}
|
||
}
|
||
|
||
# ---------------------------------------------------------------- 输出
|
||
Write-Output ""
|
||
Write-Output ("=" * 68)
|
||
Write-Output "机械校验 Mode=$Mode"
|
||
Write-Output (" product : " + $prodPath)
|
||
if ($Template) { Write-Output (" template : " + (Resolve-Path -LiteralPath $Template).Path) }
|
||
Write-Output ("=" * 68)
|
||
|
||
if ($script:pass.Count -gt 0) {
|
||
Write-Output ""
|
||
Write-Output "[PASS]"
|
||
foreach ($m in $script:pass) { Write-Output (" + " + $m) }
|
||
}
|
||
if ($script:warn.Count -gt 0) {
|
||
Write-Output ""
|
||
Write-Output "[WARN] 需人判断"
|
||
foreach ($m in $script:warn) { Write-Output (" ! " + $m) }
|
||
}
|
||
if ($script:fail.Count -gt 0) {
|
||
Write-Output ""
|
||
Write-Output "[FAIL] 必错"
|
||
foreach ($m in $script:fail) { Write-Output (" x " + $m) }
|
||
}
|
||
|
||
Write-Output ""
|
||
Write-Output ("结果:PASS {0} / WARN {1} / FAIL {2}" -f $script:pass.Count, $script:warn.Count, $script:fail.Count)
|
||
if ($script:fail.Count -gt 0) { exit 1 } else { exit 0 }
|