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:
2026-08-04 12:02:12 +08:00
parent 4ba4af2f53
commit 5c65139fd8
14 changed files with 888 additions and 2 deletions
+132
View File
@@ -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
+61
View File
@@ -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 3AGENTS.md §22"
echo ""
exit 0
fi
+59
View File
@@ -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 Controllergym-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 testWeb+ flaky-scan(§22"
echo ""
echo " 详细清单: AGENT.md(架构/命令/端口)+ AGENTS.md §13/§17"
echo ""
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
# Stop hook: runs quick quality checks and outputs JSON result.
# 轻量检查(<15s),不编译后端 — 重型检查留给 pre-push 和 CI。
# 残留扫描由 check-completeness.shPostToolUse)按文件处理,此处不重复。
# 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
+49
View File
@@ -0,0 +1,49 @@
# 系统化调试协议(Systematic Debugging
> 通用规则见 `AGENTS.md` §15(系统调试优先:先定位根因再修复,禁止"试试看")。
## 适用范围
本项目任何层级的 Bug:后端 Javagym-manage-api)、Gateway 路由、Web 前端(gym-manage-web)、
UniApp 小程序(gym-manage-uniapp / gym-manage-coach-uniapp)、数据库(PostgreSQL:55432)。
## 协议步骤
### Step 1 — 复现确认
- 拿到最小复现步骤:单测试 / 单操作 / 单输入
- 区分环境:本地 DevWeb:3002)、Docker`docker-compose logs -f backend/frontend/postgres`)、
微信开发者工具(小程序,`urlCheck: false` 已关闭 URL 校验)
### Step 2 — 分层隔离
| 层级 | 排查入口 | 快速验证 |
|------|----------|---------|
| 后端 Java | Gateway/App 控制台日志(DEBUG 输出 stdout | `cd gym-manage-api && mvn compile` |
| Gateway 路由 | `/api/**` → 8084 转发日志 | `docker-compose logs -f gateway` |
| 数据库 | SQL / 数据不一致 | `psql -U novalon -d manage_system -p 55432` |
| Web 前端 | DevTools Console + Network | `cd gym-manage-web && pnpm test` |
| UniApp | 微信开发者工具控制台 | jest(`gym-manage-uniapp` |
按上表顺序定位,**一次只换一个变量**。
### Step 3 — 假设驱动
- 对每个假设写下一行验证方法,先验证最可能/最便宜的假设
- Java 逻辑:加 SLF4J DEBUG 日志或补单测缩小范围
- 接口联调:用 Swagger`:8084/swagger-ui.html`)直调接口确认后端,再查前端参数
- UIVue DevTools 组件树 + Network 请求/响应比对
### Step 4 — 检查最近变更
```bash
git diff --name-only HEAD~5 # 最近 5 次提交变更
git log --oneline -10 # 最近 10 条提交
```
### Step 5 — 运行受影响测试
```bash
cd gym-manage-api && mvn test -pl <受影响模块> -am # 后端
cd gym-manage-web && pnpm test # Web 前端
cd gym-manage-web && pnpm test:e2e # Web E2E
```
### Step 6 — 确认根因后才提修复方案
- 记录完整复现路径与根因证据
- 修复后走完整验证:受影响单测 + 类型检查(`vue-tsc`+ 真实后端联调
- **Mock 通过 ≠ 功能可用**AGENTS.md §13)——需真实后端验证
+51
View File
@@ -0,0 +1,51 @@
{
"_comment": "Generic agent hook configuration. Environment variable AGENT_PROJECT_DIR should be set by the host agent to the project root. If unavailable, hooks fall back to pwd.",
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash \"${AGENT_PROJECT_DIR}/.agents/hooks/session-start.sh\"",
"timeout": 10000
}
]
}
],
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash \"${AGENT_PROJECT_DIR}/.agents/hooks/pre-agent-check.sh\"",
"timeout": 3000
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash \"${AGENT_PROJECT_DIR}/.agents/hooks/check-completeness.sh\"",
"timeout": 5000
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash \"${AGENT_PROJECT_DIR}/.agents/hooks/stop-check.sh\"",
"timeout": 30000
}
]
}
]
}
}