- AGENTS.md: 新增 §20 工具脚本复用 / §21 分析结论复用 / §22 Flaky 门禁,更新文档配置表
- .pi/: 新增 settings.json、rules/guardrails.md、prompts/{debug,review,ship}-gym-manage.md
- .agents/: 新增 Hook 配置与 4 个 Hook 脚本(session-start/pre-agent-check/check-completeness/stop-check)
及 protocols/systematic-debugging.md(check-completeness 含接口链缺口检测)
- scripts/flaky-scan.sh: vitest shuffle 稳定性扫描(gym-manage-web)
- .gitignore: 追踪 AGENTS.md 与 .pi 配置,忽略运行时缓存(todos/taskflows-runs/tokenomy/sessions)
133 lines
5.9 KiB
Bash
133 lines
5.9 KiB
Bash
#!/usr/bin/env bash
|
||
# PostToolUse hook: checks a single edited file for:
|
||
# 1. Unfinished markers (TODO/FIXME/skeleton/empty fn)
|
||
# 2. Integration chain gaps (based on file type)
|
||
# Runs after Write/Edit operations only on the modified file.
|
||
set -euo pipefail
|
||
|
||
PROJECT_DIR="${AGENT_PROJECT_DIR:-$(pwd)}"
|
||
|
||
# Suppress during brainstorming / exploratory sessions
|
||
if [ -f "${PROJECT_DIR}/.agents/.suppress-hooks" ]; then
|
||
exit 0
|
||
fi
|
||
|
||
f="${AGENT_EDITED_FILE:-$AGENT_FILE}"
|
||
if [ ! -f "$f" ]; then
|
||
exit 0
|
||
fi
|
||
|
||
issues=()
|
||
ext="${f##*.}"
|
||
basename=$(basename "$f")
|
||
|
||
# ============================================================
|
||
# PART 1: Unfinished markers scan
|
||
# ============================================================
|
||
|
||
if [[ "$ext" =~ ^(ts|tsx|js|jsx|vue)$ ]]; then
|
||
while IFS=: read -r line_no content; do
|
||
[ -z "$line_no" ] && continue
|
||
trimmed=$(echo "$content" | sed 's/^[[:space:]]*//')
|
||
if echo "$trimmed" | grep -qE '^(TODO|FIXME|HACK|XXX):'; then
|
||
issues+=(" L${line_no}: TODO/FIXME ${trimmed:0:100}")
|
||
elif echo "$trimmed" | grep -qi 'not implemented' && echo "$trimmed" | grep -qi 'throw'; then
|
||
issues+=(" L${line_no}: [SKELETON] ${trimmed:0:100}")
|
||
elif echo "$trimmed" | grep -qE 'function [a-zA-Z_][a-zA-Z0-9_]*\s*\(\s*\)\s*\{\s*\}'; then
|
||
if ! echo "$trimmed" | grep -q 'return'; then
|
||
issues+=(" L${line_no}: [EMPTY FN] ${trimmed:0:100}")
|
||
fi
|
||
elif echo "$trimmed" | grep -qE 'const [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*\(\)\s*=>\s*\{\s*\}'; then
|
||
issues+=(" L${line_no}: [EMPTY ARROW FN] ${trimmed:0:100}")
|
||
fi
|
||
done < <(grep -nE 'TODO:|FIXME:|HACK:|XXX:|not implemented|function [a-zA-Z_][a-zA-Z0-9_]*\s*\(\s*\)\s*\{\s*\}|const [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*\(\)\s*=>\s*\{\s*\}' "$f" 2>/dev/null || true)
|
||
elif [[ "$ext" == "java" ]]; then
|
||
while IFS=: read -r line_no content; do
|
||
[ -z "$line_no" ] && continue
|
||
trimmed=$(echo "$content" | sed 's/^[[:space:]]*//')
|
||
if echo "$trimmed" | grep -qE '^(TODO|FIXME|HACK|XXX):'; then
|
||
issues+=(" L${line_no}: TODO/FIXME ${trimmed:0:100}")
|
||
elif echo "$trimmed" | grep -qE 'UnsupportedOperationException'; then
|
||
issues+=(" L${line_no}: [SKELETON] UnsupportedOperationException")
|
||
fi
|
||
done < <(grep -nE 'TODO:|FIXME:|HACK:|XXX:|UnsupportedOperationException' "$f" 2>/dev/null || true)
|
||
fi
|
||
|
||
# ============================================================
|
||
# PART 2: Integration chain gap detection
|
||
# ============================================================
|
||
|
||
# Check 1: Java Controller 变更 → 提取映射路径,检查 Web/UniApp 请求层是否有引用
|
||
if [[ "$f" == *"gym-manage-api/"*.java ]] && grep -q '@RestController\|@Controller' "$f" 2>/dev/null; then
|
||
# 提取 @RequestMapping/@GetMapping/@PostMapping/@PutMapping/@DeleteMapping 的路径
|
||
paths=$(grep -oE '@(Request|Get|Post|Put|Delete)Mapping\([^)]*' "$f" 2>/dev/null \
|
||
| grep -oE '"/[^"]+"' | tr -d '"' | sort -u || true)
|
||
if [ -n "$paths" ]; then
|
||
# 与类级 @RequestMapping 前缀合并
|
||
prefix=$(grep -oE '@RequestMapping\([^)]*' "$f" 2>/dev/null | grep -oE '"/[^"]+"' | tr -d '"' | head -1 || true)
|
||
while IFS= read -r p; do
|
||
[ -z "$p" ] && continue
|
||
# 已含 /api 绝对路径的不再加类级前缀,避免双写
|
||
case "$p" in
|
||
/api/*) full="$p" ;;
|
||
*) full="${prefix}${p}" ;;
|
||
esac
|
||
# 去掉 Spring 模板变量({id} 等)用于模糊匹配
|
||
key=$(echo "$full" | sed 's/{[^}]*}//g')
|
||
[ -z "$key" ] && continue
|
||
hits=$(grep -rl -- "$key" "${PROJECT_DIR}/gym-manage-web/src/api" "${PROJECT_DIR}/gym-manage-uniapp/api" "${PROJECT_DIR}/gym-manage-coach-uniapp/api" 2>/dev/null | wc -l | tr -d ' ' || true)
|
||
if [ "$hits" -eq 0 ]; then
|
||
issues+=(" [GAP] 接口 '${full}' 在 Web/UniApp 请求层无引用(gym-manage-web/src/api、uniapp/api、coach-uniapp/api)")
|
||
fi
|
||
done <<< "$paths"
|
||
fi
|
||
fi
|
||
|
||
# Check 2: Web API 请求层变更 → 检查页面/store 有引用
|
||
if [[ "$f" == *"gym-manage-web/src/api/"*".api.ts" ]]; then
|
||
new_fns=$(grep -oE '^export (async )?function [a-zA-Z_][a-zA-Z0-9_]*|^export const [a-zA-Z_][a-zA-Z0-9_]*\s*=' "$f" 2>/dev/null \
|
||
| sed -E 's/^export (async )?function //; s/^export const //; s/[[:space:]]*=.*//' | sort -u || true)
|
||
if [ -n "$new_fns" ]; then
|
||
while IFS= read -r fn; do
|
||
[ -z "$fn" ] && continue
|
||
hits=$(grep -rl -- "\b${fn}\b" "${PROJECT_DIR}/gym-manage-web/src/views" "${PROJECT_DIR}/gym-manage-web/src/stores" "${PROJECT_DIR}/gym-manage-web/src/components" 2>/dev/null | wc -l | tr -d ' ' || true)
|
||
if [ "$hits" -eq 0 ]; then
|
||
issues+=(" [GAP] Web API '${fn}' 在 src/views|stores|components 无调用方")
|
||
fi
|
||
done <<< "$new_fns"
|
||
fi
|
||
fi
|
||
|
||
# Check 3: UniApp 请求层变更 → 检查 pages 有引用
|
||
if [[ "$f" == *"gym-manage-uniapp/api/"*.js ]] || [[ "$f" == *"gym-manage-coach-uniapp/api/"*.js ]]; then
|
||
uniapp_root="gym-manage-uniapp"
|
||
[[ "$f" == *"coach-uniapp"* ]] && uniapp_root="gym-manage-coach-uniapp"
|
||
new_fns=$(grep -oE '^export (async )?function [a-zA-Z_][a-zA-Z0-9_]*|^export const [a-zA-Z_][a-zA-Z0-9_]*\s*=' "$f" 2>/dev/null \
|
||
| sed -E 's/^export (async )?function //; s/^export const //; s/[[:space:]]*=.*//' | sort -u || true)
|
||
if [ -n "$new_fns" ]; then
|
||
while IFS= read -r fn; do
|
||
[ -z "$fn" ] && continue
|
||
hits=$(grep -rl -- "${fn}" "${PROJECT_DIR}/${uniapp_root}/pages" 2>/dev/null | wc -l | tr -d ' ' || true)
|
||
if [ "$hits" -eq 0 ]; then
|
||
issues+=(" [GAP] ${uniapp_root} API '${fn}' 在 pages/ 无调用方")
|
||
fi
|
||
done <<< "$new_fns"
|
||
fi
|
||
fi
|
||
|
||
# ============================================================
|
||
# REPORT
|
||
# ============================================================
|
||
|
||
if [ ${#issues[@]} -gt 0 ]; then
|
||
echo ""
|
||
echo "[COMPLETENESS] $(basename "$f") 检查发现 ${#issues[@]} 个问题:"
|
||
for issue in "${issues[@]:0:20}"; do
|
||
echo "$issue"
|
||
done
|
||
if [ ${#issues[@]} -gt 20 ]; then
|
||
echo " ... 及其他 $((${#issues[@]} - 20)) 处"
|
||
fi
|
||
echo ""
|
||
fi
|