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
This commit is contained in:
zhuyongxin
2026-09-18 18:23:23 +08:00
parent 24a71e78f1
commit fddefab0c9
10 changed files with 1748 additions and 0 deletions
@@ -0,0 +1,112 @@
---
name: value-scan
description: Use when the user wants to inventory the mechanisms worth talking about in code that is already delivered — "盘点这个域/模块的价值点", "扫一下 order 域有什么值得讲的", "提取功能点", "这个项目有什么值得讲的设计", or when interview/resume material must be reconstructed backwards from an existing module. Read-only — never modifies the scanned project. Stops at the human selection gate; deep write-up is a separate skill.
---
# value-scan · 价值点盘点(广度枚举)
## 一句话
从**已交付的代码**里,逆向枚举出**值得讲的机制点**,交人勾选后**停下**。
**三条核心原则**
1. **agent 判不了"价值"**——判据不是分类体系,而是**简历行填空测试**。
2. **价值优先,本阶段不找缺陷。** 缺陷是深入链路时自然浮现的副产物,归 `value-dig`。
3. **只读。** 不修改被扫描项目的任何文件。
## 流程
```
S0 定范围 → S1 枚举 → S2 勾选【终点:产出清单后必须停】
↓(人勾选后)
value-dig(轻理由 → 设计复盘 / 功能点 / 改造方案)
```
| 需求 | 该用 |
|---|---|
| 盘点 / 扫一遍 / 提取候选价值点 | **本 skill** |
| 把选中的点写成深度文档 | `value-dig` |
| 找 bug、修缺陷 | `diagnose` |
## S0 · 定范围
- **快路**:仓库已有域清单(`.aspirecode/sdd/rules.md` §9、`pom.xml`、`wiki/` 目录)→ 直接读。
- **慢路**:陌生仓库 → 按接口路径前缀、`-api` Feign 接口名、controller 清单切出能力面,一次一个 package。
- 范围由**人给定**(一个域/模块),不一次扫全库。
**域是入口锚点,不是物理边界。** 完整链路往往跨模块,用两头夹的办法拼:
- **聚合层(web)定链路形状**:controller / Kafka consumer / XxlJob / callback 四类入口全在聚合层,且分包与路径自带业务语义(`/consumer/fulfillment/`、topic 名、请求路径)——从这里正向切出"有哪些触发者、哪些链路"。MQ 消费、定时任务这类**非 Feign 入口**靠反推找不到。
- **原子域挖机制内容**:锁/幂等/状态机等机制本体大多在原子域的 service 实现,聚合层只有编排——读实现要去原子域。
- **中间用调用图接**(`gitnexus_route_map` / `gitnexus_cypher`);被驱动型域也可用"谁 import 了我的 Feign 接口"反查调用方作兜底。
- 只读链路经过的路径,不通读途径模块。
## S1 · 枚举
### 取材顺序(信噪比递减)
| 顺序 | 来源 | 备注 |
|:-:|---|---|
| 1 | `wiki/*.md`、`.claude/docs/**`、`devflow/projects/**` | 先读「设计说明类」,后读「问题分析类」——先读问题分析会把清单带成缺陷导向 |
| 2 | `git log`:`feat(...)` / `refactor(...)`、带单号、单次大改动 | 有意识的改动藏着设计意图 |
| 3 | `gitnexus_query` / `gitnexus_route_map` | 用图查结构,不读全文 |
| 4 | 代码结构统计 + 关键字 grep(锁/重试/MQ/Job/条件更新/状态机/延时/对账) | 只做清单式定位,不通读代码 |
> ⚠️ 读完文档必须回代码验一遍"文档写的方案是否落地"。**"文档 vs 代码分叉"本身就是高价值点**。
### 怎么看结构:按「触发者」拆链路
不按业务功能拆,按**谁触发**拆:找出「**同一行数据被几个触发者改**」(如回调/查单/取消都改同一行支付流水)——收敛点多半就是机制所在。通常 1–4 条链路,被驱动型模块可到 5–6 条(超过要写一句为什么)。
每条链路挂 1–4 个机制节点。**候选颗粒度 = 机制/能力**,不是注解、类、配置项——"用了 `@LockAction`、有 4 个 handler"是实现清单;跨 ≥2 个写类入口或 ≥2 张表的才够"机制"。
### 判据 · 简历行测试
**一级 · 准入(三条硬标准,全过才进清单)**
| 标准 | 反例(据此剔除) |
|---|---|
| ① **核心链路**(资金/履约/交付这类业务闭环) | 购物车 key 命名、查询接口 |
| ② **高级工程师的设计**(设计决策,非框架常识) | Redis `GETDEL`、Spring 自注入修事务自调用 |
| ③ **简历够硬**(能写成"我设计了 X 机制解决 Y") | 定长文件 + 签名 + SFTP 的对接实现 |
**二级 · 表达**:`用【机制】解决了【具体问题/场景】,代价是【取舍】`——本阶段只试填"机制+问题"两格;填不出机制 → 并入「已剔除」表(标 `机制格填不出`,待复核);填不出取舍**不误杀**(取舍归 `value-dig`)。
> ⚠️ **`机制格填不出` 的剔除门槛(防误杀,实测翻案教训)**:机制本体常藏在实现大类(2000+ 行)的私有方法群里,入口类名/Job 类名只是壳——**没读过入口方法向下 ~100 行,不得以此理由定稿剔除**。剔除非此理由的照常。被剔除后复评翻案的,在剔除表原行标注撤销原因与日期(如"2026-09-18 复评撤销:快照轮询机制实存,升级为 ★N"),不删行——翻案记录本身就是筛选口径的进化证据。
被剔除的点进「已剔除」表(注明未过哪条标准),不丢弃。
### 数量
亮点 **5–8 条**为宜。超过 12 条 = 颗粒度掉到实现层,退回重并。
## S2 · 门控(终点,不可跳过)
1. 候选清单**已落盘**(每条含「一句话价值」与锚点),头部「闸门状态」节写明"S1 已完成 · 待勾选";
2. 回复里**显式请求勾选**("请从这 N 条里挑 3–6 条")。用户禁用提问时也一样停——把请求写成文字,**不是**替他挑完继续写文档。
3. **否决即校准**:人否决候选("不够硬"/"一般")时,把否决原因记入清单(闸门状态或剔除表)——下次扫同域/同仓先读上次的否决记录,同类点不再重复上报;整批否决且未给新目标 = 流程终点,不强续。
## 硬规则(合并版,替代旧的硬规则/反模式/Gotchas 三张表)
| # | 规则 | 为什么 |
|:-:|---|---|
| 1 | **深度闸门**:无围栏代码块(Mermaid 除外,行内反引号可用);不贴源码、不写行号、不逐层拆解、不展开取舍 | 本阶段是广度层 |
| 2 | **不找缺陷、不读"坏味道报告"**;把克制当设计("manager 只有 3 个"可能是有意的) | 缺陷导向会摧毁清单 |
| 3 | **证据真实性**:锚点 `⟨简写⟩/路径#方法名`(机制级可锚到类;不写行号),**读过再写**,拿不准标【待确认】,每条 ≤2 个 | 错锚点毁掉可信度 |
| 4 | **每条链路(含未入选的)都要写「是什么 + 为什么入选/未入选」**;0 机制先问"真没有,还是采样不到位" | 链路是骨架,不因无亮点而省略 |
| 5 | **三层结构**:域 → 链路 → 机制节点,机制必须挂在链路图上 | 并列清单会显得"都是散的" |
| 6 | **只读 + 门控**:不改被扫项目文件;产出后必停 | — |
## 格式保证
1. **模板外置**:读 `assets/候选清单模板.md` 填空,不照印象写。
2. **机械校验**:产出后跑 `python <skills>/value-scan/assets/check.py scan -Product <产物> -Template <skills>/value-scan/assets/候选清单模板.md`(跨平台 Python 版;Windows 下若中文乱码先设 `PYTHONIOENCODING=utf-8`。旧 `check.ps1` 保留但不再维护)。FAIL 必须为 0;WARN 允许保留但写明原因。
3. **偏差回写**:被纠正过就回写模板。模板是活资产。
## 触发词与落盘
**触发词**:「盘点这个模块的价值点」「扫一下 X 域有什么值得讲的」「提取功能点」。
**落盘**:`<项目根>/docs/{域}-候选价值点.md`(可被用户覆盖)。
**下一步**:用户勾选后,用 `value-dig` 接手。
@@ -0,0 +1,268 @@
<#
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 }
@@ -0,0 +1,307 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
value-scan / value-dig 产物机械校验(Python 版,等价移植自 check.ps1,跨平台)
------------------------------------------------------------------
用法:
# S1 候选清单
python check.py scan <产物路径> --template <skills>/value-scan/assets/候选清单模板.md
# S4 功能点清单
python check.py dig <产物路径> --template <skills>/value-dig/assets/深度模板/功能点清单模板.md
# S4 设计思路与取舍 / 改造方案(整篇级文档,只跑通用校验)
python check.py doc <产物路径> --template <路径>
退出码:0 = 无 FAIL;1 = 有 FAIL;2 = 用法/IO 错误
说明:FAIL = 必错;WARN = 需人判断
"""
import argparse
import re
import sys
from pathlib import Path
FAIL, WARN, PASS = [], [], []
# ⟨ ⟩ 占位符(U+27E8 / U+27E9)
PH_L, PH_R = '\u27e8', '\u27e9'
def fail(m):
FAIL.append(m)
def warn(m):
WARN.append(m)
def pass_(m):
PASS.append(m)
def read_text(path_str):
p = Path(path_str)
if not p.is_file():
print(f"[ERROR] 文件不存在: {path_str}", file=sys.stderr)
sys.exit(2)
text = p.read_text(encoding='utf-8-sig') # 自动剥 BOM
return text, text.splitlines()
def head_index(lines, pattern):
for i, line in enumerate(lines):
if re.search(pattern, line):
return i
return -1
def next_head_index(lines, start):
for i in range(start + 1, len(lines)):
if re.match(r'^\s*#+\s+\S', lines[i]):
return i
return len(lines)
def line_snip(line, width=40):
"""报错附带的行内容摘要(改进:报错不指内容曾导致误诊)"""
s = line.strip().replace('|', '\\|')
return s[:width] + ('…' if len(s) > width else '')
def main():
ap = argparse.ArgumentParser(description='value-scan/value-dig 产物机械校验')
ap.add_argument('mode', choices=['scan', 'dig', 'doc'])
ap.add_argument('product', help='产物路径')
ap.add_argument('--template', '-t', help='模板路径')
ap.add_argument('-Mode', '-Product', '-Template', dest='legacy', help=argparse.SUPPRESS)
args = ap.parse_args()
prod_path = Path(args.product).resolve()
text, lines = read_text(args.product)
# ------------------------------------------------ 围栏代码块
fences = []
in_fence = False
f_start, f_lang = 0, ''
for i, line in enumerate(lines):
if re.match(r'^\s*```', line):
if not in_fence:
in_fence, f_start = True, i
f_lang = re.sub(r'^\s*```', '', line).strip()
else:
in_fence = False
fences.append({'start': f_start, 'end': i, 'lang': f_lang,
'body': i - f_start - 1})
if in_fence:
fail(f'存在未闭合的代码围栏(起始行 {f_start + 1})')
# ------------------------------------------------ 通用 1:引号逐行配对
odd_quote = [i + 1 for i, line in enumerate(lines)
if line.count('"') % 2 != 0]
if not odd_quote:
pass_('引号逐行配对')
else:
fail('引号未配对的行: ' + ', '.join(map(str, odd_quote)))
# ------------------------------------------------ 通用 2:mermaid 合法性
mermaid_fences = [f for f in fences if f['lang'] == 'mermaid']
mm_bad = 0
for f in mermaid_fences:
body_lines = lines[f['start'] + 1:f['end']]
body = '\n'.join(body_lines)
first = next((l for l in body_lines if l.strip()), '')
end_count = len(re.findall(r'(?m)^\s*end\s*$', body))
if 'sequenceDiagram' in first:
blk = len(re.findall(r'(?m)^\s*(alt|opt|loop|par|critical|break|rect)\b', body))
if blk != end_count:
fail(f"mermaid 时序图(第 {f['start'] + 1} 行起)alt/opt/loop 等 {blk} 个但 end={end_count}")
mm_bad += 1
else:
sg = len(re.findall(r'(?m)^\s*subgraph\s', body))
if sg != end_count:
fail(f"mermaid 流程图(第 {f['start'] + 1} 行起)subgraph={sg} 但 end={end_count}")
mm_bad += 1
if (re.search(r'(?m)^\s*subgraph\s+\S+\s*\[', body)
and not re.search(r'(?m)^\s*subgraph\s+\S+\s*\["', body)):
fail(f'mermaid(第 {f["start"] + 1} 行起)subgraph 缺引号标题,必须写 subgraph id["标题"]')
mm_bad += 1
if re.search(r'(?m)^\s*subgraph\s+\S+\s*\[\(', body):
fail(f'mermaid(第 {f["start"] + 1} 行起)用了 subgraph xxx[(...)],会解析失败')
mm_bad += 1
if mermaid_fences and mm_bad == 0:
pass_(f'mermaid 块 {len(mermaid_fences)} 个通过')
# ------------------------------------------------ 通用 3:表格列数一致(含行内容摘要)
ti = 0
while ti < len(lines):
if re.match(r'^\s*\|', lines[ti]):
bs = ti
blk = []
while ti < len(lines) and re.match(r'^\s*\|', lines[ti]):
blk.append(lines[ti])
ti += 1
counts = sorted({b.count('|') for b in blk})
if len(counts) > 1:
fail(f"表格列数不一致(第 {bs + 1} 行起):pipe 数 = {'/'.join(map(str, counts))}"
f"|首行内容: {line_snip(blk[0])}")
else:
ti += 1
# ------------------------------------------------ 通用 3b:表格不得缩进
indented = [i + 1 for i, line in enumerate(lines) if re.match(r'^[ \t]+\|', line)]
if not indented:
pass_('表格均顶格(无嵌套缩进表)')
else:
fail('表格存在缩进(第 ' + ', '.join(map(str, indented)) + ' 行起)——嵌套在列表里的表多数渲染器不显示,必须顶格')
# ------------------------------------------------ 通用 4:章节完整性
if args.template:
_, t_lines = read_text(args.template)
t_heads = [l for l in t_lines if re.match(r'^\s*#+\s+\S', l)]
missing_literal, missing_ph = [], []
for h in t_heads:
t = re.sub(r'\s+$', '', re.sub(r'^\s*#+\s+', '', h))
has_ph = PH_L in t
ph_rx = re.escape(PH_L) + '[^' + re.escape(PH_R) + ']*' + re.escape(PH_R)
rx = re.escape(re.sub(ph_rx, '@@PH@@', t)).replace('@@PH@@', '.*')
hit = any(re.match(r'^\s*#+\s+' + rx + r'\s*$', pl) for pl in lines)
if not hit:
(missing_ph if has_ph else missing_literal).append(t)
if not missing_literal:
pass_('章节完整性:模板必备节全部存在')
else:
fail('缺章节(字面量,必错): ' + ' | '.join(missing_literal))
if missing_ph:
warn('示例性章节未匹配(可能条数不同,需人判): ' + ' | '.join(missing_ph))
# ------------------------------------------------ scan 专属
if args.mode == 'scan':
non_mermaid = [f for f in fences if f['lang'] != 'mermaid']
if not non_mermaid:
pass_('深度闸门:S1 无围栏代码块(仅 mermaid)')
else:
fail('S1 不允许围栏代码块,发现 {} 个(第 {} 行起)'.format(
len(non_mermaid),
', '.join(str(f['start'] + 1) for f in non_mermaid)))
bad_anchor, anchor_count = [], 0
for line in lines:
m = re.match(r'^\s*\*\*锚点\*\*\s*[::]\s*(.+?)\s*$', line)
if not m:
continue
anchor_count += 1
items = [s for s in re.split(r'[、,,]', m.group(1)) if s.strip()]
if len(items) > 2:
bad_anchor.append(f'锚点超过 2 个({len(items)} 个): {line.strip()}')
for it in items:
a = it.strip().strip('`')
if re.search(r':\d', a):
bad_anchor.append(f'S1 锚点不写行号 -> {a}')
elif not re.search(r'\w[/\\]\w', a):
bad_anchor.append(f'S1 锚点须为 路径#方法名(机制级可到类名) -> {a}')
if anchor_count == 0:
warn('未找到 **锚点** 行(若产物为空则忽略)')
elif not bad_anchor:
pass_(f'锚点格式合规({anchor_count} 处,路径#方法名)')
else:
fail('锚点格式不合规: ' + ' ; '.join(bad_anchor))
if head_index(lines, r'^\s*#+\s*闸门状态') >= 0:
pass_('有「闸门状态」节(S2 门控有证据)')
else:
fail('缺「闸门状态」节 —— S2 门控没有证据')
star_count = sum(1 for l in lines if re.match(r'^\s*#+\s*★', l))
if star_count <= 12:
pass_(f'候选条数 {star_count}(≤12)')
else:
fail(f'候选条数 {star_count} 超过 12(颗粒度掉到实现层,需重并)')
if 0 < star_count < 5:
warn(f'候选条数 {star_count} 少于 5——先确认是否采样不到位,而非真没有')
# ------------------------------------------------ dig 专属
if args.mode == 'dig':
skel_idx = head_index(lines, r'^\s*#+\s*主干调用骨架')
skel_end = next_head_index(lines, skel_idx) if skel_idx >= 0 else -1
if skel_idx >= 0:
pass_('有「主干调用骨架」节')
else:
warn('未找到「主干调用骨架」节(仅 S4 ① 功能点清单必需)')
exempt = 0
for f in fences:
if f['lang'] == 'mermaid':
continue
if skel_idx >= 0 and skel_idx < f['start'] < skel_end:
exempt += 1
continue
if f['body'] > 10:
warn(f"局部代码块超过 10 行(第 {f['start'] + 1} 行起,{f['body']} 行)"
f"——行数非硬限,请人判断是关键片段还是源码摘录")
pass_(f'局部代码块行数检查完成(豁免骨架块 {exempt} 个)')
if skel_idx >= 0:
skel_fences = [f for f in fences if skel_idx < f['start'] < skel_end]
if not skel_fences:
warn('骨架节里没有代码块')
else:
sf = skel_fences[0]
miss_tag = []
for bl in lines[sf['start'] + 1:sf['end']]:
if not bl.strip():
continue
if re.match(r'^\s*//', bl):
continue
if re.search(r':\s*$', bl):
continue
if not re.search(r'[(\[]', bl):
continue
if '//' not in bl:
miss_tag.append(bl.strip())
if not miss_tag:
pass_('骨架每行都带方式标记')
else:
fail(f"骨架有 {len(miss_tag)} 行缺方式标记: "
+ ' ; '.join(miss_tag[:3]))
if not re.search(r'\.(java|xml|yaml|yml|sql):\d+', text):
fail('未找到任何 `文件:行号` 证据(本阶段必须有可核对的锚点)')
else:
pass_('存在 `文件:行号` 形式的证据')
# ------------------------------------------------ doc 专属
if args.mode == 'doc':
is_plan = head_index(lines, r'^\s*#+\s*0\.\s*现状问题登记') >= 0
if is_plan:
code_fences = [f for f in fences if f['lang'] != 'mermaid']
if len(code_fences) >= 1:
pass_(f'改造方案含关键片段 {len(code_fences)} 个')
else:
fail('改造方案缺「关键片段」代码块——必须"照着能讲代码"(伪代码/SQL 均可,只禁大段源码摘录)')
if len(code_fences) >= 2:
pass_(f"关键片段 {len(code_fences)} 个(骨架 + 改造点)")
elif len(code_fences) == 1:
warn('只有 1 个关键片段——可能只有链路骨架,各改造点未给伪代码,需人判')
# ------------------------------------------------ 输出
print()
print('=' * 68)
print(f'机械校验 Mode={args.mode}')
print(f' product : {prod_path}')
if args.template:
print(f" template : {Path(args.template).resolve()}")
print('=' * 68)
for tag, bucket, mark in (('[PASS]', PASS, '+ '), ('[WARN] 需人判断', WARN, '! '), ('[FAIL] 必错', FAIL, 'x ')):
if bucket:
print()
print(tag)
for m in bucket:
print(f' {mark}{m}')
print()
print(f'结果:PASS {len(PASS)} / WARN {len(WARN)} / FAIL {len(FAIL)}')
sys.exit(1 if FAIL else 0)
if __name__ == '__main__':
main()
@@ -0,0 +1,146 @@
# ⟨域⟩ 域 · 候选价值点(S1 产物)
> **阶段**:S1 枚举(**只产出亮点**;缺陷不在此阶段——它是深入链路时自然浮现的副产物)
> **域**:`⟨模块名⟩`(走 S0 ⟨快路 / 慢路⟩:⟨域清单来源⟩)
> **结构**:**域 → 链路 → 链路上的机制节点** 三层
> **筛选标准(三条,全过才进)**:**① 是否核心链路 ② 是否符合高级工程师的设计 ③ 写到简历上够不够硬**
> **深度闸门**:允许「**这个点在讲什么**」的业务语言说明;**禁止围栏代码块(行内反引号可用)、禁止逐层拆解、禁止取舍复盘**(那些属深度阶段)
> **取材**:① 设计说明类文档 ② `git log` ③ 图查询 / 结构统计 ④ 关键字 grep
---
# 闸门状态
> **本节是 S2 门控的证据**——没有它,"停在第几段"无法核查。
| 段 | 状态 | 说明 |
|:-:|---|---|
| S0 定范围 | ✅ | ⟨范围声明一句话⟩ |
| **S1 枚举** | ✅ 已完成 | 候选 ⟨N⟩ 条(**亮点**)+ 剔除 ⟨M⟩ 条 |
| **S2 勾选** | ⏸ **待人工勾选** | **请从下面挑 3–6 条**;本文件到此为止,**未做深度产出** |
> **勾选/否决后回写本表(保持 3 列不压列)**,两种回写形状:
>
> ```
> | **S2 勾选** | ✅ 已勾选 | ⟨勾了哪些(★N…)/ 谁日期⟩ → 产出《⟨产物名⟩》 |
> | **S2 勾选** | ↩️ 被否决改向 | 否决原因:⟨用户原话摘要⟩ → 改挖 ⟨新目标⟩(入口 A′) |
> ```
---
## 一、链路总览
**一句话**:⟨统领句——不是复述流程,而是给出这条链路的**主线判断**。例:"这条链路的每一段都在把外部系统给的不确定,收敛成我方的确定状态"⟩
**读法**:⟨一条端到端链路 = …→…→…;下面每个机制都挂在这张图的某个位置上⟩
**图例**:`★` = 入选的机制节点;**未标 ★ 的链路仍属本链路的一环**(见第二、三节),只是未列为独立机制。
### 端到端链路图(★ = 入选的机制节点)
```mermaid
flowchart LR
subgraph PA["链路 A · ⟨链路名⟩"]
A1["⟨起点⟩"] --> A2["⟨机制节点⟩ ★1"]
A3["⟨兜底 / 分支⟩ ★2"]
end
subgraph PB["链路 B · ⟨链路名⟩"]
B1["⟨起点⟩"] --> B2["⟨机制节点⟩ ★3"]
B2 --> B3["⟨机制节点⟩ ★4"]
end
subgraph PC["链路 C · ⟨链路名⟩"]
C1["⟨起点⟩"] --> C2["⟨终点⟩"]
end
A2 --> B1
A3 -.-> A2
B3 --> C1
C2 -.-> A2
```
⟨可选⟩**关键差异表**(若这条链路的核心是"几种结果后果完全不同",用一张表压住)
| ⟨维度⟩ | ⟨取值1⟩ | ⟨取值2⟩ | ⟨取值3⟩ |
|---|---|---|---|
| ⟨例:对方结果⟩ | | | |
| ⟨例:可逆性⟩ | | | |
| ⟨例:处置⟩ | | | |
---
## 二、链路上的机制节点(⟨N⟩ 个 ★)
> **按链路分组**。每点 4 段,**段落式**(不要压成表格——"做了什么"常是 5–7 条,塞进单元格必然被简化)。
### 链路 ⟨A⟩ · ⟨链路名⟩
## ★1 · ⟨机制名⟩
**核心内容**:⟨1 句:这个机制是什么,把它的"形状"说清(一把锁 + 三级短路 + 一条条件更新)⟩
**这个点在讲什么**
- **业务场景**:⟨谁在什么时刻触发了什么,为什么这事难;点出"同一时刻还有谁在改同一行数据"⟩
- **做了什么**(⟨用一句话概括做法⟩):
1. ⟨…⟩;
2. ⟨…⟩;
3. ⟨…⟩。
- **解决了什么问题**:⟨不做会怎样⟩
**一句话价值**:⟨1 句,可讲述;说清"判断权 / 控制权"落在谁手里⟩
**锚点**:`⟨简写⟩/⟨路径⟩#⟨方法名⟩`
---
## ★2 · ⟨机制名⟩
⟨同 ★1 的 4 段结构⟩
---
### 链路 ⟨B⟩ · ⟨链路名⟩
## ★3 · ⟨机制名⟩
⟨同 ★1 的 4 段结构⟩
---
### 链路 ⟨C⟩ · ⟨链路名⟩(⟨依附型,未列为独立机制 / 未入选⟩)
> **本链路 0 个独立机制,但说明不可省**——否则图上有个框、没人知道里面在干嘛。
**核心内容**:⟨它是什么⟩
**这个点在讲什么**
- **业务场景**:⟨…⟩
- **做了什么**:⟨…⟩
- **依附关系**:⟨它的哪几个设计决策其实是 ★N 在另一个方向 / 另一个域上的复用;判不清就按独立节点处理,不要为了凑数强行依附⟩
- **为什么未入选 / 为什么依附**:⟨★ 必须写清⟩
**锚点**:`⟨…⟩`
---
## 三、已剔除(附理由,供复核筛选口径)
> **被剔除的点不丢弃**——注明**未过哪条标准**。
**「未过的标准」建议取值**:`①核心链路` · `②非设计决策(框架常识 / 通用工程质量)` · `③简历不硬` · `机制格填不出` · `示例数据 / 字典表 / 配置装配`
| # | 被剔除的点 | 未过的标准 | 理由 |
|:-:|---|---|---|
| 1 | ⟨例:key 命名约定⟩ | `①核心链路` | 不落在业务闭环上 |
| 2 | ⟨例:Redis `GETDEL` 用法⟩ | `②非设计决策` | 框架常识用法,非设计决策 |
| 3 | ⟨例:契约模式 / CI / Checkstyle⟩ | `②非设计决策` | 通用工程质量,无设计含量 |
| 4 | ⟨例:表 CRUD / 单表封装⟩ | `机制格填不出` | 弱候选,并入本表待复核 |
---
## 四、待确认 / 覆盖缺口
| # | 项 | 说明 |
|:-:|---|---|
| 1 | **⟨链路名⟩ 0 个节点** | ⟨先问"真没亮点,还是采样不到位"(回看该链路的触发者与写类接口);判不清就写明理由⟩ |
| 2 | `⟨路径⟩#⟨方法名⟩` | 【待确认】⟨拿不准的锚点必须标出,禁止猜⟩ |
| 3 | **文档 vs 代码分叉** | ⟨设计文档写的方案在代码里是否落地?不落地本身就是高价值点⟩ |