Files
git-learn/skill-workbench/generated-skills/skills/value-scan/assets/check.ps1
T
zhuyongxin fddefab0c9 Add value-scan and value-dig skills for reverse-engineering value points
- 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
2026-09-18 18:23:23 +08:00

269 lines
14 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<#
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 }