跳转至

帝国军刀

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