/pmd-check Skill
对 Java 代码批量执行 PMD 静态检查(PMD 7 内置规则集)。
触发场景
用户输入:
/pmd-check—— 对整个项目跑mvn pmd:pmd/pmd-check <模块名>—— 对指定模块跑mvn -pl <模块名> pmd:pmd
<模块名> 可以是:
- 模块根路径(如
user-service)—— 直接定位该模块 - 模块内子目录(如
user-service/src/main/java/com/foo)—— 向上找最近的 pom.xml 定位所属模块 - 单个 .java 文件(如
user-service/src/main/java/Foo.java)—— 同上,定位到所属模块 - 绝对路径—— 自动转换为相对项目根路径
无论 <模块名> 指向多深,实际执行粒度都是模块级:对整个所属模块跑 PMD 扫描。
若传入项目聚合根,等同 /pmd-check(不带参数)全量扫描;若路径不存在或向上找不到 pom.xml → 报错并结束。
执行步骤
所有步骤必须在同一个 bash -c 中执行(common.sh 使用 bash 关联数组 declare -A,不能在 zsh 中直接 source;且 $SCAN_ROOT 等变量需在同一 bash 进程中传递)。
使用 Bash 工具执行以下完整脚本(<ARG> 替换为用户传入的 path,可能为空):
bash -c '
source .claude/hooks/lib/common.sh
ensure_java_home || { warn_missing_java_home; exit 1; }
PROJECT_ROOT="$(find_project_root)"
cd "$PROJECT_ROOT"
# ---- 步骤 1:运行 PMD 扫描 ----
ARG="<用户传入的 path,可能为空>"
if [[ -z "$ARG" ]]; then
mvn -q -B pmd:pmd -DincludeTests=false
SCAN_ROOT="$PROJECT_ROOT"
else
if [[ ! -e "$ARG" ]]; then
echo "错误:路径不存在:$ARG"
echo "提示:/pmd-check 只支持传入模块根、模块内的子目录或 .java 文件;"
echo " 若想扫描整个项目,请直接使用 /pmd-check(不带参数)。"
exit 1
fi
MOD="$(find_module_root "$ARG")" || {
echo "错误:$ARG 不属于任何 Maven 模块(未找到 pom.xml)。"
echo "提示:/pmd-check 只支持传入模块根、模块内的子目录或 .java 文件;"
echo " 若想扫描整个项目,请直接使用 /pmd-check(不带参数)。"
exit 1
}
if [[ "$MOD" == "$PROJECT_ROOT" ]]; then
mvn -q -B pmd:pmd -DincludeTests=false
SCAN_ROOT="$PROJECT_ROOT"
else
REL="${MOD#$PROJECT_ROOT/}"
mvn -q -B -pl "$REL" pmd:pmd -DincludeTests=false
SCAN_ROOT="$MOD"
fi
fi
# pmd:pmd 不因违规而 fail build,报告输出到各模块 target/pmd.xml
# ---- 步骤 2+3:收集报告并解析 ----
# 用 mapfile 将 find 输出读入 bash 数组,再用 "${PMD_ARR[@]}" 展开
# 这样每个文件路径作为独立参数传给 parse_pmd_report(内部 awk 通过 "$@" 接收)
# 不能用 xargs(parse_pmd_report 是 bash 函数,xargs 只能调用外部命令)
# 不能用 parse_pmd_report $PMD_FILES(多个路径会被拼成单个字符串,awk 找不到文件)
mapfile -t PMD_ARR < <(find "$SCAN_ROOT" -type f -path "*/target/pmd.xml" -not -path "*/node_modules/*")
if (( ${#PMD_ARR[@]} == 0 )); then
echo "FILE_COUNT=0"
echo "TOTAL_COUNT=0"
exit 0
fi
REPORT_FILE="$(mktemp)"
parse_pmd_report "${PMD_ARR[@]}" > "$REPORT_FILE"
SUMMARY="$(head -1 "$REPORT_FILE")"
FILE_COUNT="$(echo "$SUMMARY" | cut -d" " -f1)"
TOTAL_COUNT="$(echo "$SUMMARY" | cut -d" " -f2)"
FORMAT="$(format_pmd_report_cn "$REPORT_FILE" "$PROJECT_ROOT")"
rm "$REPORT_FILE"
# 输出供 Claude 读取的结果
echo "FILE_COUNT=$FILE_COUNT"
echo "TOTAL_COUNT=$TOTAL_COUNT"
echo "---FORMAT_START---"
echo "$FORMAT"
echo "---FORMAT_END---"
'
从 Bash 工具输出中提取:
FILE_COUNT为有违规的文件数,TOTAL_COUNT为总违规数---FORMAT_START---和---FORMAT_END---之间的内容为中文分类表格格式,按规则类别分组展示输出报告:
扫描完成:N 个文件有违规,共 M 处。 $FORMAT 的内容(直接输出) 请选择后续操作: A. 修复 → 我会逐个文件调用 Edit 修复违规 B. 只看报告 → 结束,不做任何修改 C. 给出修复方案 → 逐文件给出具体代码修改建议,但不实际修改代码若
$FILE_COUNT为 0 → 向用户输出:"扫描完成:全部通过 ✓"
- 等待用户响应:
- A(修复) → 对每个有违规的文件,使用 Edit 工具修复违规
- 修复完成后,Stop hook 会自动跑
spotless:apply + pmd:pmd复查;未通过会阻断并反馈
- 修复完成后,Stop hook 会自动跑
- B(只看报告) → 直接结束
- C(给出修复方案) → 对每个有违规的文件,读取源码并给出具体修复建议(指出应删/改的代码位置和修改方式),但不调用 Edit/Write 修改代码
- A(修复) → 对每个有违规的文件,使用 Edit 工具修复违规
与 hook 的关系
回合结束时 Stop hook 会自动跑 spotless:apply + pmd:pmd。本 skill 用于主动全量或按模块扫描,不受 hook 阻断计数影响。
注意
- 必须先输出报告再询问是否修复,不要默认修复
- 用户回复"修复"后,逐文件调 Edit;不要尝试批量并发
- 传模块名/子目录/单文件时,实际是对整个所属模块扫描,不做子目录粒度过滤
- 若传入项目聚合根,等同
/pmd-check(不带参数)全量扫描 - 若路径不存在或向上找不到
pom.xml→ 报错并结束 - 如果失败,提示用户检查 pom.xml 是否已包含 maven-pmd-plugin(PMD 7 规则集)