帝国军刀¶
You can translate it into your own country's language.
#!/bin/bash
# ==============================================================================
# 🗡️ 帝国军刀 V1.1
# 审计:使用阻断模式对审计目标展开修复 命令:./qas.sh <directory>
# 勘察:使用侦查模式获得全貌以制订策略 命令:./qas.sh <directory> -n
# 测试:对关联的测试进行审计并单元测试 命令:./qas.sh <directory> -t
# 更新:新增AI模式,降低token以及干扰 命令:./qas.sh <directory> -a
# 场景:对指定目录所有文件进行全面静态审计,并可选执行关联测试的单元测试。
# ==============================================================================
# ===============
# 准备开始:环境配置
# ===============
# --------------------------------------------------------------
# --- 1. 零容忍,发现问题立即终止 ---
# --------------------------------------------------------------
set -euo pipefail
# --------------------------------------------------------------
# --- 2. 以当前位置为帝国中心(建议在仓库根执行) ---
# --------------------------------------------------------------
export PYTHONPATH=$(pwd)
# --------------------------------------------------------------
# --- 3. PyCourt 语言切换:默认为中文,外部可覆盖PYCOURT_LANG=en ---
# --------------------------------------------------------------
export PYCOURT_LANG="${PYCOURT_LANG:-zh}"
# --------------------------------------------------------------
# --- 4. 通用输出函数(与匕首保持一致风格) ---
# --------------------------------------------------------------
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
print_chapter_header() {
# 人类模式下输出章节标题;AI 模式下默认关闭以减少噪音
if [[ "${PYCOURT_UI_MODE:-human}" != "ai" ]]; then
echo -e "\n${BLUE}================== $1 ==================${NC}"
fi
}
print_sub_header() {
# 人类模式下输出小节标题;AI 模式下默认关闭以减少噪音
if [[ "${PYCOURT_UI_MODE:-human}" != "ai" ]]; then
echo -e "\n${YELLOW}--- $1 ---${NC}"
fi
}
print_success() {
echo -e "${GREEN}✅ $1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_error() {
echo -e "${RED}❌ $1${NC}"
}
# ---------------------------------------------------
# --- 5. 外部工具封装与可用性检查---
# 只封装工具本身,具体参数通过 pyproject.toml 配置
# ---------------------------------------------------
run_mypy() {
poetry run mypy "$@"
}
run_pyright() {
poetry run pyright "$@"
}
run_bandit() {
poetry run bandit "$@"
}
run_ruff_check() {
poetry run ruff check "$@"
}
run_ruff_format() {
poetry run ruff format "$@"
}
# 报告缺少的外部工具
check_tool() {
local cmd="$1"
local desc="$2"
if ! poetry run "$cmd" --version >/dev/null 2>&1; then
print_warning "缺少外部工具: $cmd ($desc),将跳过对应审计步骤。"
return 1
fi
return 0
}
# --------------------------------------------------------------
# --- 6. 参数解析与命令 ---
# --------------------------------------------------------------
AUDIT_DIR="" # 审计目标目录(位置参数)
AUDIT_NON_BLOCKING=0 # 是否启用非阻断模式,默认 0:阻断模式。
QAS_ENABLE_TEST_PHASE=${QAS_ENABLE_TEST_PHASE:-0} # 是否启用测试阶段,默认 0:关闭。
while [[ $# -gt 0 ]]; do
case "$1" in
-n)
AUDIT_NON_BLOCKING=1
shift
;;
-t)
QAS_ENABLE_TEST_PHASE=1
shift
;;
-a|--ai)
# AI 模式:切换为 AI 友好输出(由后端根据 PYCOURT_AUDIENCE/PYCOURT_UI_MODE 控制)
export PYCOURT_AUDIENCE=${PYCOURT_AUDIENCE:-ai}
export PYCOURT_UI_MODE=${PYCOURT_UI_MODE:-ai}
# 若当前语言为中文系 (zh*),在 AI 模式下自动切换到英文文案,
# 用户若已显式设置为 en/其他语言则保留其选择。
if [[ "${PYCOURT_LANG:-}" == zh* ]]; then
export PYCOURT_LANG=en
fi
shift
;;
--)
shift
break
;;
-*)
echo "❌ 未知参数: $1" >&2
exit 1
;;
*)
if [[ -z "$AUDIT_DIR" ]]; then
AUDIT_DIR="$1"
shift
else
echo "❌ 多余参数: $1" >&2
exit 1
fi
;;
esac
done
if [ -z "$AUDIT_DIR" ]; then
echo "❌ 用法错误: 必须提供审计目录,例如: ./qas.sh src/module" >&2
exit 1
fi
# --------------------------------------------------------------
# --- 7. 是否阻断的统一封装 ---
# --------------------------------------------------------------
run_or_warn() {
"$@" || {
if [ "$AUDIT_NON_BLOCKING" = "0" ]; then
print_error "发现问题,审计终止!" # 在阻断模式下,任何失败立即退出;
exit 1
else
print_warning "发现问题!继续侦查..." # 在非阻断模式下,仅打印问题报告。
fi
}
}
# --------------------------------------------------------------
# --- 8. PyCourt 统一审计入口(Scope 级别)
# --------------------------------------------------------------
run_judges() {
local codes="$1"
local target="$2"
run_or_warn poetry run pycourt scope "$target" --select "$codes"
}
# ===============================================================================
# 审计内容编排与封装
# ===============================================================================
run_static_audit_on_target() {
local audit_target="$1"
print_chapter_header "第一阶段:帝国军刀 - 对【${audit_target}】进行静态审计"
# =======================================================================
# 第一章:架构生死线
# 审判最根本的、动摇项目根基的“叛国罪”。
# =======================================================================
print_chapter_header "第一章:架构生死线"
# ---------------------------------------------------
# 1.1 架构位置:循环依赖 / 门面规范 / 依赖倒置
# 先确保文件站在正确的架构世界,否则后续所有结果都失去意义。
# ---------------------------------------------------
# 禁止使用TYPE_CHECKING等手段引入循环依赖
print_sub_header "1.1.1 循环依赖法庭 (TC001)"
run_judges "TC001" "$audit_target"
# 禁止在门面层使用领域逻辑并聚合导出
print_sub_header "1.1.2 门面纪律法庭 (RE001)"
run_judges "RE001,RE002,RE003" "$audit_target"
# 禁止违反依赖倒置原则直接依赖具体实现
print_sub_header "1.1.3 依赖倒置法庭 (DI001)"
run_judges "DI001" "$audit_target"
# ---------------------------------------------------
# 1.2 事务与边界:UoW / 边界 DTO / 向量触发协议
# 在架构位置正确的前提下,优先保证事务语义与边界契约的正确。
# ---------------------------------------------------
# 禁止违反 unit of work 原则的事务处理
print_sub_header "1.2.1 仓库事务法庭 (UW001)"
run_judges "UW001,UW002,UW003,UW004" "$audit_target"
# 禁止违反边界 DTO 规范的跨层数据传递
print_sub_header "1.2.2 疆域边界法庭 (BC001)"
run_judges "BC001" "$audit_target"
# 禁止违反向量触发协议的事件处理
print_sub_header "1.2.3 向量触发法庭 (VT001)"
run_judges "VT001" "$audit_target"
# ---------------------------------------------------
# 1.3 类型与领域纪律:Any/dict/cast/object 滥用
# 在“做对事、站对层”的前提下,收紧类型与领域使用习惯,防止灰色地带。
# ---------------------------------------------------
# 禁止滥用鸭子类型、Any、cast 等破坏类型纯净度的行为
print_sub_header "1.3.1 鸭子类型法庭 (AC001)"
run_judges "AC001,AC002,AC003" "$audit_target"
# 禁止滥用通用对象类型(object)破坏领域纯净度的行为
print_sub_header "1.3.2 鸭子类型法庭 (OU001)"
run_judges "OU001" "$audit_target"
# 禁止滥用时间相关操作破坏领域纯净度的行为
print_sub_header "1.3.3 时间漂移法庭 (DT001)"
run_judges "DT001" "$audit_target"
# 禁止违反技能配置规范的行为
print_sub_header "1.3.4 技能配置法庭 (SK001)"
run_judges "SK001" "$audit_target"
# =======================================================================
# 第二章:类型联邦调查局
# 审判所有违反“类型法”的行为,确保逻辑的严谨。
# =======================================================================
print_chapter_header "第二章:类型联邦调查局"
# ---------------------------------------------------
# --- 2.1 Pyright 终审:最严格的深度逻辑推断 ---
# 先用 Pyright 做一轮更“挑剔”的静态分析,尽早暴露语法/推断问题。
# ---------------------------------------------------
print_sub_header "2.1 Pyright 审查"
if check_tool "pyright" "Python 静态类型检查器"; then
run_pyright "$audit_target"
else
print_warning "跳过 Pyright 审查"
fi
# ---------------------------------------------------
# --- 2.2 Mypy 复核:对照canonical类型配置做二次确认 ---
# 再用 Mypy 做一轮“官方”类型复核,确保符合帝国标准。
# ---------------------------------------------------
print_sub_header "2.2 Mypy 复核"
if check_tool "mypy" "Python 静态类型检查器"; then
run_mypy "$audit_target"
else
print_warning "跳过 Mypy 审查。"
fi
# =======================================================================
# 第三章:安全署
# 审判危害帝国安全与内部秩序的“中级罪行”。
# =======================================================================
print_chapter_header "第三章:安全署"
# ---------------------------------------------------
# --- 3. Bandit 安全稽查 ---
# 在进入风格/可维护性审查前,优先排除明显的安全隐患.
# 对测试文件,只关心 B101(assert) 这一条罪行,避免噪音过大。
# ---------------------------------------------------
print_sub_header "3. 安全稽查 (Bandit)"
if check_tool "bandit" "安全审计工具 (Bandit)"; then
# 目录目标:递归扫描 (-r);单文件:直接扫描。
if [ -d "$audit_target" ]; then
if [[ "$audit_target" == tests* ]]; then
run_bandit -q -r "$audit_target" -s B101
else
run_bandit -q -r "$audit_target"
fi
else
if [[ "$audit_target" == tests* ]]; then
run_bandit -q "$audit_target" -s B101
else
run_bandit -q "$audit_target"
fi
fi
else
print_warning "跳过 Bandit 安全稽查。"
fi
# =======================================================================
# 第四章:内政署(仪容仪表)
# 检查代码的仪容仪表和风格。
# 在类型与安全通过后,可按需选择检查“渎职”和“懒政”类问题。
# =======================================================================
print_chapter_header "第四章:内政署"
# 确保所有公共接口均有完整文档
print_sub_header "4.1 文档字符串法庭 (DS001)"
run_judges "DS001,DS002" "$audit_target"
# 检查代码复杂度,防止过度复杂化
print_sub_header "4.2 代码复杂度法庭 (LL001)"
run_judges "LL001,LL002" "$audit_target"
# 禁止字符串/数字等硬编码常量
print_sub_header "4.3 硬编码法庭 (HC001)"
run_judges "HC001,HC004,HC005" "$audit_target"
# 检查是否绕过RuleProvider机制的常量使用
print_sub_header "4.4 可调参数法庭 (PC001)"
run_judges "PC001,PC002" "$audit_target"
# 采用命名空间类型定义常量风格
print_sub_header "4.5 常量风格法庭 (HC002)"
run_judges "HC002,HC003" "$audit_target"
# =======================================================================
# 第五章:环境署(清理战场)
# 清理所有轻微的“市容”问题,并进行最终的格式化。
# 所有语义/安全/风格审查通过后,最后一步统一自动修复 + 格式化输出。
# =======================================================================
print_chapter_header "第五章:环境署"
print_sub_header "5. 清理战场 (Ruff)"
if check_tool "ruff" "Python Lint & Format 工具 (Ruff)"; then
run_ruff_check "$audit_target" --fix
run_ruff_format "$audit_target"
else
print_warning "跳过 Ruff 审查与格式化。"
fi
}
# ==============================================================================
# 军刀任务编排
# ==============================================================================
# ------------------------------------------------------------------------------
# --- 第一阶段:帝国军刀 - 静态审计 ---
# ------------------------------------------------------------------------------
print_chapter_header "🗡️ 第一阶段:帝国军刀 - 对【${AUDIT_DIR}】进行静态审计"
print_sub_header "审计目标: $AUDIT_DIR"
if [ "$AUDIT_NON_BLOCKING" = "1" ]; then
print_warning "模式: 非阻断式侦察"
fi
run_static_audit_on_target "$AUDIT_DIR"
print_success "✅ 目标战区【${AUDIT_DIR}】静态审计通过!"
# ------------------------------------------------------------------------------
# --- 第二阶段:帝国军刀 - 国防演习(可选,通过 -t 控制) ---
# ------------------------------------------------------------------------------
if [ "$QAS_ENABLE_TEST_PHASE" -eq 1 ]; then
print_warning "演习: 启用单元测试"
print_chapter_header "第二阶段:帝国国防演习"
# 特殊情况:当审计目标本身就是 tests* 时,视为“直接审计测试战区”,
# 不再额外执行 TP 审查与 pytest 演习,避免重复与歧义。
if [[ "$AUDIT_DIR" == tests* ]]; then
print_warning "审计目标为测试本身,跳过国防演习。"
elif [ ! -d "tests" ]; then
print_warning "未发现 tests 目录,跳过演习。"
else
# 1. 智能寻找与审计目标对应的测试镜像目录
# 优先尝试严格镜像:AUDIT_DIR=foo/bar → tests/foo/bar
# 其次尝试顶层镜像:AUDIT_DIR=foo/... → tests/foo
# 若仍未命中,则退化为全局 tests/。
RELATED_TEST_DIR=""
# 严格镜像
candidate="tests/${AUDIT_DIR}"
if [ -d "$candidate" ]; then
RELATED_TEST_DIR="$candidate"
else
# 顶层目录镜像
top_level="${AUDIT_DIR%%/*}"
candidate="tests/${top_level}"
if [ -d "$candidate" ]; then
RELATED_TEST_DIR="$candidate"
elif [ -d "tests" ]; then
# 退化为全局 tests/
RELATED_TEST_DIR="tests"
fi
fi
if [ -z "$RELATED_TEST_DIR" ]; then
print_warning "未找到与 $AUDIT_DIR 匹配的镜像测试目录,跳过国防演习。"
else
print_sub_header "测试战区镜像: $RELATED_TEST_DIR"
# 2. 单元测试军规审查(TP 系列,仅针对匹配到的测试战区)
print_sub_header "2.1 帝国戎卫兵团军规审查 (TP 系列)"
run_or_warn poetry run python pycourt/judge.py "$RELATED_TEST_DIR" --select TP001,TP002,TP003
print_success "✅ 测试战区【${RELATED_TEST_DIR}】单元测试纯净度通过"
# 3. 对测试战区静态审计
print_sub_header "2.2 测试战区静态审计"
run_static_audit_on_target "$RELATED_TEST_DIR"
# 4. 执行单元测试演习
print_sub_header "2.3 单元测试演习 (pytest -m unit)"
print_sub_header "演习靶场: $RELATED_TEST_DIR"
print_sub_header "演习兵种: 纯粹步兵 (标记: 'unit')"
COV_ARGS=(--cov="$AUDIT_DIR" --cov-report=term-missing --cov-fail-under=0)
set +e
poetry run pytest -m "unit" "${COV_ARGS[@]}" "$RELATED_TEST_DIR"
status=$?
set -e
if [ "$status" -ne 0 ] && [ "$status" -ne 5 ]; then # 5 = no tests collected
run_or_warn false
fi
fi
fi
print_success "目标战区【${AUDIT_DIR}】单元测试完成!"
fi
# ==============================================================================
# 最终裁决
# ==============================================================================
if [ "$AUDIT_NON_BLOCKING" = "1" ]; then
print_chapter_header "🏛️ 帝国军刀完成侦察 - 战区情报已达!"
else
print_chapter_header "🏛️ 帝国军刀审计完成 - 战区已达纯净!"
fi