chore: 参考 novavis 优化 Agent 配置体系
新增 AGENTS.md 规则(§1-§22)、Hook 脚本和可复用工具脚本: - 重构 AGENTS.md:新增 16 条规则(任务分解、双轨验证、骨架占位禁止、 测试命令退出码保留、工具脚本可复用、完成前验证门禁、Flaky 测试门禁等) - 新增 .trae/hooks/:session-start / pre-agent-check / check-completeness / stop-check - 新增 .trae/settings.json:关联 4 个 Hook 到 SessionStart/PreToolUse/PostToolUse/Stop - 新增 scripts/:flaky-scan.sh / check-task-checklist.sh / check-issue-checklist.sh - 调整 .gitignore:保留 hooks/settings 纳入版本控制,忽略运行时状态文件
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
# PostToolUse hook: 检查单个编辑文件中的未完成标记
|
||||
# 在 Write/Edit 操作后对修改的文件运行
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="${AGENT_PROJECT_DIR:-$(pwd)}"
|
||||
|
||||
# Suppress during brainstorming / exploratory sessions
|
||||
if [ -f "${PROJECT_DIR}/.trae/.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 '^\s*(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
|
||||
fi
|
||||
done < <(grep -nE 'TODO:|FIXME:|HACK:|XXX:|not implemented|function [a-zA-Z_][a-zA-Z0-9_]*\s*\(\s*\)\s*\{\s*\}' "$f" 2>/dev/null || true)
|
||||
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,73 @@
|
||||
#!/usr/bin/env bash
|
||||
# PreToolUse hook: 写操作前根据文件路径输出 Skill 提示
|
||||
# 不阻止操作,仅输出提示信息
|
||||
set -euo pipefail
|
||||
|
||||
f="${AGENT_EDITED_FILE:-$AGENT_FILE}"
|
||||
[ -z "$f" ] && exit 0
|
||||
[ ! -f "$f" ] && [ ! -e "$f" ] && exit 0
|
||||
|
||||
basename=$(basename "$f")
|
||||
|
||||
# Skill 提示: 命中时输出,供 agent 决策(AGENTS.md §3 Skill-First)
|
||||
skill_hint() {
|
||||
local name="$1"; shift
|
||||
echo " → [SKILL] $name: $*"
|
||||
}
|
||||
|
||||
# 检查 1: 算法文件 → 提示 TDD + 交叉验证
|
||||
if [[ "$f" == *"/algorithms/"* ]]; then
|
||||
echo ""
|
||||
echo "[PRE-CHECK] 算法变更: $basename"
|
||||
skill_hint "tdd" "RED-GREEN-REFACTOR 测试优先"
|
||||
skill_hint "systematic-debugging" "算法结果须与传统典籍/已知实现交叉验证"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 检查 2: 测试文件 → 提示测试相关 skill
|
||||
if [[ "$basename" == *.test.* || "$basename" == *.spec.* || "$f" == *"/__tests__/"* ]]; then
|
||||
echo ""
|
||||
echo "[PRE-CHECK] 测试文件变更: $basename"
|
||||
skill_hint "tdd" "RED-GREEN-REFACTOR 测试优先"
|
||||
skill_hint "systematic-debugging" "测试失败先定位根因再修复"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 检查 3: 组件文件 → 提示前端设计 skill
|
||||
if [[ "$f" == *"/components/"* && "$basename" == *.vue ]]; then
|
||||
echo ""
|
||||
echo "[PRE-CHECK] 组件变更: $basename"
|
||||
skill_hint "frontend-design" "UniApp 组件设计最佳实践"
|
||||
skill_hint "impeccable" "UI 审查与视觉打磨"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 检查 4: 页面文件 → 提示全链路验证
|
||||
if [[ "$f" == *"/pages/"* && "$basename" == *.vue ]]; then
|
||||
echo ""
|
||||
echo "[PRE-CHECK] 页面变更: $basename"
|
||||
skill_hint "frontend-design" "页面布局与交互设计"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 检查 5: 国际化文件 → 提示同步所有语言
|
||||
if [[ "$f" == *"/locales/"* && "$basename" == *.ts ]]; then
|
||||
echo ""
|
||||
echo "[PRE-CHECK] 国际化变更: $basename"
|
||||
echo " → 确保同步更新所有 locales/*.ts 文件"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 检查 6: 文档/注释类 → 中文文档规范
|
||||
if [[ "$basename" == *.md || "$basename" == *.adoc ]]; then
|
||||
echo ""
|
||||
echo "[PRE-CHECK] 文档变更: $basename"
|
||||
skill_hint "chinese-documentation" "中文排版/术语/结构规范"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env bash
|
||||
# SessionStart hook: 启动时输出项目关键上下文和提醒
|
||||
# 不阻止会话启动,仅输出提示信息
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="${AGENT_PROJECT_DIR:-$(pwd)}"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " 万事宜 (Everything Is Suitable) — 项目上下文"
|
||||
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_ALGORITHM_CHANGE=""
|
||||
HAS_TEST_CHANGE=""
|
||||
HAS_LOCALE_CHANGE=""
|
||||
HAS_COMPONENT_CHANGE=""
|
||||
|
||||
while IFS= read -r f; do
|
||||
[ -z "$f" ] && continue
|
||||
case "$f" in
|
||||
*/algorithms/*) HAS_ALGORITHM_CHANGE="yes" ;;
|
||||
*/__tests__/*|*.spec.*|*.test.*) HAS_TEST_CHANGE="yes" ;;
|
||||
*/locales/*) HAS_LOCALE_CHANGE="yes" ;;
|
||||
*/components/*) HAS_COMPONENT_CHANGE="yes" ;;
|
||||
esac
|
||||
done <<< "$CHANGED_FILES"
|
||||
|
||||
# 变更提醒
|
||||
if [ -n "$HAS_ALGORITHM_CHANGE" ]; then
|
||||
echo ""
|
||||
echo " ⚠️ 检测到算法文件变更,请确保:"
|
||||
echo " ┌─ 测试覆盖 ─────────────────────────────────┐"
|
||||
echo " │ npx vitest run algorithms/__tests__/ │"
|
||||
echo " ├─ 交叉验证 ─────────────────────────────────┤"
|
||||
echo " │ 算法结果须与传统典籍/已知实现交叉验证 │"
|
||||
echo " └────────────────────────────────────────────────┘"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ -n "$HAS_TEST_CHANGE" ]; then
|
||||
echo " ⚠️ 检测到测试文件变更,请确保:"
|
||||
echo " ┌─ 运行验证 ─────────────────────────────────┐"
|
||||
echo " │ npm test │"
|
||||
echo " ├─ Flaky 检查 ────────────────────────────────┤"
|
||||
echo " │ 新增测试至少连续运行 3 次确认稳定性 │"
|
||||
echo " └────────────────────────────────────────────────┘"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ -n "$HAS_LOCALE_CHANGE" ]; then
|
||||
echo " ⚠️ 检测到国际化文件变更,请确保同步所有语言:"
|
||||
echo " locales/zh-CN.ts locales/en.ts locales/ja.ts ..."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ -n "$HAS_COMPONENT_CHANGE" ]; then
|
||||
echo " ⚠️ 检测到组件变更,请确保:"
|
||||
echo " ┌─ 单元测试 ─────────────────────────────────┐"
|
||||
echo " │ npx vitest run components/ │"
|
||||
echo " ├─ E2E 测试 ──────────────────────────────────┤"
|
||||
echo " │ npm run test:e2e │"
|
||||
echo " └────────────────────────────────────────────────┘"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Skill 推荐
|
||||
echo " 🎯 Skill 推荐(基于变更类型):"
|
||||
if [ -n "$HAS_ALGORITHM_CHANGE" ]; then
|
||||
echo " 算法 → TDD / systematic-debugging"
|
||||
fi
|
||||
if [ -n "$HAS_TEST_CHANGE" ]; then
|
||||
echo " 测试 → TDD / systematic-debugging"
|
||||
fi
|
||||
if [ -n "$HAS_COMPONENT_CHANGE" ]; then
|
||||
echo " 组件 → frontend-design / impeccable"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
# Stop hook: runs quick quality checks and outputs JSON result.
|
||||
# 轻量检查(<10s)
|
||||
# 残留扫描由 check-completeness.sh(PostToolUse)按文件处理,此处不重复。
|
||||
set -euo pipefail
|
||||
|
||||
cd "${AGENT_PROJECT_DIR:-$(pwd)}"
|
||||
|
||||
# Suppress during brainstorming / exploratory sessions
|
||||
if [ -f ".trae/.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/3: TypeScript type check ----
|
||||
echo '[1/3] TypeScript 类型检查...'
|
||||
APP_DIR="everything-is-suitable-uniapp"
|
||||
if [ -f "${APP_DIR}/node_modules/.bin/tsc" ]; then
|
||||
if (cd "${APP_DIR}" && npx tsc --noEmit --pretty false 2>&1); then
|
||||
echo ' tsc: PASS'
|
||||
else
|
||||
echo ' tsc: FAIL'
|
||||
HAS_ERRORS=1
|
||||
fi
|
||||
else
|
||||
echo ' tsc: SKIP(未安装依赖,运行 npm install)'
|
||||
fi
|
||||
|
||||
# ---- 2/3: TODO/FIXME 残留扫描 ----
|
||||
echo '[2/3] 代码残留扫描(TODO/FIXME/skeleton)...'
|
||||
FOUND=$(grep -rn 'TODO:\|FIXME:\|not implemented.*throw' "${APP_DIR}/src" --include='*.ts' --include='*.vue' --include='*.js' 2>/dev/null | grep -v 'node_modules' | grep -v '.test.' | head -10 || true)
|
||||
if [ -z "$FOUND" ]; then
|
||||
echo ' 残留扫描: PASS'
|
||||
else
|
||||
echo ' 残留扫描: 发现以下残留标记:'
|
||||
echo "$FOUND"
|
||||
HAS_ERRORS=1
|
||||
fi
|
||||
|
||||
# ---- 3/3: 测试文件完整性检查 ----
|
||||
echo '[3/3] 测试文件一致性检查...'
|
||||
# 检查 src 中每个 .ts/.vue 文件是否有关联测试文件
|
||||
ORPHANED=0
|
||||
while IFS= read -r src_file; do
|
||||
[ -z "$src_file" ] && continue
|
||||
base=$(basename "$src_file" .ts)
|
||||
base=$(basename "$base" .vue)
|
||||
dir=$(dirname "$src_file")
|
||||
test_file="${dir}/__tests__/${base}.test.ts"
|
||||
if [ ! -f "$test_file" ]; then
|
||||
# 跳过 index.vue 等特殊文件
|
||||
case "$base" in
|
||||
index|main|App) continue ;;
|
||||
esac
|
||||
# 跳过类型定义文件
|
||||
case "$src_file" in
|
||||
*/types/*) continue ;;
|
||||
esac
|
||||
ORPHANED=$((ORPHANED + 1))
|
||||
fi
|
||||
done < <(find "${APP_DIR}/src" -name '*.ts' -o -name '*.vue' 2>/dev/null | head -100)
|
||||
if [ "$ORPHANED" -eq 0 ]; then
|
||||
echo ' 测试文件一致性: PASS'
|
||||
else
|
||||
echo " 测试文件一致性: ${ORPHANED} 个源文件缺少关联测试(仅供参考,非强制)"
|
||||
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
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"_comment": "Trae Hook 配置。环境变量 AGENT_PROJECT_DIR 应由宿主 agent 设置为项目根目录。",
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash \"${AGENT_PROJECT_DIR}/.trae/hooks/session-start.sh\"",
|
||||
"timeout": 10000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash \"${AGENT_PROJECT_DIR}/.trae/hooks/pre-agent-check.sh\"",
|
||||
"timeout": 3000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash \"${AGENT_PROJECT_DIR}/.trae/hooks/check-completeness.sh\"",
|
||||
"timeout": 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash \"${AGENT_PROJECT_DIR}/.trae/hooks/stop-check.sh\"",
|
||||
"timeout": 30000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env bash
|
||||
# Issue 关闭门禁检查 — 扫描已关闭的 Issue 文档是否存在未勾选子项
|
||||
# 用法:
|
||||
# bash scripts/check-issue-checklist.sh # 检查所有 Issue
|
||||
# bash scripts/check-issue-checklist.sh --quiet # 静默模式(仅退出码)
|
||||
# bash scripts/check-issue-checklist.sh --help # 显示帮助
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
QUIET=0
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [options]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --quiet 静默模式(仅通过退出码表示结果)"
|
||||
echo " --help 显示此帮助"
|
||||
exit 0
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--quiet) QUIET=1; shift ;;
|
||||
--help) usage ;;
|
||||
*) echo "Unknown option: $1"; usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 检查 docs/plans/ 下的 Issue 文档
|
||||
PLANS_DIR="${PROJECT_DIR}/docs/plans"
|
||||
HAS_ERRORS=0
|
||||
VIOLATIONS=()
|
||||
|
||||
if [ ! -d "$PLANS_DIR" ]; then
|
||||
[ "$QUIET" -eq 0 ] && echo " 检查: ${PLANS_DIR} 不存在,跳过"
|
||||
echo '{"status":"ok"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 扫描已关闭的文档(包含 "status: closed" 或 "已关闭" 标记)
|
||||
while IFS= read -r plan_file; do
|
||||
[ -z "$plan_file" ] && continue
|
||||
basename=$(basename "$plan_file" .md)
|
||||
|
||||
# 检查是否包含 closed 标记
|
||||
if grep -qi "status.*closed\|已关闭\|已完成" "$plan_file" 2>/dev/null; then
|
||||
# 检查是否有未勾选的 checklist 项
|
||||
UNCHECKED=$(grep -cE '^\s*[-*]\s+\[[ ]\]' "$plan_file" 2>/dev/null || true)
|
||||
if [ "$UNCHECKED" -gt 0 ]; then
|
||||
VIOLATIONS+=("${basename}: ${UNCHECKED} 个未勾选子项")
|
||||
HAS_ERRORS=1
|
||||
fi
|
||||
fi
|
||||
done < <(find "$PLANS_DIR" -name '*.md' -type f 2>/dev/null)
|
||||
|
||||
if [ "$HAS_ERRORS" -eq 0 ]; then
|
||||
[ "$QUIET" -eq 0 ] && echo " Issue 门禁: PASS"
|
||||
echo '{"status":"ok"}'
|
||||
exit 0
|
||||
else
|
||||
[ "$QUIET" -eq 0 ] && echo " Issue 门禁: FAIL"
|
||||
for v in "${VIOLATIONS[@]}"; do
|
||||
echo " - $v"
|
||||
done
|
||||
echo '{"status":"error"}'
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env bash
|
||||
# Task 完成门禁检查 — 扫描已标记完成的 Task 是否存在未勾选子项
|
||||
# 用法:
|
||||
# bash scripts/check-task-checklist.sh # 检查所有 Task
|
||||
# bash scripts/check-task-checklist.sh --quiet # 静默模式(仅退出码)
|
||||
# bash scripts/check-task-checklist.sh --help # 显示帮助
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
QUIET=0
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [options]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --quiet 静默模式(仅通过退出码表示结果)"
|
||||
echo " --help 显示此帮助"
|
||||
exit 0
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--quiet) QUIET=1; shift ;;
|
||||
--help) usage ;;
|
||||
*) echo "Unknown option: $1"; usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 检查 docs/plans/ 下的 Task 文档
|
||||
PLANS_DIR="${PROJECT_DIR}/docs/plans"
|
||||
HAS_ERRORS=0
|
||||
VIOLATIONS=()
|
||||
|
||||
if [ ! -d "$PLANS_DIR" ]; then
|
||||
[ "$QUIET" -eq 0 ] && echo " 检查: ${PLANS_DIR} 不存在,跳过"
|
||||
echo '{"status":"ok"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 扫描已完成的计划文档
|
||||
while IFS= read -r plan_file; do
|
||||
[ -z "$plan_file" ] && continue
|
||||
basename=$(basename "$plan_file" .md)
|
||||
|
||||
# 检查是否包含"已完成"标记
|
||||
if grep -qi "已完成\|completed" "$plan_file" 2>/dev/null; then
|
||||
# 检查是否有未勾选的 checklist 项
|
||||
UNCHECKED=$(grep -cE '^\s*[-*]\s+\[[ ]\]' "$plan_file" 2>/dev/null || true)
|
||||
if [ "$UNCHECKED" -gt 0 ]; then
|
||||
VIOLATIONS+=("${basename}: ${UNCHECKED} 个未勾选子项")
|
||||
HAS_ERRORS=1
|
||||
fi
|
||||
fi
|
||||
done < <(find "$PLANS_DIR" -name '*.md' -type f 2>/dev/null)
|
||||
|
||||
if [ "$HAS_ERRORS" -eq 0 ]; then
|
||||
[ "$QUIET" -eq 0 ] && echo " Task 门禁: PASS"
|
||||
echo '{"status":"ok"}'
|
||||
exit 0
|
||||
else
|
||||
[ "$QUIET" -eq 0 ] && echo " Task 门禁: FAIL"
|
||||
for v in "${VIOLATIONS[@]}"; do
|
||||
echo " - $v"
|
||||
done
|
||||
echo '{"status":"error"}'
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env bash
|
||||
# Flaky test scanner — 连续运行测试检测不稳定测试
|
||||
# 用法:
|
||||
# bash scripts/flaky-scan.sh # 随机顺序运行 3 次全量测试
|
||||
# bash scripts/flaky-scan.sh --runs 5 # 指定运行次数
|
||||
# bash scripts/flaky-scan.sh --spec <文件> # 指定测试文件
|
||||
# bash scripts/flaky-scan.sh --help # 显示帮助
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
APP_DIR="${PROJECT_DIR}/everything-is-suitable-uniapp"
|
||||
|
||||
RUNS=3
|
||||
SPEC=""
|
||||
VERBOSE=0
|
||||
|
||||
# 颜色
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [options]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --runs <N> 运行次数 (default: 3)"
|
||||
echo " --spec <file> 指定测试文件"
|
||||
echo " --verbose 详细输出"
|
||||
echo " --help 显示此帮助"
|
||||
exit 0
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--runs) RUNS="$2"; shift 2 ;;
|
||||
--spec) SPEC="$2"; shift 2 ;;
|
||||
--verbose) VERBOSE=1; shift ;;
|
||||
--help) usage ;;
|
||||
*) echo "Unknown option: $1"; usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ ! -d "$APP_DIR" ]; then
|
||||
echo "错误: 未找到应用目录 ${APP_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$APP_DIR"
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " Flaky Test Scanner"
|
||||
echo " 运行次数: ${RUNS}"
|
||||
echo " 测试文件: ${SPEC:-全部}"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
PASS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
declare -a FAILED_RUNS
|
||||
|
||||
for i in $(seq 1 "$RUNS"); do
|
||||
echo ""
|
||||
echo " [${i}/${RUNS}] 运行中..."
|
||||
|
||||
if [ -n "$SPEC" ]; then
|
||||
CMD="npx vitest run --reporter=verbose \"$SPEC\""
|
||||
else
|
||||
CMD="npx vitest run --reporter=verbose"
|
||||
fi
|
||||
|
||||
if [ "$VERBOSE" -eq 1 ]; then
|
||||
echo " → $CMD"
|
||||
fi
|
||||
|
||||
set +e
|
||||
OUTPUT=$(eval "$CMD" 2>&1)
|
||||
EXIT_CODE=$?
|
||||
set -e
|
||||
|
||||
if [ "$EXIT_CODE" -eq 0 ]; then
|
||||
echo -e " [${i}/${RUNS}] ${GREEN}PASS${NC}"
|
||||
PASS_COUNT=$((PASS_COUNT + 1))
|
||||
else
|
||||
echo -e " [${i}/${RUNS}] ${RED}FAIL${NC} (exit code: ${EXIT_CODE})"
|
||||
FAIL_COUNT=$((FAIL_COUNT + 1))
|
||||
FAILED_RUNS+=("$i")
|
||||
if [ "$VERBOSE" -eq 1 ]; then
|
||||
echo " --- 失败输出 ---"
|
||||
echo "$OUTPUT" | tail -50
|
||||
echo " ---"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " 结果汇总"
|
||||
echo " PASS: ${PASS_COUNT}/${RUNS}"
|
||||
echo " FAIL: ${FAIL_COUNT}/${RUNS}"
|
||||
|
||||
if [ "$FAIL_COUNT" -eq 0 ]; then
|
||||
echo -e " ${GREEN}结论: 未检测到 flaky 测试,测试稳定 ✓${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e " ${RED}结论: 检测到 flaky 测试${NC}"
|
||||
echo " 失败运行: ${FAILED_RUNS[*]}"
|
||||
echo ""
|
||||
echo " 常见 flaky 根因:"
|
||||
echo " ① vi.clearAllMocks() 不清除 mockResolvedValue 实现"
|
||||
echo " ② 模块级可变 let mock 变量被测试修改未还原"
|
||||
echo " ③ vi.stubGlobal 全局替换未在 afterEach 恢复"
|
||||
echo " ④ waitFor 未 import"
|
||||
echo ""
|
||||
echo " 建议: 对失败运行使用 --verbose 查看详细输出"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user