chore(config): 参考 novavis 完善 Agent 配置体系
- 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)
This commit was merged in pull request #57.
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
# PreToolUse hook: 写操作前根据文件路径输出集成提醒
|
||||
# 不阻止操作,仅输出提示信息
|
||||
set -euo pipefail
|
||||
|
||||
f="${AGENT_EDITED_FILE:-$AGENT_FILE}"
|
||||
[ -z "$f" ] && exit 0
|
||||
[ ! -e "$f" ] && exit 0
|
||||
|
||||
basename=$(basename "$f")
|
||||
|
||||
# 检查 1: 新建/修改 Java Controller → 提醒全链路(API → Service → Web/UniApp)
|
||||
if [[ "$f" == *"gym-manage-api/"*.java ]]; then
|
||||
if grep -q '@RestController\|@Controller' "$f" 2>/dev/null; then
|
||||
echo ""
|
||||
echo "[PRE-CHECK] 修改 Java Controller: $basename"
|
||||
echo " → 全链路要求(AGENTS.md §13/§14):"
|
||||
echo " 1) Service 层实现业务逻辑(Controller 不直连 Repository)"
|
||||
echo " 2) 同步 gym-manage-web/src/api/*.api.ts(请求路径/参数/类型)"
|
||||
echo " 3) 同步 gym-manage-uniapp/api/*.js 与 coach-uniapp/api/*.js"
|
||||
echo " 4) Swagger 注解齐全(@ApiOperation/@ApiModelProperty)"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
# 仅 Service/其它 Java 文件:提醒写单测
|
||||
echo ""
|
||||
echo "[PRE-CHECK] 修改后端 Java: $basename"
|
||||
echo " → 业务逻辑改动需配套 JUnit 测试(cd gym-manage-api && mvn test -pl <模块> -am)"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 检查 2: 修改 Web 请求层 → 提醒同步类型与页面
|
||||
if [[ "$f" == *"gym-manage-web/src/api/"*".api.ts" ]]; then
|
||||
echo ""
|
||||
echo "[PRE-CHECK] Web API 接口变更: $basename"
|
||||
echo " → 同步更新:"
|
||||
echo " 1) 接口路径/参数与后端 Controller 注解一致"
|
||||
echo " 2) 类型定义与后端 DTO/VO 字段对齐(camelCase)"
|
||||
echo " 3) 页面调用方(src/views、src/stores)使用新签名"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 检查 3: 修改 UniApp 请求层 → 提醒同步
|
||||
if [[ "$f" == *"gym-manage-uniapp/api/"*.js ]] || [[ "$f" == *"gym-manage-coach-uniapp/api/"*.js ]]; then
|
||||
echo ""
|
||||
echo "[PRE-CHECK] UniApp API 变更: $basename"
|
||||
echo " → 同步更新: 页面调用方(pages/*)+ 后端接口契约 + Web 端同名接口"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 检查 4: 新建/修改测试文件 → 提醒 flaky 门禁
|
||||
if [[ "$f" == *".test."* ]] || [[ "$f" == *".spec."* ]] || [[ "$f" == *"Test.java" ]]; then
|
||||
echo ""
|
||||
echo "[PRE-CHECK] 测试文件: $basename"
|
||||
echo " → Web 测试(vitest)需通过 bash scripts/flaky-scan.sh --spec <文件> --runs 3(AGENTS.md §22)"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
# SessionStart hook: 启动时输出项目关键上下文和集成提醒
|
||||
# 不阻止会话启动,仅输出提示信息
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="${AGENT_PROJECT_DIR:-$(pwd)}"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " Gym Manage — 项目上下文"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# 当前分支
|
||||
BRANCH=$(git branch --show-current 2>/dev/null || echo "未知")
|
||||
echo " 分支: $BRANCH"
|
||||
|
||||
# 变更文件统计
|
||||
STAGED=$(git diff --cached --name-only 2>/dev/null | wc -l | tr -d ' ')
|
||||
UNSTAGED=$(git diff --name-only 2>/dev/null | wc -l | tr -d ' ')
|
||||
echo " 变更: ${STAGED} 个暂存, ${UNSTAGED} 个未暂存"
|
||||
|
||||
# 检测关键文件变更
|
||||
CHANGED_FILES=$(git diff --name-only HEAD 2>/dev/null; git diff --cached --name-only 2>/dev/null; git ls-files --others --exclude-standard 2>/dev/null | sort -u)
|
||||
HAS_API_CHANGE=""
|
||||
HAS_WEB_API_CHANGE=""
|
||||
HAS_UNIAPP_CHANGE=""
|
||||
|
||||
while IFS= read -r f; do
|
||||
[ -z "$f" ] && continue
|
||||
case "$f" in
|
||||
gym-manage-api/*.java) HAS_API_CHANGE="yes" ;;
|
||||
gym-manage-web/src/api/*.api.ts) HAS_WEB_API_CHANGE="yes" ;;
|
||||
gym-manage-uniapp/api/*.js|gym-manage-coach-uniapp/api/*.js) HAS_UNIAPP_CHANGE="yes" ;;
|
||||
esac
|
||||
done <<< "$CHANGED_FILES"
|
||||
|
||||
# 集成提醒
|
||||
if [ -n "$HAS_API_CHANGE" ] || [ -n "$HAS_WEB_API_CHANGE" ] || [ -n "$HAS_UNIAPP_CHANGE" ]; then
|
||||
echo ""
|
||||
echo " ⚠️ 检测到接口相关文件变更,请确保全链路完整性:"
|
||||
echo ""
|
||||
echo " ┌─ Layer 1: Java Controller(gym-manage-api)─────────────┐"
|
||||
echo " │ @RestController / @RequestMapping / Service 层 │"
|
||||
echo " ├─ Layer 2: Web 请求层 ──────────────────────────────────┤"
|
||||
echo " │ gym-manage-web/src/api/*.api.ts(请求路径/参数/类型) │"
|
||||
echo " ├─ Layer 3: UniApp 请求层 ───────────────────────────────┤"
|
||||
echo " │ gym-manage-uniapp/api/*.js + coach-uniapp/api/*.js │"
|
||||
echo " ├─ Layer 4: 页面/组件 ───────────────────────────────────┤"
|
||||
echo " │ gym-manage-web/src/views/* + uniapp pages/* │"
|
||||
echo " └─ Layer 5: 测试验证 ────────────────────────────────────┘"
|
||||
echo " mvn test(后端)+ pnpm test(Web)+ flaky-scan(§22)"
|
||||
echo ""
|
||||
echo " 详细清单: AGENT.md(架构/命令/端口)+ AGENTS.md §13/§17"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env bash
|
||||
# Stop hook: runs quick quality checks and outputs JSON result.
|
||||
# 轻量检查(<15s),不编译后端 — 重型检查留给 pre-push 和 CI。
|
||||
# 残留扫描由 check-completeness.sh(PostToolUse)按文件处理,此处不重复。
|
||||
# Outputs diagnostic info to stderr, final JSON status to stdout.
|
||||
set -euo pipefail
|
||||
cd "${AGENT_PROJECT_DIR:-$(pwd)}"
|
||||
|
||||
# Suppress during brainstorming / exploratory sessions
|
||||
if [ -f ".agents/.suppress-hooks" ]; then
|
||||
echo '{"status":"ok","mode":"suppressed"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
HAS_ERRORS=0
|
||||
|
||||
# All diagnostics go to stderr
|
||||
{
|
||||
echo '=== [STOP HOOK QUALITY GATE] ==='
|
||||
|
||||
# ---- 1/2: 变更文件残留扫描(未完成标记 + 调试遗留)----
|
||||
echo '[1/2] 变更文件残留扫描...'
|
||||
CHANGED=$( { git diff --name-only HEAD 2>/dev/null; git diff --cached --name-only 2>/dev/null; } | sort -u )
|
||||
LEFTOVERS=""
|
||||
while IFS= read -r f; do
|
||||
[ -z "$f" ] && continue
|
||||
[ -f "$f" ] || continue
|
||||
case "$f" in
|
||||
gym-manage-web/src/*|gym-manage-uniapp/**|gym-manage-coach-uniapp/**|*.java|*.ts|*.tsx|*.js|*.vue)
|
||||
if grep -nE '\.only\(|\.skip\(' "$f" 2>/dev/null | grep -qE '\.(only|skip)\('; then
|
||||
LEFTOVERS="${LEFTOVERS} [LEFTOVER] ${f}: .only()/.skip() 残留\n"
|
||||
fi
|
||||
if grep -nE 'console\.log' "$f" 2>/dev/null | grep -qv 'console\.log(`' 2>/dev/null; then
|
||||
LEFTOVERS="${LEFTOVERS} [LEFTOVER] ${f}: console.log 残留\n"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done <<< "$CHANGED"
|
||||
|
||||
if [ -n "$LEFTOVERS" ]; then
|
||||
printf '%b' "$LEFTOVERS"
|
||||
echo " ⚠️ 存在调试/测试残留(未阻断,提交前请清理)"
|
||||
else
|
||||
echo ' 残留扫描: PASS'
|
||||
fi
|
||||
|
||||
# ---- 2/2: 未提交任务状态检查 ----
|
||||
echo '[2/2] 任务状态检查...'
|
||||
if [ -d ".pi/todos" ]; then
|
||||
OPEN=$(ls .pi/todos/*.md 2>/dev/null | wc -l | tr -d ' ')
|
||||
echo " 打开的任务: ${OPEN}(如需关闭请使用 todo 工具或手动更新)"
|
||||
else
|
||||
echo ' 任务目录: 不存在'
|
||||
fi
|
||||
|
||||
# ---- Summary ----
|
||||
if [ "$HAS_ERRORS" -eq 0 ]; then
|
||||
echo '=== [STOP HOOK: ALL PASS] ==='
|
||||
else
|
||||
echo '=== [STOP HOOK: ERRORS FOUND] ==='
|
||||
fi
|
||||
} >&2
|
||||
|
||||
# Output final status as JSON
|
||||
if [ "$HAS_ERRORS" -eq 0 ]; then
|
||||
echo '{"status":"ok"}'
|
||||
else
|
||||
echo '{"status":"error"}'
|
||||
fi
|
||||
Reference in New Issue
Block a user