Claude Skill

auto-test-project

当用户明确要求"测试项目"、"运行 auto-test-project"或"进行项目级测试"时使用。对完整项目进行多轮 A 轮批判性测试 + B 轮质量检查,系统化发现、记录、修复问题。⚠️ 不适用:用户只是想优化功能(应直接修改)、只是询问项目问题(应直接回答)、没有明确"测试"意图。

LLM Mart · 0 points · 17 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download huangwb8-skills-skills_alpha_auto-test-project-dd1fab8.zip · 82 KB
Part of huangwb8/skills — 22 skills

Install

skills CLI npx skills add https://github.com/huangwb8/skills/tree/main/skills/alpha/auto-test-project
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install huangwb8-skills@llmmart
Git git clone https://github.com/huangwb8/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole huangwb8/skills collection as a plugin from our marketplace. Git is the plain clone.

README

auto-test-project

这个 skill 用来对完整项目做 A 轮批判性测试和 B 轮质量检查,适合“项目级测试驱动优化”;如果你只是想修一个明确功能点,通常不该直接用它。

用法

最推荐用法

请使用 auto-test-project skill 对本项目进行项目级测试驱动优化。
输入:项目根目录 `.`,以及要重点检查的问题或优化目标
输出:`.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/plans/` 与 `.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/` 下的 A 轮/B 轮计划、测试记录和验证结果

进阶用法

请使用 auto-test-project skill 对这个项目做完整测试闭环。
输入:项目根目录 `.`,重点关注认证流程、文档一致性和配置安全
输出:多轮 A 轮计划、测试记录、B 轮质量检查结果
另外,还有下列参数约束:
- A 轮要求:至少发现 10 个问题
- B 轮要求:必须执行
- 输出要求:所有证据都写入 `.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/plans/` 和 `.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/`

能做什么

  • 把一次“项目测试”拆成可追溯的 A 轮问题发现和 B 轮质量复检。
  • 为技能项目、工作流项目、脚本工具集、文档项目提供统一的测试闭环。
  • 强制把测试计划、问题清单、验证结果写入文件,而不是只给口头建议。
  • 默认把 B 轮质量检查视为必做步骤。
  • 不适合替代单个 bugfix、单个功能开发或日常答疑。

使用示例

示例 1:测试一个技能仓库

请使用 auto-test-project skill 测试这个 skill 项目。
输入:项目根目录 `.`,重点关注 README、SKILL.md、config.yaml 和 scripts 的一致性
输出:A 轮问题计划、测试记录和 B 轮质量检查结果

示例 2:做一轮带重点的项目审查

请使用 auto-test-project skill 对这个项目做项目级测试。
输入:项目根目录 `.`,重点检查路径安全、输出目录隔离和文档口径
输出:测试报告与修复建议

示例 3:要求完整闭环

请使用 auto-test-project skill 对本项目做完整测试驱动优化。
输入:项目根目录 `.`
输出:`.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/plans/`、`.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/`、B 轮质量检查结果
另外,还有下列参数约束:
- 至少执行 1 轮 A 轮
- 不跳过 B 轮
- 收尾时验证测试会话完整性

输出

  • .bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/plans/vYYYYMMDDHHMM.md:A 轮问题分析与改进计划。
  • .bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/vYYYYMMDDHHMM/:A 轮测试会话目录,至少包含 TEST_PLAN.md 和 TEST_REPORT.md。
  • .bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/plans/B轮-vYYYYMMDDHHMM.md:B 轮质量检查报告。
  • .bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/B轮-vYYYYMMDDHHMM/:B 轮验证会话目录。
  • README 不会替你“自动通过测试”;它强调的是问题发现、证据沉淀和闭环验证。

配置

  • 配置文件:auto-test-project/config.yaml
  • 默认 A 轮轮次:1
  • 单轮最少问题数:10
  • A 轮建议目标问题数:15-25
  • B 轮默认:mandatory: true
  • 关键配置节:
    • directories
    • test_rounds
    • b_round_check.dimensions
    • verification

备选用法(脚本/硬编码)

如果你想先创建标准会话骨架,再人工补计划和报告,可以直接走脚本。

创建 A 轮或 B 轮会话

TASK_ROOT=".bensz-api/task-{yyyymmdd-hhmm}-{简短描述}"
python3 auto-test-project/scripts/create_test_session.py \
  --project-root . \
  --task-root "$TASK_ROOT" \
  --kind a \
  --create-plan

省略 --task-root 会分配一个全新的任务根;A/B 轮与 continuation 应始终回传同一个 task root,不能靠脚本猜测最近任务。

用现有计划填充 TEST_PLAN

python3 auto-test-project/scripts/create_test_session.py \
  --project-root . \
  --task-root "$TASK_ROOT" \
  --kind a \
  --create-plan \
  --seed-test-plan-from-plan

校验测试会话完整性

python3 auto-test-project/scripts/verify_test_session.py \
  --project-root . \
  --task-root "$TASK_ROOT" \
  --require-plan \
  "$TASK_ROOT/auto-test-project/output/tests/v202603241200"

旧 .bensz-api/skills/auto-test-project/ 只支持显式只读验证:传入 --legacy-root .bensz-api/skills/auto-test-project。创建脚本没有 legacy 写入模式。

常见问题

Q:它和 auto-test-code 有什么区别?

A:auto-test-project 面向整个项目,强调跨模块、跨文档、跨配置的一致性和闭环;不是单文件或单函数测试器。

Q:我只想修一个功能,还要用它吗?

A:通常不用。只有当你需要系统性测试、质量复检、沉淀计划与证据时,它才最有价值。

Q:为什么一定要写 `.bensz-api/task-

A:这是它的核心价值之一。没有计划和证据,项目级测试很难复现、比较和收尾。

Q:可以跳过 B 轮吗?

A:默认不建议。B 轮负责检查一致性、安全性、过度设计和配置集中化,缺了它就不算完整闭环。

Skill manifest

auto-test-project(项目级自动化测试驱动优化)

目标

为具备明确目录和可执行入口的完整项目提供可追溯的项目级测试与优化流水线:项目初始化、A 轮问题发现与修复、B 轮质量原则检查、验证和交付总结。仅在用户明确要求项目级测试时触发;单个 Agent Skill 使用 auto-test-skill。

本 Skill 将“项目”定义为具有指令文件或等价入口、明确目录结构和功能模块,并包含可执行代码、脚本或流程定义的项目,包括 Agent Skills、工作流项目、脚本工具集和结构化文档项目。本 Skill 不替代领域业务判断,不默认修改远程系统,不把报告模式误当作发布阻断,也不负责单 Skill 测试。

流程

输入

  • 项目根目录、用户指定的测试范围、优化目标或 A 轮次数。
  • 项目指令文件、配置文件、模块目录、可执行代码/脚本和已有测试入口。
  • 需要关注的历史问题、已知约束和可接受的修改边界。

排除历史任务产物、缓存、依赖目录和敏感信息。先验证项目结构与可执行入口,再确定项目类型、核心模块和跨模块测试边界;配置中的 project_testing、a_round 和 b_round_check 是规划口径的单一来源。

执行步骤

  1. 初始化会话:使用宿主已经公开并锁定的任务根;调用 scripts/create_test_session.py 创建 A/B 会话及模板文件。缺省 --task-root 时才分配新任务;A/B 轮和 continuation 必须显式复用同一 task root,不猜测最近任务。
  2. 识别项目:检查指令文件、目录结构、功能模块、入口和测试边界,排除 node_modules/、__pycache__/、.git/、tests/、plans/ 和 _artifacts/ 等配置的排除路径。
  3. 执行 A 轮(可重复 N 次):结合 references/CRITICAL_THINKING_GUIDE.md 与配置的审查范围独立发现问题;为每个问题记录证据、影响、优先级、修复建议和验收标准,形成可引用的 P0-1 等编号。
  4. 修复并轻量测试:按计划优先修复 P0/P1,再处理其它问题;只做最小必要修改,补充 TEST_PLAN.md 和 TEST_REPORT.md 中的可复现命令、结果和证据。项目已有测试时优先运行受影响范围,再按风险扩大验证。
  5. 判断下一轮:检查计划中的问题是否均有报告对应项、成功标准是否有验证结论,以及 P0/P1 是否闭环;达到用户指定轮数或明确的停止条件后进入 B 轮。
  6. 执行 B 轮质量检查:依据 config.yaml:b_round_check.dimensions 检查硬编码与 AI 功能规划、冗余残留、安全性、过度设计、通用性、一致性、项目指令文件瘦身和配置集中化;对发现的 P0/P1 做针对性修复与轻量验证。
  7. 收尾验证:每个会话运行 scripts/verify_test_session.py,最终运行 scripts/verify_all_sessions.py --require-plan;更新目标项目的 CHANGELOG.md,并保留失败证据与提前结束原因。

输出

交付以下可追溯产物,具体模板由 config.yaml:templates 指定:

  • A 轮计划:<task-root>/auto-test-project/output/plans/vYYYYMMDDHHMM.md。
  • A 轮会话:<task-root>/auto-test-project/output/tests/vYYYYMMDDHHMM/,包含 TEST_PLAN.md 和 TEST_REPORT.md。
  • B 轮计划:<task-root>/auto-test-project/output/plans/B轮-vYYYYMMDDHHMM.md。
  • B 轮会话:<task-root>/auto-test-project/output/tests/B轮-vYYYYMMDDHHMM/,包含 TEST_PLAN.md 和 TEST_REPORT.md。
  • 正式代码、文档和项目 CHANGELOG.md:按目标项目原有目录约定保存。

报告必须区分已修复、未修复、无法验证和不确定问题,并提供复现命令或后续人工动作;不得用空报告或口头结论代替证据。

输出管理

所有计划草案、测试报告、会话元数据、命令输出和验证证据写入当前任务根下的 auto-test-project/,不得写入旧的 .bensz-api/skills/auto-test-project/。规划文档放在 output/plans/,会话放在 output/tests/,B 轮会话名统一使用 B轮- 前缀。

旧目录仅允许验证脚本通过 --legacy-root 显式只读检查;创建脚本不得写入。不得覆盖用户已有文件,不得把缓存、依赖或测试运行产物写入源码目录。

校验

使用分钟级会话 ID vYYYYMMDDHHMM。典型 A 轮初始化与验证命令如下:

TASK_ROOT=".bensz-api/task-{yyyymmdd-hhmm}-{简短描述}"
python3 auto-test-project/scripts/create_test_session.py \
  --project-root . --task-root "$TASK_ROOT" --kind a --create-plan
python3 auto-test-project/scripts/verify_test_session.py \
  --project-root . --task-root "$TASK_ROOT" --require-plan \
  "$TASK_ROOT/auto-test-project/output/tests/vYYYYMMDDHHMM"

B 轮创建时显式关联 A 轮:

python3 auto-test-project/scripts/create_test_session.py \
  --project-root . --task-root "$TASK_ROOT" --kind b \
  --id vYYYYMMDDHHMM --a-test-id vYYYYMMDDHHMM

最终验证:

python3 auto-test-project/scripts/verify_all_sessions.py \
  --project-root . --task-root "$TASK_ROOT" --require-plan

通过标准:每轮会话均有非空计划和报告,模板占位符已替换,计划与报告的问题编号和成功标准可对应,验证脚本通过,P0 修复率为 100%,P1 修复率达到配置门槛,且 B 轮强制完成或明确记录无法完成的原因。仓库级 Skill 结构和公共约束由仓库治理检查器负责,不由本 Skill 的会话验证脚本替代。

失败与恢复

将失败分类为输入缺失、项目结构无效、脚本错误、测试失败、外部依赖不可用和结果不确定;保存命令、输出、错误和已生成证据,不伪造通过、不删除失败记录。

可在同一任务根重试未完成阶段;A/B continuation 必须复用原 task root 和关联 ID。输入或环境问题先停止并给出补充项/复现命令;测试失败保留失败报告并允许针对性修复后重跑;无法取得可靠证据时标记为不确定并转人工复核。达到用户指定轮数后不得擅自扩展范围。

约束

公共硬约束

本块由 docs/templates/skill-common-constraints.md 统一维护;每个 SKILL.md 的 ## 约束 必须逐字同步本块,不得在副本中改写公共规则。

  • 任务需要落盘时,使用唯一的 ./.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/ 根目录;共享材料放入 shared/,Skill 专属材料放入该 Skill 的 input/、output/、log/。
  • 正式交付物、源代码和正式计划按项目约定保存,不写入任务工作区;未经授权不覆盖、删除、迁移或远程写入。
  • 项目维护变更检查 BAC 可用性并记录需求、AI 产出、工具结果、文件改动和验证摘要;BAC 只做过程审计,不替代署名、责任或合规判断。
  • 不记录 API Key、访问令牌、密码、Cookie、环境/凭据文件、私有 Prompt、身份信息、本地用户名、主机名或不必要的大体积原始数据。
  • 文件路径必须规范化并限制在授权项目范围内;外部 URL、子进程和网络访问遵循最小权限,防止路径遍历、SSRF 和命令注入。
  • Skill 版本唯一记录在自身 config.yaml:skill_info.version;公开 API、协议、目录或配置变更同步文档与 CHANGELOG.md。
  • bensz-collect-bugs 是一个 Agent Skill;仅将 Bensz Agent Skill 或 Bensz 基础设施本身的设计缺陷交给它。先脱敏写入 ~/.bensz-skills/bugs/,当前任务不中断,只有用户明确要求才公开上报,禁止直接修改用户已安装的 Skill 源码。

Skill 专属约束

  • A 轮至少按配置完成用户指定次数;若提前结束,必须说明原因和未完成范围。
  • B 轮质量检查为强制阶段,维度以 config.yaml:b_round_check.dimensions 为准,不得在正文另设易漂移的默认清单。
  • 计划、修复、测试、证据和结论必须形成闭环;P0/P1 不得仅以建议或口头判断结案。
  • 与 auto-test-skill 的边界固定为:本 Skill 面向完整项目及跨模块关系,auto-test-skill 面向单个 Skill 目录。
  • 可复用的 FAQ(references/FAQ.md)、最佳实践、问题挖掘技巧、反例、严格示例(references/EXAMPLE_STRICT_MINIMAL.md)和报告示例放在 references/;会话创建、单会话验证、批量验证和 Skill 自检使用 scripts/,不得把这些详细材料重新堆回正文。
Files (skills)
  • references
    • ANTI_PATTERNS_LIBRARY.md 13.5 KB
      # 项目/技能开发反例库
      
      **文档版本**:v1.0.0
      **创建时间**:2026-01-14
      **用途**:为 auto-test-project 提供常见反例库,用于快速识别项目级/跨模块反模式(同样适用于 auto-test-skill)
      
      ---
      
      ## 使用说明
      
      本文档按照"B 轮质量原则检查维度"分类(以 `config.yaml:b_round_check.dimensions` 为准),每类包含常见反例。
      
      **使用方法**:
      1. 在检查项目/skill 时,对比本文档中的反例
      2. 发现相似模式时,记录为问题
      3. 参考"正确做法"给出修复建议
      
      ---
      
      ## 1. 硬编码/AI功能规划反例
      
      ### 反例 1: 让 AI "手动创建目录"
      
      **错误表现**:
      ```markdown
      ## 执行步骤
      1. 创建目录:`output/reports/{timestamp}/`
      2. 创建文件:`output/reports/{timestamp}/summary.md`
      ```
      
      **问题**:这是确定性操作,应脚本化
      
      **正确做法**:
      ```markdown
      ## 执行步骤
      1. 运行 `scripts/init_session.py` 自动创建目录和文件
      ```
      
      ---
      
      ### 反例 2: 配置值硬编码在文档中
      
      **错误表现**:
      ```markdown
      ## 配置说明
      最大重试次数:3次
      超时时间:30秒
      ```
      
      **问题**:应移至 config.yaml
      
      **正确做法**:
      ```yaml
      # config.yaml
      retries:
        max: 3
      timeout: 30  # 秒
      ```
      
      ```markdown
      ## 配置说明
      详见 config.yaml 中的 `retries.max` 和 `timeout` 配置项
      ```
      
      ---
      
      ### 反例 3: 让 AI 每次编写相同代码
      
      **错误表现**:
      ```markdown
      ## 步骤 2
      用 Python 读取 CSV 文件:
      ```python
      import csv
      with open(file, 'r') as f:
          reader = csv.reader(f)
          ...
      ```
      
      **问题**:AI 每次都要"记住"这段代码
      
      **正确做法**:
      ```markdown
      ## 步骤 2
      运行 `scripts/read_csv.py --input {file}` 自动读取
      ```
      
      ---
      
      ### 反例 4: 过度配置化
      
      **错误表现**:
      ```yaml
      # config.yaml
      file_formats:
        csv:
          extension: ".csv"
          delimiter: ","
          encoding: "utf-8"
      ```
      
      **问题**:这些是 CSV 标准定义,不需要配置
      
      **正确做法**:
      ```python
      # scripts/reader.py
      DELIMITER = ","  # CSV 标准
      ENCODING = "utf-8"  # 现代标准
      ```
      
      ---
      
      ## 2. 冗余残留错误检查反例
      
      ### 反例 1: 残留引用
      
      **错误表现**:
      ```markdown
      # SKILL.md
      详见 references/OLD_TEMPLATE.md
      ```
      ```bash
      # 实际情况
      $ ls references/OLD_TEMPLATE.md
      ls: cannot access: No such file or directory
      ```
      
      **问题**:引用已删除的文件
      
      **正确做法**:
      ```markdown
      # SKILL.md
      详见 references/NEW_TEMPLATE.md
      ```
      
      ---
      
      ### 反例 2: 重复段落
      
      **错误表现**:
      ```markdown
      ## 输入格式
      输入必须是 PDF 格式,文件大小不超过 10MB...
      
      ## 使用示例
      示例 1:输入一个 PDF 文件...
      示例 2:输入一个 PDF 文件...(与示例 1 几乎相同)
      ```
      
      **问题**:内容重复,应合并
      
      **正确做法**:
      ```markdown
      ## 输入格式
      输入必须是 PDF 格式,文件大小不超过 10MB...
      
      ## 使用示例
      示例:输入一个 PDF 文件并解析...
      ```
      
      ---
      
      ### 反例 3: 僵尸文件
      
      **错误表现**:
      ```
      references/unused_guide.md  # 从未被 SKILL.md 或任何脚本引用
      assets/old_template.txt     # 已被新模板替代,但未删除
      scripts/backup_old.py       # 标记为"备份",但未说明用途
      ```
      
      **问题**:无用的文件占用空间,污染代码库
      
      **正确做法**:
      ```bash
      # 使用 Grep 搜索引用
      grep -r "unused_guide" .
      # 如果无结果,删除文件
      rm references/unused_guide.md
      ```
      
      ---
      
      ### 反例 4: 配置重复定义
      
      **错误表现**:
      ```yaml
      # config.yaml
      output:
        directory: "./output"
        format: "json"
      ```
      ```markdown
      # SKILL.md
      ## 配置说明
      - output_dir: 输出目录(默认:`output/`)
      - output_format: 输出格式(默认:`json`)
      ```
      
      **问题**:配置项名称不一致(`output.directory` vs `output_dir`)
      
      **正确做法**:
      ```markdown
      ## 配置说明
      详见 config.yaml 中的 `output.directory` 和 `output.format`
      ```
      
      ---
      
      ## 3. 安全性检查反例
      
      ### 反例 1: 路径遍历漏洞
      
      **错误表现**:
      ```python
      # 危险:未验证用户输入
      user_path = input("输入文件路径:")
      with open(user_path, 'r') as f:  # 可能访问任意文件
          ...
      ```
      
      **问题**:用户可输入 `../../etc/passwd` 访问任意文件
      
      **正确做法**:
      ```python
      import os
      
      user_path = input("输入文件路径:")
      resolved = os.path.realpath(user_path)
      base_dir = os.path.realpath("./data")
      
      if not resolved.startswith(base_dir):
          raise ValueError("路径必须在 data 目录内")
      
      with open(resolved, 'r') as f:
          ...
      ```
      
      ---
      
      ### 反例 2: 敏感信息泄露
      
      **错误表现**:
      ```python
      # 错误日志中暴露详细信息
      except Exception as e:
          print(f"错误:处理文件 {user_path} 时失败,详情:{str(e)}")
          # user_path 可能是用户数据,e 可能包含内部路径
      ```
      
      **问题**:泄露用户数据和系统内部信息
      
      **正确做法**:
      ```python
      except Exception as e:
          logger.error(f"处理文件失败:{e}", exc_info=True)
          # 不记录 user_path,使用日志系统而非 print
      ```
      
      ---
      
      ### 反例 3: 命令注入风险
      
      **错误表现**:
      ```python
      # 危险:用户输入直接用于系统命令
      os.system(f"convert {user_input} output.pdf")
      ```
      
      **问题**:用户可输入 `; rm -rf /` 执行任意命令
      
      **正确做法**:
      ```python
      import subprocess
      
      subprocess.run(["convert", user_input, "output.pdf"], check=True)
      # 使用参数化 API,而非字符串拼接
      ```
      
      ---
      
      ### 反例 4: 硬编码密钥
      
      **错误表现**:
      ```python
      # config.yaml
      api_key: "sk-1234567890abcdef"
      ```
      
      **问题**:密钥硬编码,会提交到 Git
      
      **正确做法**:
      ```python
      # config.yaml
      api_key: ${API_KEY}  # 从环境变量读取
      ```
      
      ```bash
      # .env(不提交到 Git)
      API_KEY=sk-1234567890abcdef
      ```
      
      ---
      
      ## 4. 过度设计检查反例
      
      ### 反例 1: 为未来预留功能
      
      **错误表现**:
      ```yaml
      # config.yaml
      output_formats:
        pdf:
          enabled: true
          engine: "reportlab"
        docx:
          enabled: false  # 未来可能支持
        html:
          enabled: false  # 未来可能支持
        markdown:
          enabled: false  # 未来可能支持
      ```
      
      **问题**:当前只支持 PDF,其他格式不应硬编码
      
      **正确做法**:
      ```yaml
      # config.yaml
      output_format: "pdf"  # 唯一支持的格式
      # 未来需要时再添加
      ```
      
      ---
      
      ### 反例 2: 过度抽象
      
      **错误表现**:
      ```python
      class OutputFormatFactory:
          """输出格式工厂(当前只有一种格式)"""
          def create_formatter(self, format_type):
              if format_type == "pdf":
                  return PDFFormatter()
              # 未来扩展点...
      
      class PDFFormatter(AbstractFormatter):
          def format(self, data):
              # 实际上就是直接调用一个函数
              return convert_to_pdf(data)
      ```
      
      **问题**:只有一种格式时,工厂和抽象层都是不必要的
      
      **正确做法**:
      ```python
      def format_output(data, output_path):
          """格式化输出为 PDF"""
          convert_to_pdf(data, output_path)
      ```
      
      ---
      
      ### 反例 3: 配置项过多
      
      **错误表现**:
      ```yaml
      # 本可以简单的功能,配置项却超过 20 个
      processing:
        retries: 3
        retry_delay: 1.0
        retry_backoff: 2.0
        retry_jitter: true
        timeout:
          connect: 10
          read: 30
          total: 60
        validation:
          strict: true
          level: "high"
          custom_rules: []
        # ... 还有 10+ 个配置项
      ```
      
      **问题**:大部分场景下这些值不需要改变
      
      **正确做法**:
      ```yaml
      # 只暴露真正需要配置的项
      processing:
        timeout: 30  # 大部分场景够用
        retries: 3   # 大部分场景够用
      # 其他值使用合理的默认值,硬编码在代码中
      ```
      
      ---
      
      ## 5. 通用性检查反例
      
      ### 反例 1: 年份限定
      
      **错误表现**:
      ```markdown
      ## 功能说明
      本 skill 用于处理 2024 年度 NSFC 申请书格式
      ```
      
      **问题**:年份硬编码,2025 年就需要修改
      
      **正确做法**:
      ```markdown
      ## 功能说明
      本 skill 用于处理 NSFC 申请书格式(支持所有版本)
      ```
      
      ---
      
      ### 反例 2: 场景限定过窄
      
      **错误表现**:
      ```markdown
      ## 适用场景
      - 将 WeChat 文章同步到 Notion
      ```
      
      **问题**:限制了平台,实际逻辑可通用化
      
      **正确做法**:
      ```markdown
      ## 适用场景
      - 将网页文章同步到笔记应用(支持 WeChat、Notion、Obsidian 等)
      ```
      
      ---
      
      ### 反例 3: 时间敏感示例
      
      **错误表现**:
      ```markdown
      ## 示例
      输入:`--date 2025-01-14`
      输出:`report_20250114.pdf`
      ```
      
      **问题**:示例日期会过时
      
      **正确做法**:
      ```markdown
      ## 示例
      输入:`--date {YYYY-MM-DD}`
      输出:`report_{YYYYMMDD}.pdf`
      ```
      
      ---
      
      ### 反例 4: 不必要的品牌限定
      
      **错误表现**:
      ```markdown
      本 skill 专为 ChatGPT Plus 用户设计...
      ```
      
      **问题**:限制了 AI 平台,实际功能通用
      
      **正确做法**:
      ```markdown
      本 skill 适用于各类 AI 助手平台(Claude、ChatGPT、Gemini 等)
      ```
      
      ---
      
      ## 6. 一致性检查反例
      
      ### 反例 1: YAML 与正文不一致
      
      **错误表现**:
      ```yaml
      ---
      name: pdf-merger
      description: 合并多个 PDF 文件
      ---
      ```
      ```markdown
      # SKILL.md
      ## 功能说明
      本 skill 用于分割和提取 PDF 页面...
      ```
      
      **问题**:YAML 说是合并,正文说是分割提取
      
      **正确做法**:
      ```yaml
      ---
      name: pdf-splitter
      description: 分割和提取 PDF 页面
      ---
      ```
      
      ---
      
      ### 反例 2: 配置项不一致
      
      **错误表现**:
      ```markdown
      # SKILL.md
      ## 配置说明
      - `output_dir`: 输出目录(默认:`output/`)
      - `max_retries`: 最大重试次数(默认:3)
      ```
      ```yaml
      # config.yaml
      output:
        directory: "./output"
      retries:
        max: 5  # 与文档中的默认值 3 不一致
      ```
      
      **问题**:文档与配置不一致
      
      **正确做法**:
      ```markdown
      # SKILL.md
      ## 配置说明
      详见 config.yaml 中的 `output.directory` 和 `retries.max`
      ```
      ```yaml
      # config.yaml
      output:
        directory: "./output"  # 默认输出目录
      retries:
        max: 5  # 最大重试次数
      ```
      
      ---
      
      ### 反例 3: 示例与实际不一致
      
      **错误表现**:
      ```markdown
      # README.md
      ## 使用示例
      /run pdf-merger --input file1.pdf,file2.pdf --output merged.pdf
      ```
      ```markdown
      # SKILL.md(当前版本)
      ## 参数说明
      - `--input`: 输入文件(支持目录和文件,非逗号分隔列表)
      ```
      
      **问题**:README 中的语法是旧版本
      
      **正确做法**:
      ```markdown
      # README.md
      ## 使用示例
      /run pdf-merger --input ./input_dir --output merged.pdf
      ```
      
      ---
      
      ### 反例 4: 术语不一致
      
      **错误表现**:
      ```markdown
      # 一处文档使用"测试会话"(session)
      ## 创建测试会话
      v202601141900
      
      # 另一处使用"测试轮次"(round)
      ## 测试轮次说明
      第一轮测试...
      ```
      
      **问题**:术语不统一
      
      **正确做法**:
      ```markdown
      # 统一使用"测试会话"(session)
      ## 创建测试会话
      v202601141900
      
      ## 测试会话说明
      第一个测试会话...
      ```
      
      ---
      
      ## 7. SKILL.md 瘦身检查反例
      
      ### 反例 1: 完整模板内容嵌入 SKILL.md
      
      **错误表现**:
      ```markdown
      # SKILL.md(臃肿)
      ## A 轮计划模板
      
      ## 测试 ID: {{TEST_ID}}
      ## 测试时间: {{CHECK_TIME}}
      ## ... 完整的 100 行模板内容 ...
      ```
      
      **问题**:应引用 `references/A_ROUND_PLAN_TEMPLATE.md`
      
      **正确做法**:
      ```markdown
      # SKILL.md(精简)
      ## A 轮计划模板
      
      详见 `references/A_ROUND_PLAN_TEMPLATE.md`
      ```
      
      ---
      
      ### 反例 2: 详细配置说明
      
      **错误表现**:
      ```markdown
      # SKILL.md(臃肿)
      ## 配置说明
      
      ### output_dir
      - 类型:字符串
      - 默认值:"output/"
      - 说明:指定输出目录的路径。可以是相对路径或绝对路径...
      - 示例:output_dir: "./reports"
      
      ### max_retries
      - 类型:整数
      - 默认值:3
      - 说明:最大重试次数。当操作失败时会自动重试...
      - 示例:max_retries: 5
      
      # ... 20+ 个配置项的详细说明
      ```
      
      **问题**:应移至 config.yaml 注释
      
      **正确做法**:
      ```markdown
      # SKILL.md(精简)
      ## 配置说明
      
      详见 config.yaml 中的注释说明。
      ```
      
      ```yaml
      # config.yaml
      # 输出目录(可包含环境变量,如:${HOME}/reports)
      output_dir: "./output"
      
      # 最大重试次数(0 表示不重试)
      max_retries: 3
      ```
      
      ---
      
      ### 反例 3: 详细技术实现
      
      **错误表现**:
      ```markdown
      # SKILL.md(臃肿)
      ## 实现细节
      
      ### PDF 解析逻辑
      使用 PyPDF2 库解析 PDF 文件。首先打开文件,然后逐页读取...
      具体实现:[100 行技术说明]
      ```
      
      **问题**:应移至 scripts/ 注释或独立技术文档
      
      **正确做法**:
      ```markdown
      # SKILL.md(精简)
      ## 实现细节
      
      详见 `scripts/parse_pdf.py` 中的 docstring 和注释。
      ```
      
      ---
      
      ### 反例 4: 行数过多
      
      **错误表现**:
      ```
      SKILL.md: 500+ 行
      references/: 空目录或只有 1-2 个文件
      ```
      
      **问题**:SKILL.md 过于冗长
      
      **正确做法**:
      - SKILL.md 控制在 300 行以内
      - 详细内容移至 references/
      - 技术细节移至 scripts/ 注释
      - 配置说明移至 config.yaml 注释
      
      ---
      
      ## 使用反例库进行问题发现
      
      ### 步骤 1: 快速扫描
      
      浏览 skill 文件,对比反例库中的模式:
      - 是否有"让 AI 手动操作"的模式?
      - 是否有"为未来预留功能"的配置?
      - 是否有"年份限定"的文档?
      
      ### 步骤 2: 深度验证
      
      对发现的疑似问题,进一步验证:
      - 这个配置项真的需要吗?
      - 这个抽象真的有必要吗?
      - 这个限定真的合理吗?
      
      ### 步骤 3: 记录问题
      
      使用问题记录模板记录:
      ```
      #### 问题 X: [反例名称]
      
      **位置**: `文件:行号`
      
      **反例类型**: [质量原则之一]
      
      **问题描述**:
      [具体描述问题现象,参考反例库]
      
      **优先级**: P0/P1/P2
      
      **修复建议**:
      [参考反例库中的"正确做法"]
      
      **验证方法**:
      [如何确认修复成功]
      ```
      
      ---
      
      **模板说明**:
      
      本文档用于 auto-test-skill 快速识别常见问题。
      
      使用时:
      1. 熟悉质量原则的反例模式
      2. 检查 skill 时对比反例库
      3. 发现相似模式时记录为问题
      4. 参考"正确做法"给出修复建议
      
    • A_ROUND_PLAN_TEMPLATE.md 7.5 KB
      # A 轮优化计划模板(项目级)
      
      **计划版本**: v{{TIMESTAMP}}
      **制定时间**: {{PLAN_DATE}}
      **当前版本**: {{CURRENT_VERSION}}
      **目标项目**: {{PROJECT_NAME}}
      **项目路径**: {{PROJECT_ROOT}}
      
      ---
      
      ## 全局视图
      
      ### 我在优化 journey 的哪个阶段?
      
      - [ ] **首轮分析**(理解现状 + 发现问题)
      - [ ] **中间迭代**(逐项修复 + 渐进增强)
      - [ ] **收尾阶段**(质量检查 + 文档完善)
      
      **当前轮次**: A 轮 #{{ROUND_NUMBER}} / 共 {{TOTAL_ROUNDS}} 轮
      
      ### 本轮解决的核心问题是什么?
      
      **一句话概括**(例: "重构模块间接口,消除循环依赖"):
      
      {{ONE_LINE_SUMMARY}}
      
      ### 本轮完成后,距离最终目标还有多远?
      
      | 状态 | 内容 |
      |------|------|
      | **已完成** | {{COMPLETED_ITEMS}} |
      | **本轮完成** | {{CURRENT_ROUND_ITEMS}} |
      | **待完成** | {{PENDING_ITEMS}} |
      
      ---
      
      ## 与上轮的关联
      
      > 注:如启用“独立评估”模式(默认),本节可留空或仅记录“本轮不依赖上轮产物”的声明;如用户明确要求沿上轮跟进,再补充关联信息。
      
      **上轮输出** (A 轮 #{{PREV_ROUND_NUMBER}}):
      - {{PREV_ROUND_OUTPUT}}
      
      **本轮基于上轮发现的问题**:
      {{BASED_ON_PREV_ISSUES}}
      
      ---
      
      ## 项目级分析
      
      ### 涉及的模块
      
      | 模块 | 路径 | 变更类型 | 影响范围 |
      |------|------|---------|---------|
      | {{MODULE_1}} | {{PATH_1}} | {{CHANGE_TYPE_1}} | {{IMPACT_1}} |
      | {{MODULE_2}} | {{PATH_2}} | {{CHANGE_TYPE_2}} | {{IMPACT_2}} |
      
      ### 跨模块依赖
      
      {{MODULE_DEPENDENCIES}}
      
      ---
      
      ## 本轮目标
      
      1. {{GOAL_1}}
      2. {{GOAL_2}}
      3. {{GOAL_3}}
      
      ---
      
      ## 系统视角与批判性分析(⭐️ 核心章节)
      
      **⚠️ 重要说明**:本项目级测试的核心价值在于**系统视角和批判性思维**,而非替代 linter 进行表面检查。
      
      ### 本轮使用的批判性分析框架
      
      **请明确本轮使用的批判性分析技巧**(从 `references/PROJECT_ISSUE_DISCOVERY_TECHNIQUES.md` 技巧 0 选择):
      
      - [ ] **技巧 0.1:第一性原理思考**(评估项目是否偏离核心目标)
      - [ ] **技巧 0.2:架构合理性质疑**(评估模块划分、依赖方向、抽象层次)
      - [ ] **技巧 0.3:价值导向的问题分类**(避免噪音级问题,聚焦痛点级/隐患级)
      - [ ] **技巧 0.4:根本原因分析(5 Whys)**(挖掘问题本质,而非修复表象)
      
      **本轮主要使用**:{{CRITICAL_FRAMEWORK_USED}}(例:"技巧 0.1 + 0.2")
      
      ### 本轮核心目标对齐度分析
      
      **项目核心目标**(从 CLAUDE.md/AGENTS.md 提取):
      
      {{PROJECT_CORE_GOAL}}
      
      **本轮优化目标与核心目标的关系**:
      
      {{ALIGNMENT_WITH_CORE_GOAL}}
      
      **批判性质疑**:本轮发现的问题中,是否有质疑"为什么需要这个功能/模块/配置?"的问题?
      
      {{CRITICAL_QUESTIONING}}
      
      ### 本轮问题类型分布预期
      
      | 问题类型 | 预期数量 | 占比 | 说明 |
      |---------|---------|------|------|
      | **痛点级**(用户核心功能受阻) | {{PAIN_POINT_COUNT}} | ~20% | 不修复就无法使用的问题 |
      | **隐患级**(未来风险) | {{HIDDEN_RISK_COUNT}} | ~50% | 技术债务、架构问题 |
      | **噪音级**(表面问题) | {{NOISE_COUNT}} | ~30% | 可选优化,避免过多 |
      
      **⚠️ 质量门槛**:每轮至少有 **1-2 个痛点级问题**(架构级/设计级),否则说明分析深度不足。
      
      ### 本轮最具洞察力的发现(非表面问题)
      
      **本轮发现的"本质问题"**(非表象):
      
      {{MOST_INSIGHTFUL_FINDING}}
      
      **为什么这是本质问题?**(使用 5 Whys 分析):
      
      {{ROOT_CAUSE_ANALYSIS}}
      
      ---
      
      ## 问题清单(P0-P2)
      
      **⚠️ 问题记录要求**(强化批判性思维):
      
      每个 P0/P1 问题**必须**包含以下字段:
      - **批判性质疑**:质疑设计合理性,而非只描述现象
      - **根本原因**:使用 5 Whys 分析,至少回答 3 次"为什么"
      - **价值判断**:为什么这是 P0 而不是 P2?修复它的价值是什么?
      
      ### 优先级定义
      
      | 优先级 | 定义 | 示例 |
      |--------|------|------|
      | **P0** | 阻塞性问题:不修复就无法继续;或安全风险;或核心功能缺陷 | 跨模块接口错误、核心流程阻塞、安全漏洞 |
      | **P1** | 重要优化:显著提升质量/安全性/可维护性 | 模块间接口优化、测试覆盖不足、性能问题 |
      | **P2** | 锦上添花:改进体验、完善细节、后续迭代 | 文档优化、代码风格统一、建议性改进 |
      
      **重要**: 优先级是**相对于本轮目标**的,不是绝对重要性。项目级问题需要考虑跨模块影响。
      
      ---
      
      ### P0(阻塞/安全/核心)
      
      #### P0-1: {{P0_1_TITLE}}
      
      **位置**: `{{FILE}}:{{LINE}}`
      
      **涉及模块**: {{MODULES}}
      
      **影响**:
      {{P0_1_IMPACT}}
      
      **批判性质疑**(⭐️ 必填):
      {{P0_1_CRITICAL_QUESTIONING}}
      
      **根本原因**(5 Whys 分析):
      {{P0_1_ROOT_CAUSE}}
      
      **修复建议**(针对根本原因,而非表象):
      {{P0_1_FIX}}
      
      **价值判断**:为什么这是 P0?修复它的价值是什么?
      {{P0_1_VALUE_JUDGEMENT}}
      
      **验证方法**:
      {{P0_1_VERIFY}}
      
      ---
      
      ### P1(重要优化)
      
      #### P1-1: {{P1_1_TITLE}}
      
      **位置**: `{{FILE}}:{{LINE}}`
      
      **涉及模块**: {{MODULES}}
      
      **影响**:
      {{P1_1_IMPACT}}
      
      **批判性质疑**(⭐️ 必填):
      {{P1_1_CRITICAL_QUESTIONING}}
      
      **根本原因**(5 Whys 分析):
      {{P1_1_ROOT_CAUSE}}
      
      **修复建议**(针对根本原因,而非表象):
      {{P1_1_FIX}}
      
      **价值判断**:为什么这是 P1?修复它的价值是什么?
      {{P1_1_VALUE_JUDGEMENT}}
      
      **验证方法**:
      {{P1_1_VERIFY}}
      
      ---
      
      ### P2(锦上添花)
      
      #### P2-1: {{P2_1_TITLE}}
      
      **位置**: `{{FILE}}:{{LINE}}`
      
      **涉及模块**: {{MODULES}}
      
      **影响**:
      {{P2_1_IMPACT}}
      
      **修复建议**:
      {{P2_1_FIX}}
      
      **验证方法**:
      {{P2_1_VERIFY}}
      
      ---
      
      ## 修改步骤(可选)
      
      1. {{STEP_1}}
      2. {{STEP_2}}
      3. {{STEP_3}}
      
      ---
      
      ## 轻量测试计划
      
      ### 验证点
      
      - {{VERIFY_POINT_1}}
      - {{VERIFY_POINT_2}}
      - {{VERIFY_POINT_3}}
      
      ### 跨模块验证
      
      {{CROSS_MODULE_VALIDATION}}
      
      ### 测试数据准备(如需要)
      
      {{TEST_DATA_PREPARATION}}
      
      ---
      
      ## 完成后的下一轮预告
      
      **预计下一轮聚焦**:
      {{NEXT_ROUND_FOCUS}}
      
      **预计完成时间**:
      {{NEXT_ROUND_TIME}}
      
      ---
      
      **模板说明**:
      
      本模板用于项目级 A 轮优化计划的生成。使用时:
      
      1. **替换占位符**: 将 `{{VAR}}` 替换为实际内容
      2. **删除不需要的章节**: 如某些章节不适用,可删除
      3. **保持结构完整**: 确保全局视图、与上轮关联、项目级分析、问题清单(P0-P2)核心章节完整
      4. **优先级判定**: 明确标注为什么这个问题是 P0/P1/P2
      5. **项目级考虑**: 记录涉及的模块、跨模块依赖、跨模块验证
      
      **与 auto-test-skill 的区别**:
      
      | 维度 | skill 级 (auto-test-skill) | 项目级 (auto-test-project) |
      |------|---------------------------|---------------------------|
      | **分析对象** | 单个 skill 的 SKILL.md/config.yaml | 项目的多个模块、多个文件 |
      | **问题范围** | skill 内部问题 | 跨模块、跨文件问题 |
      | **依赖分析** | skill 内依赖 | 跨模块依赖、接口关系 |
      | **验证范围** | skill 功能验证 | 跨模块交互验证 |
      | **变更影响** | skill 级变更 | 项目级连锁反应 |
      
      **关键原则**:
      
      - **全局意识**: 每轮都明确自己在优化 journey 中的位置
      - **上下文连贯**: 说明与上轮的关联,避免孤立的问题清单
      - **项目视野**: 考虑跨模块影响和依赖关系
      - **优先级依据**: P0/P1/P2 必须有明确的判定标准,考虑项目级影响
      - **可追溯性**: 每个问题都要有位置、影响、修复建议、验证方法
      
    • CONSTRUCTIVE_SUGGESTION_GUIDELINES.md 7.4 KB
      # 建设性建议标准
      
      **文档版本**:v1.0.0
      **创建时间**:2026-01-14
      **用途**:为 auto-test-project 提供"什么是建设性建议"的判断标准(同样适用于 auto-test-skill)
      
      ---
      
      ## 核心定义
      
      **建设性建议** = 具体可执行的改进方案,包含明确的修复路径和验证方法。
      
      ---
      
      ## ✅ 建设性建议的特征
      
      ### 1. 可执行
      
      每条建议必须包含具体的修复方案,不是"应该改进XX"这种泛泛而谈。
      
      **示例**:
      - ❌ "建议增加更多日志"
      - ✅ "在 `scripts/foo.py` 第 42 行的 `except` 块中,记录具体的异常类型和堆栈信息"
      
      ### 2. 有证据
      
      每条建议必须基于具体文件/行号/代码,不是凭空猜测。
      
      **示例**:
      - ❌ "建议优化文档结构"
      - ✅ "SKILL.md 第 30 行的描述与 config.yaml 第 15 行不一致,应统一为'XXX'"
      
      ### 3. 有价值
      
      修复后能带来明显的质量提升(安全性/可维护性/用户体验)。
      
      **示例**:
      - ❌ "建议优化注释风格"(价值低)
      - ✅ "建议增加路径遍历防御"(安全性提升)
      
      ### 4. 可验证
      
      每条建议必须包含明确的验证方法,确认修复成功。
      
      **示例**:
      - ❌ "建议修复配置加载逻辑"
      - ✅ "建议修复配置加载逻辑:验证方法 → 在 `tests/` 中运行空配置文件测试,确认有默认值回退"
      
      ---
      
      ## ❌ 非建设性建议的例子
      
      ### 类型 1:泛泛而谈
      
      | ❌ 泛泛建议 | ✅ 建设性建议 |
      |------------|--------------|
      | "建议增加更多日志" | "在 `scripts/foo.py` 第 42 行的 `except` 块中,记录异常类型和堆栈" |
      | "建议优化文档" | "SKILL.md 第 30 行与 config.yaml 第 15 行不一致,应统一为..." |
      | "建议增强测试" | "缺少 `--dry-run` 参数的测试,应在 `tests/` 中增加验证用例" |
      | "建议改进错误处理" | "第 88 行的 `except` 块捕获了所有异常但未记录,应细化为具体异常类型" |
      
      ### 类型 2:无具体位置
      
      | ❌ 无位置建议 | ✅ 建设性建议 |
      |--------------|--------------|
      | "某个配置项没有说明" | "config.yaml 第 23 行的 `timeout` 项缺少单位说明,应补充为'秒'" |
      | "代码中有重复逻辑" | "`scripts/bar.py` 第 105-110 行与第 145-150 行完全相同,应提取为函数" |
      | "示例无法运行" | "README.md 第 18 行的示例命令缺少必需的 `--input` 参数" |
      
      ### 类型 3:价值不明确
      
      | ❌ 价值不明确 | ✅ 建设性建议 |
      |--------------|--------------|
      | "建议统一注释风格" | "建议统一注释风格:当前混用 `#` 和 `//`,导致某些编辑器语法高亮失效" |
      | "建议优化变量命名" | "建议优化变量命名:`tmp1`/`tmp2` 无法表达用途,改为 `input_path`/`output_path`" |
      | "建议重构函数" | "建议重构 `process()` 函数:当前 200 行,包含 3 层嵌套,难以测试" |
      
      ### 类型 4:无验证方法
      
      | ❌ 无验证方法 | ✅ 建设性建议 |
      |--------------|--------------|
      | "建议修复路径验证" | "建议修复路径验证:验证方法 → 构造输入 `../../etc/passwd`,确认被拒绝" |
      | "建议增加默认值" | "建议增加默认值:验证方法 → 删除 config.yaml 中的该配置项,确认仍能运行" |
      | "建议更新文档" | "建议更新文档:验证方法 → 按照文档步骤执行,确认能成功运行" |
      
      ---
      
      ## 建设性建议的"黄金公式"
      
      ```
      位置 + 问题现象 + 影响分析 + 具体修复方案 + 验证方法
      ```
      
      ### 完整示例
      
      **位置**:`scripts/validator.py:45-48`
      
      **问题现象**:
      ```python
      # 当前代码
      if path.startswith("../"):
          raise ValueError("Invalid path")
      ```
      
      **影响分析**:
      - 只检查 `../` 前缀,无法防御 `..\\`(Windows)、`./../`、绝对路径绕过等攻击向量
      - 存在路径遍历漏洞风险
      
      **具体修复方案**:
      ```python
      # 修复后代码
      import os
      resolved = os.path.realpath(path)
      if not resolved.startswith(os.path.realpath(base_dir)):
          raise ValueError(f"Path {path} is outside base directory")
      ```
      
      **验证方法**:
      1. 构造恶意输入 `../../etc/passwd`,确认被拒绝
      2. 构造绕过输入 `./../../etc/passwd`,确认被拒绝
      3. 构造合法输入 `data/test.csv`,确认通过验证
      
      ---
      
      ## 建议质量自检清单
      
      在提交建议前,确认每条建议都满足:
      
      - [ ] **包含具体位置**:文件名 + 行号(如 `SKILL.md:30`)
      - [ ] **包含修复方案**:不是"建议"而是"改为..."
      - [ ] **包含验证方法**:如何确认修复成功
      - [ ] **有明确价值**:能提升安全性/可维护性/用户体验
      - [ ] **可独立执行**:不需要额外的上下文信息
      
      ---
      
      ## 常见反模式
      
      ### 反模式 1:"应该"式建议
      
      ❌ "应该增加错误处理"
      ✅ "第 42 行缺少对 `FileNotFoundError` 的处理,应增加 `try-except` 块"
      
      ### 反模式 2:"问题列表"式建议
      
      ❌ "问题:1) 无日志 2) 无测试 3) 无文档"
      ✅ 拆分为 3 条独立建议,每条都有位置和修复方案
      
      ### 反模式 3:"模糊优化"式建议
      
      ❌ "建议优化性能"
      ✅ "第 88 行的循环嵌套复杂度为 O(n²),建议改用字典降低到 O(n)"
      
      ### 反模式 4:"假设用户会"式建议
      
      ❌ "建议用户先创建目录"
      ✅ "脚本应自动创建目录,而非假设用户已创建(见 `scripts/foo.py:55`)"
      
      ---
      
      ## 不同优先级的建议标准
      
      ### P0 建议(必须修复)
      
      **特征**:
      - 阻塞性问题:不修复就无法继续
      - 安全风险:路径遍历、命令注入、敏感信息泄露
      - 核心功能缺失:缺少关键功能、无法完成基本任务
      
      **示例**:
      - "存在路径遍历漏洞(`scripts/validator.py:45`),用户可访问任意文件"
      - "缺少必需的配置项 `api_key`(`config.yaml`),导致脚本无法启动"
      
      ### P1 建议(强烈建议)
      
      **特征**:
      - 重要优化:显著提升质量/安全性/可维护性
      - 测试覆盖不足:核心功能缺少测试
      - 文档缺失:用户无法理解如何使用
      
      **示例**:
      - "缺少对 `--dry-run` 参数的测试(`tests/`),建议增加验证用例"
      - "SKILL.md 第 30 行缺少步骤 2 的详细说明,用户无法正确执行"
      
      ### P2 建议(可选)
      
      **特征**:
      - 改进体验:提升可用性、易读性
      - 完善细节:注释、代码风格、命名
      - 后续迭代:不影响当前使用的改进
      
      **示例**:
      - "变量名 `tmp1`/`tmp2` 不够直观(`scripts/bar.py:105`),建议改为 `input_path`/`output_path`"
      - "建议统一注释风格:当前混用 `#` 和 `//`(`scripts/*.py`)"
      
      ---
      
      ## 数量要求
      
      根据 auto-test-skill 的要求:
      
      - **每轮 A 轮**:至少 10 个问题(P0 + P1 + P2 总和),鼓励 15-20 个
      - **B 轮检查**:至少 10-20 个建设性建议
      
      **建议分布**(参考):
      - P0:2-4 个(如无严重问题,可少于 2 个)
      - P1:4-8 个(重点)
      - P2:4-8 个(锦上添花)
      
      ---
      
      ## 本轮建议质量检查
      
      在提交建议前,回答以下问题:
      
      1. **位置明确吗?** 每条建议都包含文件名和行号
      2. **方案具体吗?** 每条建议都描述了"改为..."而非"建议..."
      3. **可验证吗?** 每条建议都有明确的验证方法
      4. **有价值吗?** 每条建议都能带来明显的质量提升
      5. **数量达标吗?** 总数 ≥ 10,且 P0+P1 占比 ≥ 60%
      
      ---
      
      **模板说明**:
      
      本文档用于指导 auto-test-skill 生成高质量的建设性建议。
      
      使用时:
      1. 参考本文档的"黄金公式"撰写建议
      2. 使用"建议质量自检清单"验证每条建议
      3. 确保建议数量和优先级分布符合要求
      
    • CRITICAL_THINKING_GUIDE.md 13.8 KB
      # 批判性思维指南
      
      **文档版本**:v1.1.0
      **创建时间**:2026-01-16
      **用途**:为 auto-test-project 提供「如何进行批判性思考」的思考框架(同样适用于 auto-test-skill)
      
      ---
      
      ## 核心思想
      
      **批判性思维** ≠ 找茬
      = 系统性质疑 + 多角度验证 + 边缘情况探索 + 深度挖掘
      
      本文档提供**三大思考框架**,帮助 AI 在每轮 A 轮中发现真正有价值的问题。
      
      ---
      
      ## A 轮独立评估(强制)
      
      在 auto-test-project(以及 auto-test-skill)中,每轮 A 轮默认采用**独立评估**模式:
      
      - **不查看**上轮的 `plans/` 与 `tests/`(避免确认偏差/路径依赖)
      - 只基于目标项目/目标 skill 的**当前工作文件**证据(如核心文档、配置、脚本、模板等;排除历史产物与无关文件)
      - 目标:让"多轮"带来"多角度",而不是"重复确认同一结论"
      
      ### 独立评估 vs 渐进式评估(对比)
      
      | 维度 | 独立评估(默认) | 渐进式评估(谨慎使用) |
      |------|------------------|------------------------|
      | 输入依赖 | 不依赖上轮产物 | 强依赖上轮计划/报告 |
      | 偏差风险 | 更低(减少确认偏差) | 更高(容易路径依赖) |
      | 多轮价值 | 更稳定(角度更分散) | 递减风险更高(只盯变更点) |
      | 适用场景 | 用户要求多轮审查/需要多角度发现问题 | 用户明确要求“沿着上轮路线持续修复/追踪同一议题” |
      
      ---
      
      ## 框架 1: 系统视角思考
      
      ### 目的
      避免"盲人摸象",从**系统架构**层面审视技能的设计合理性。
      
      ### 思考维度
      
      #### 维度 1: 这个技能的核心价值是什么?
      
      **自问清单**:
      - 这个技能要解决的核心问题是什么?
      - 当前设计是否真的解决了这个问题?
      - 是否有更简单的解决方案?
      
      **高质量问题示例**:
      ```
      问题:auto-test-skill 的核心价值是"发现系统性问题",
      但当前工作流只要求"列出 10 个问题",没有区分"表面问题"vs"深层问题"。
      位置:SKILL.md:87-90(问题数量要求)
      优先级:P0
      修复:增加"问题深度检查",要求每轮至少 3 个"系统性问题"
      ```
      
      #### 维度 2: 工作流的每个步骤都必要吗?
      
      **自问清单**:
      - 这个步骤是否真正贡献于最终目标?
      - 删除这个步骤会怎样?
      - 能否合并相似步骤?
      
      **高质量问题示例**:
      ```
      问题:工作流包含"执行优化"和"轻量测试"两个独立步骤,
      但"优化"的本质就是"验证修复效果",两者重叠。
      位置:SKILL.md:105-115
      优先级:P1
      修复:合并为"修复并验证"步骤,减少文档冗余
      ```
      
      #### 维度 3: 配置项真的需要可配置吗?
      
      **自问清单**:
      - 这个配置项在不同使用场景下会有不同值吗?
      - 如果只有一个合理值,为什么还要配置?
      - 硬编码会失去什么灵活性?
      
      **高质量问题示例**:
      ```
      问题:output_format 配置项只有 "json" 一个有效值(无其他格式支持),
      过度配置化,增加理解成本。
      位置:config.yaml:30
      优先级:P1
      修复:移除配置项,直接硬编码为 "json"
      ```
      
      #### 维度 4: 文件结构反映了什么样的设计理念?
      
      **自问清单**:
      - 目录结构是否清晰传达了技能的用途?
      - 是否存在"职责不清"的文件?
      - 文件之间的依赖关系是否合理?
      
      **高质量问题示例**:
      ```
      问题:references/ 目录下混合了"模板"和"指南"两类文档,
      但没有清晰的命名区分,难以快速定位。
      位置:references/
      优先级:P2
      修复:重命名文件,添加前缀:TEMPLATE_*.md vs GUIDE_*.md
      ```
      
      ---
      
      ## 框架 2: 刁钻角度思考
      
      ### 目的
      通过**极端情况、恶意输入、隐式假设**等刁钻角度,发现隐藏问题。
      
      ### 角度 1: 边缘情况压力测试
      
      **测试场景矩阵**:
      
      | 输入类型 | 正常输入 | 边缘输入 | 极端输入 | 恶意输入 |
      |----------|----------|----------|----------|----------|
      | **路径** | `data/test.csv` | `path with spaces` | `""` (空字符串) | `../../etc/passwd` |
      | **配置** | 完整 YAML | `{}` (空配置) | `timeout: -1` (非法值) | 恶意 YAML 注入 |
      | **文件** | 1MB 文件 | 0 字节文件 | 10GB 文件 | 特殊字符文件 (`\n`, `\0`) |
      
      **高质量问题示例**:
      ```
      问题:路径验证只检查 `../` 前缀,无法防御 `./../` 绕过攻击。
      攻击向量:用户输入 `./../../etc/passwd` 可访问任意文件
      位置:scripts/validator.py:45
      优先级:P0
      修复:使用 os.path.realpath() 规范化后再验证
      验证:构造输入 `./../../etc/passwd`,确认被拒绝
      ```
      
      ### 角度 2: 恶意用户测试
      
      **攻击场景清单**:
      1. **路径遍历**:能否访问 `../../etc/passwd`?
      2. **命令注入**:能否通过 `; rm -rf /` 执行任意命令?
      3. **资源耗尽**:能否通过超大文件(>1GB)耗尽内存?
      4. **并发竞态**:能否通过同时修改配置文件破坏系统?
      5. **符号链接攻击**:能否通过 symlink 读取敏感文件?
      
      **高质量问题示例**:
      ```
      问题:未验证符号链接,用户可创建 symlink 到 `/etc/passwd`,
      脚本会跟随 symlink 读取敏感文件。
      位置:scripts/reader.py:34
      优先级:P0
      修复:验证解析后的路径是否在 base_dir 内
      验证:创建 symlink 到敏感文件,确认被拒绝
      ```
      
      ### 角度 3: 隐式假设识别
      
      **关键词搜索**(发现未验证的假设):
      - `应该` → 通常意味着"实际上没做"
      - `会` → 通常意味着"假设会发生"
      - `自动` → 通常意味着"没有验证"
      - `用户` → 通常意味着"假设用户会做某事"
      
      **高质量问题示例**:
      ```
      问题:"用户应先创建目录" → 假设用户会手动创建目录,
      但实际不会,导致脚本失败。
      位置:SKILL.md:55
      优先级:P1
      修复:脚本应自动创建目录(见 scripts/setup.py:12)
      验证:在空目录运行脚本,确认自动创建目录
      ```
      
      ### 角度 4: 自我质疑法
      
      **对每个设计决策问**:
      - "这个设计真的有用吗?还是'自我感动'?"
      - "有更简单的实现方式吗?"
      - "这个配置项真的需要吗?"
      
      **高质量问题示例**:
      ```
      问题:session_format 配置项使用复杂的 Jinja2 模板语法
      (`v{year}{month}{day}{hour}{minute}`),但实际只有一个固定格式。
      位置:config.yaml:24
      优先级:P1
      修复:移除配置项,直接使用 Python datetime 格式化
      理由:过度设计,增加理解成本
      ```
      
      ---
      
      ## 框架 3: 问题质量标准
      
      ### 目的
      避免"凑够 10 个问题",确保每个问题都有**真正的价值**。
      
      ### 黄金标准
      
      每个问题必须满足:
      
      ```
      位置 + 现象 + 影响(为什么重要) + 修复方案 + 验证方法
      ```
      
      ### 质量检查清单
      
      在提交问题前,确认每条问题都满足:
      
      #### 检查 1: 位置精确吗?
      - ❌ "某个配置项没有说明"
      - ✅ "config.yaml 第 23 行的 `retry_delay` 配置项缺少说明"
      
      #### 检查 2: 现象具体吗?
      - ❌ "建议增加错误处理"
      - ✅ "第 42 行缺少对 `FileNotFoundError` 的处理,文件不存在时会崩溃"
      
      #### 检查 3: 影响明确吗?
      - ❌ "影响用户体验"
      - ✅ "用户无法恢复错误,只能重新运行整个流程"
      
      #### 检查 4: 修复方案具体吗?
      - ❌ "建议优化文档"
      - ✅ "在 config.yaml 第 23 行增加注释:`# 重试延迟(秒)`"
      
      #### 检查 5: 验证方法明确吗?
      - ❌ "验证修复成功"
      - ✅ "构造输入 `../../etc/passwd`,确认被拒绝"
      
      ---
      
      ## 问题优先级判定
      
      ### P0(阻塞/安全/核心)
      
      **特征**:
      - 不修复就无法使用
      - 存在安全风险(路径遍历、命令注入、信息泄露)
      - 核心功能缺失
      
      **示例**:
      ```
      问题:存在路径遍历漏洞,用户可访问任意文件
      位置:scripts/validator.py:45
      优先级:P0
      理由:安全风险,可能泄露敏感信息
      ```
      
      ### P1(重要优化)
      
      **特征**:
      - 显著提升质量/安全性/可维护性
      - 影响核心工作流
      - 过度设计/冗余/不一致
      
      **示例**:
      ```
      问题:output_format 配置项只有一个有效值,过度设计
      位置:config.yaml:30
      优先级:P1
      理由:增加理解成本,无实际灵活性
      ```
      
      ### P2(锦上添花)
      
      **特征**:
      - 改进体验、完善细节
      - 不影响核心功能
      - 后续迭代
      
      **示例**:
      ```
      问题:变量名 `tmp1` 不够直观
      位置:scripts/bar.py:105
      优先级:P2
      理由:不影响功能,但影响可读性
      ```
      
      ---
      
      ## 数量要求(强制)
      
      ### A 轮要求
      
      **最低要求**(不满足则继续挖掘):
      - P0 + P1 + P2 总和 ≥ 10
      - P0 + P1 占比 ≥ 60%
      
      **推荐分布**:
      - P0:2-4 个(系统性问题、安全风险)
      - P1:4-8 个(过度设计、冗余、一致性)
      - P2:3-6 个(细节优化)
      
      ### 系统性问题专项要求
      
      **每轮必须包含至少 3 个"系统性问题"**:
      
      | 系统性问题类型 | 定义 | 示例 |
      |---------------|------|------|
      | **架构设计问题** | 工作流/配置/文件结构层面的设计缺陷 | "工作流步骤重叠,无明确职责分工" |
      | **过度设计问题** | 不必要的抽象/配置/灵活性 | "只有一个值的配置项" |
      | **一致性问题** | 跨文件/跨文档的矛盾 | "SKILL.md 说 X,config.yaml 说 Y" |
      | **安全性问题** | 路径遍历/命令注入/信息泄露 | "未验证符号链接" |
      
      ---
      
      ## 批判性思维检查清单
      
      ### 在提交 A 轮计划前,确认:
      
      - [ ] **系统视角**:是否从架构层面审视设计合理性?
      - [ ] **刁钻角度**:是否测试了边缘情况、恶意输入、隐式假设?
      - [ ] **问题质量**:每个问题都包含位置+现象+影响+修复+验证吗?
      - [ ] **优先级合理**:P0/P1 占比 ≥ 60% 吗?
      - [ ] **系统性问题**:至少 3 个系统性问题(架构/过度设计/一致/安全)吗?
      - [ ] **独立评估**:是否未查看 `plans/` 与 `tests/`,避免确认偏差/路径依赖?
      - [ ] **范围覆盖**:是否覆盖必要工作文件/目录(而非只看变更点)?
      
      ---
      
      ## 高质量问题示例库
      
      ### 示例 1: 系统性架构问题
      
      ```
      问题:auto-test-skill 的核心价值是"发现系统性问题",
      但当前工作流只要求"列出 10 个问题",没有区分"表面问题"vs"深层问题"。
      
      位置:SKILL.md:87-90(问题数量要求)
      
      问题类型:架构设计问题
      
      现象:
      工作流只规定了问题数量(≥ 10),未规定问题质量,
      导致 AI 倾向于列出"不痛不痒"的表面问题(如"缺少注释")。
      
      影响:
      技能无法实现"发现系统性问题"的核心价值,
      沦为"表面问题列表生成器"。
      
      优先级:P0
      
      修复建议:
      在 SKILL.md 第 87-90 行增加"问题深度要求":
      ```
      ### 问题深度要求(强制)
      每轮必须包含至少 3 个"系统性问题":
      - 架构设计问题(工作流/配置/文件结构)
      - 过度设计问题(不必要的抽象/配置)
      - 一致性问题(跨文件矛盾)
      - 安全性问题(路径遍历/命令注入)
      ```
      
      验证方法:
      执行 3 轮 A 轮测试,确认每轮都包含至少 3 个系统性问题。
      ```
      
      ### 示例 2: 过度设计问题
      
      ```
      问题:session_format 配置项使用复杂的 Jinja2 模板语法,
      但实际只有一个固定格式。
      
      位置:config.yaml:24
      
      问题类型:过度设计问题
      
      现象:
      ```yaml
      session_format: "v{year}{month}{day}{hour}{minute}"
      ```
      这个配置项看似"灵活",但实际上:
      1. 没有其他格式选项
      2. 用户不需要自定义时间戳格式
      3. 增加 YAML 解析复杂度(需要 Jinja2 引擎)
      
      影响:
      - 过度设计,增加理解成本
      - 增加 YAML 解析复杂度
      - 无实际灵活性
      
      优先级:P1
      
      修复建议:
      移除 session_format 配置项,直接使用 Python datetime 格式化:
      ```python
      # scripts/create_test_session.py
      session_id = datetime.now().strftime("v%Y%m%d%H%M")
      ```
      
      验证方法:
      1. 删除 config.yaml 中的 session_format
      2. 运行 scripts/create_test_session.py
      3. 确认生成的 session_id 格式为 v202601161200
      ```
      
      ### 示例 3: 安全性问题
      
      ```
      问题:路径验证只检查 `../` 前缀,无法防御绕过攻击。
      
      位置:scripts/validator.py:45-48
      
      问题类型:安全性问题
      
      现象:
      ```python
      if path.startswith("../"):
          raise ValueError("Invalid path")
      ```
      只检查 `../` 前缀,但以下攻击向量可绕过:
      - `./../etc/passwd`
      - `.././../etc/passwd`
      - 绝对路径 `/etc/passwd`
      - Windows: `..\\..\\windows\\system32`
      
      影响:
      存在路径遍历漏洞,用户可访问任意文件,
      包括敏感信息(如 `/etc/passwd`、`~/.ssh/id_rsa`)。
      
      优先级:P0
      
      修复建议:
      ```python
      import os
      
      resolved = os.path.realpath(path)
      base_dir = os.path.realpath("./data")
      
      if not resolved.startswith(base_dir):
          raise ValueError(f"Path {path} is outside base directory")
      
      return resolved
      ```
      
      验证方法:
      1. 构造输入 `./../../etc/passwd`,确认被拒绝
      2. 构造输入 `.././../etc/passwd`,确认被拒绝
      3. 构造输入 `data/test.csv`,确认通过
      ```
      
      ---
      
      ## 使用指南
      
      ### 何时使用本文档?
      
      在执行 A 轮测试时,按以下顺序使用:
      
      1. **开始前**:阅读「框架 1: 系统视角思考」,建立全局意识
      2. **分析时**:使用「框架 2: 刁钻角度思考」,挖掘隐藏问题
      3. **评估时**:使用「框架 3: 问题质量标准」,确保问题价值
      4. **提交前**:使用「批判性思维检查清单」,最后把关
      
      ### 组合使用技巧
      
      为达到 10-20 个高质量问题,建议组合使用:
      
      1. **系统视角**(框架 1)→ 发现 3-5 个系统性问题(P0/P1)
      2. **刁钻角度**(框架 2)→ 发现 3-5 个安全性/边缘问题(P0/P1)
      3. **常规检查**(ISSUE_DISCOVERY_TECHNIQUES.md)→ 发现 4-8 个细节问题(P1/P2)
      
      **总计**:10-18 个问题,P0+P1 占比 ≥ 60%
      
      ---
      
      **模板说明**:
      
      本文档是 auto-test-skill 的核心思考指南。
      
      使用时:
      1. 每轮 A 轮开始前,快速浏览三大框架
      2. 挖掘问题时,对照框架检查是否遗漏重要角度
      3. 提交前,使用检查清单最后把关
      
    • EXAMPLE_STRICT_MINIMAL.md 1.5 KB
      # 严格模式最小示例(P0-1 编号)
      
      本示例用于演示如何让“计划文档 + 测试报告”在严格模式下通过一致性检查:
      
      - 计划文档包含 `#### P0-1:` 这类可引用编号
      - 测试报告中出现相同编号
      - 运行验证脚本时使用 `--require-plan`
      
      ## 示例:.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/plans/vYYYYMMDDHHMM.md(节选)
      
      ```markdown
      #### P0-1: create_test_session 的 B 轮创建崩溃
      
      位置: auto-test-project/scripts/create_test_session.py:120
      
      影响: --kind b 无法使用
      
      修复建议: 先计算 session_name 再构造模板变量
      
      验证方法: 运行 create_test_session.py --kind b 并确认返回 0
      ```
      
      ## 示例:.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/vYYYYMMDDHHMM/TEST_REPORT.md(节选)
      
      ```markdown
      ### P0-1: create_test_session 的 B 轮创建崩溃
      
      修复前: --kind b 运行报错
      
      修复措施: 调整变量初始化顺序
      
      修复后: --kind b 可创建 .bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/B轮-vYYYYMMDDHHMM/
      
      验证方法:
      TASK_ROOT=".bensz-api/task-{yyyymmdd-hhmm}-{简短描述}"
      python3 auto-test-project/scripts/create_test_session.py --project-root . --task-root "$TASK_ROOT" --kind b --id vYYYYMMDDHHMM
      ```
      
      ## 严格验证命令
      
      在项目根目录执行:
      
      ```bash
      python3 auto-test-project/scripts/verify_test_session.py --project-root . --task-root "$TASK_ROOT" --require-plan "$TASK_ROOT/auto-test-project/output/tests/vYYYYMMDDHHMM"
      ```
      
    • EXAMPLE_TEST_REPORT.md 11.9 KB
      # 项目级测试报告示例
      
      这是一个完整的测试报告示例,展示 `auto-test-project` 期望的输出质量。
      
      **关键特征**:
      - 至少 10 个问题(本示例 12 个)
      - 每个问题都有:位置、影响、修复建议、验证方法
      - 使用多种问题挖掘技巧
      - 包含跨模块分析
      
      说明:本示例采用较旧的“问题 1/问题 2”编号风格;若你要启用验证脚本的严格模式(要求计划-报告用 `P0-1` 这种可引用编号对齐),请优先参考 `references/EXAMPLE_STRICT_MINIMAL.md`。
      
      ---
      
      # A 轮测试报告(v202601151200)
      
      **测试会话**: v202601151200
      **项目根目录**: /path/to/example-project
      **测试时间**: 2026-01-15 12:00
      **关联规划文档**: plans/v202601151200.md
      
      ---
      
      ## 执行摘要
      
      **状态**: ✅ 通过
      
      **简要说明**: 本轮测试对 example-project 进行了全面的项目级分析,发现 12 个问题(3 个 P0、5 个 P1、4 个 P2)。主要问题集中在跨模块一致性、配置管理和文档同步。已修复所有 P0 问题,P1 问题修复 80%,遗留问题将在下一轮处理。
      
      ---
      
      ## 问题发现(使用问题挖掘技巧)
      
      ### 技巧 1: 跨模块一致性检查(3 个问题)
      
      #### 问题 1: config.yaml 中的 `api.timeout` 在各模块中使用不一致
      
      **位置**:
      - `config.yaml:45`
      - `src/api/client.py:23`
      - `src/api/server.py:67`
      
      **问题描述**:
      - config.yaml 定义 `api.timeout: 30`(秒)
      - client.py 使用 `timeout=20`(硬编码)
      - server.py 使用 `timeout=30`(从配置读取)
      
      **影响**: API 调用超时行为不一致,可能导致客户端提前超时而服务器仍在处理
      
      **优先级**: P0
      
      **修复建议**:
      在 `src/api/client.py:23` 中,将硬编码的 `timeout=20` 改为从配置读取:
      ```python
      # 修复前
      response = requests.get(url, timeout=20)
      
      # 修复后
      from config import settings
      response = requests.get(url, timeout=settings.api.timeout)
      ```
      
      **验证方法**:
      ```bash
      # 1. 搜索代码中的硬编码超时
      grep -r "timeout=2" src/
      
      # 2. 确认所有超时都从配置读取
      grep -r "settings.api.timeout" src/
      ```
      
      ---
      
      #### 问题 2: 日志格式不统一
      
      **位置**:
      - `src/utils/logger.py:15-30`
      - `src/auth/service.py:45`
      - `src/data/repository.py:78`
      
      **问题描述**:
      - logger.py 定义 JSON 格式日志
      - auth.service 使用字符串拼接日志
      - data.repository 使用 f-string 日志
      
      **影响**: 日志解析困难,无法统一分析
      
      **优先级**: P1
      
      **修复建议**:
      统一使用 `logger.py` 中的 `structured_log()` 函数
      
      **验证方法**:
      ```bash
      # 搜索非结构化日志
      grep -r "logger\\.info\\|logger\\.error" src/ --include="*.py" | grep -v "structured_log"
      ```
      
      ---
      
      #### 问题 3: 错误码定义分散
      
      **位置**:
      - `src/api/errors.py:10-50`
      - `src/auth/errors.py:5-20`
      - `src/data/errors.py:8-15`
      
      **问题描述**:
      三个模块都定义错误码,但存在重复(如 `ERR_INVALID_PARAM`)
      
      **影响**: 错误处理逻辑混乱,客户端无法正确识别错误类型
      
      **优先级**: P1
      
      **修复建议**:
      将所有错误码集中到 `src/common/errors.py`,各模块导入使用
      
      **验证方法**:
      ```bash
      # 检查是否有重复的错误码定义
      grep -r "ERR_" src/ --include="*.py" | cut -d: -f2 | sort | uniq -d
      ```
      
      ---
      
      ### 技巧 2: 依赖关系分析(2 个问题)
      
      #### 问题 4: 循环依赖风险
      
      **位置**:
      - `src/api/__init__.py` 导入 `src/auth`
      - `src/auth/__init__.py` 导入 `src/utils`
      - `src/utils/__init__.py` 导入 `src/api`
      
      **问题描述**:
      虽然当前没有直接循环导入,但依赖关系复杂,未来重构风险高
      
      **影响**: 模块耦合度高,难以独立测试和维护
      
      **优先级**: P1
      
      **修复建议**:
      引入依赖注入容器(如 `dependency-injector`),解耦模块依赖
      
      **验证方法**:
      ```python
      # 使用 pydeps 生成依赖图
      pip install pydeps
      pydeps src --max-bacon=3 --cluster
      ```
      
      ---
      
      #### 问题 5: 第三方依赖版本不兼容
      
      **位置**:
      - `requirements.txt:15`
      - `requirements.txt:23`
      
      **问题描述**:
      - `requests==2.28.0`
      - `urllib3==2.0.0`(但 requests 2.28.0 要求 urllib3<1.27)
      
      **影响**: 安装时可能报错,运行时行为不确定
      
      **优先级**: P0
      
      **修复建议**:
      统一版本:`requests==2.28.0` 和 `urllib3==1.26.0`
      
      **验证方法**:
      ```bash
      # 使用 pip-check 检查依赖冲突
      pip install pip-check
      pip-check
      ```
      
      ---
      
      ### 技巧 3: 配置管理审查(2 个问题)
      
      #### 问题 6: 敏感信息硬编码
      
      **位置**:
      - `src/api/client.py:10`
      - `src/database/connection.py:5`
      
      **问题描述**:
      ```python
      # client.py:10
      API_KEY = "sk-1234567890abcdef"  # 硬编码
      
      # connection.py:5
      DB_PASSWORD = "password123"  # 硬编码
      ```
      
      **影响**: 严重安全风险,密钥泄露到代码仓库
      
      **优先级**: P0
      
      **修复建议**:
      使用环境变量或密钥管理服务(如 AWS Secrets Manager)
      
      **验证方法**:
      ```bash
      # 搜索硬编码密钥
      grep -r "sk-.*\|password.*=" src/ --include="*.py" -i
      ```
      
      ---
      
      #### 问题 7: 配置项未分类
      
      **位置**:
      - `config.yaml`(全文件)
      
      **问题描述**:
      所有配置项平铺在一起,未按功能模块分组
      
      **影响**: 配置文件难以维护,新增配置项容易遗漏
      
      **优先级**: P2
      
      **修复建议**:
      按模块分组配置:
      ```yaml
      # 修复前
      api.timeout: 30
      db.host: localhost
      auth.secret: key
      
      # 修复后
      api:
        timeout: 30
      
      database:
        host: localhost
      
      auth:
        secret: key
      ```
      
      **验证方法**:
      阅读配置文件,确认有清晰的章节分组
      
      ---
      
      ### 技巧 4: 文档同步检查(2 个问题)
      
      #### 问题 8: README.md 中的安装命令过时
      
      **位置**:
      - `README.md:20`
      - `requirements.txt`
      
      **问题描述**:
      README.md 中的安装命令:
      ```bash
      pip install -r requirements-dev.txt
      ```
      但实际文件名是 `requirements.txt`
      
      **影响**: 新用户无法按文档成功安装
      
      **优先级**: P1
      
      **修复建议**:
      修改 README.md:20 为 `pip install -r requirements.txt`
      
      **验证方法**:
      ```bash
      # 按文档执行安装命令,确认成功
      pip install -r requirements.txt
      ```
      
      ---
      
      #### 问题 9: API 文档与实际签名不符
      
      **位置**:
      - `docs/api.md:50-60`
      - `src/api/endpoints.py:45-55`
      
      **问题描述**:
      文档中 `GET /users/:id` 返回 `User` 对象
      实际代码返回 `Dict[str, Any]`
      
      **影响**: API 用户困惑,类型提示失效
      
      **优先级**: P2
      
      **修复建议**:
      统一文档和代码签名,建议使用 Pydantic 模型
      
      **验证方法**:
      对比文档中的响应类型与代码实际返回类型
      
      ---
      
      ### 技巧 5: 边缘情况压力测试(1 个问题)
      
      #### 问题 10: 空配置文件处理缺失
      
      **位置**:
      - `src/config/loader.py`
      
      **问题描述**:
      如果 `config.yaml` 为空或格式错误,程序崩溃而非使用默认值
      
      **影响**: 用户误删配置后无法启动程序
      
      **优先级**: P2
      
      **修复建议**:
      增加配置验证和默认值回退:
      ```python
      try:
          config = yaml.safe_load(f) or get_default_config()
      except Exception as e:
          logger.warning(f"Failed to load config: {e}, using defaults")
          config = get_default_config()
      ```
      
      **验证方法**:
      ```bash
      # 测试空配置
      mv config.yaml config.yaml.bak
      touch config.yaml  # 创建空文件
      python -m src.main  # 确认不崩溃
      mv config.yaml.bak config.yaml
      ```
      
      ---
      
      ### 技巧 6: 代码"模式匹配"(2 个问题)
      
      #### 问题 11: 异常处理模式不一致
      
      **位置**:
      - `src/api/client.py:50`
      - `src/auth/service.py:78`
      
      **问题描述**:
      ```python
      # client.py:50
      try:
          response = requests.get(url)
      except Exception as e:
          print(e)  # 吞掉异常
      
      # service.py:78
      try:
          user = authenticate(credentials)
      except Exception:
          raise AuthError("Authentication failed")  # 包装后抛出
      ```
      
      两处处理异常的模式完全不同
      
      **影响**: 代码风格不统一,调试困难
      
      **优先级**: P2
      
      **修复建议**:
      统一异常处理策略(参考 `src/utils/exceptions.py` 的指导)
      
      **验证方法**:
      ```bash
      # 搜索所有 try-except 块
      grep -r "try:" src/ --include="*.py" -A 2
      ```
      
      ---
      
      ## 问题修复记录
      
      ### P0-1: 敏感信息硬编码(问题 6)
      
      **位置**: `src/api/client.py:10`, `src/database/connection.py:5`
      
      **修复前**:
      ```python
      API_KEY = "sk-1234567890abcdef"
      DB_PASSWORD = "password123"
      ```
      
      **修复措施**:
      1. 安装 `python-dotenv`
      2. 创建 `.env` 文件:
      ```
      API_KEY=sk-1234567890abcdef
      DB_PASSWORD=password123
      ```
      3. 修改代码:
      ```python
      from dotenv import load_dotenv
      load_dotenv()
      
      API_KEY = os.getenv("API_KEY")
      DB_PASSWORD = os.getenv("DB_PASSWORD")
      ```
      4. 更新 `.gitignore` 忽略 `.env`
      
      **修复后**:
      敏感信息从代码中移除,环境变量加载正常
      
      **验证方法**:
      ```bash
      # 确认代码中无硬编码密钥
      grep -r "sk-.*\|password.*=" src/ --include="*.py" -i
      
      # 确认环境变量加载
      python -c "from src.api.client import API_KEY; print(API_KEY is not None)"
      ```
      
      **验证结果**: ✅ 通过
      
      ---
      
      ### P0-2: 第三方依赖版本不兼容(问题 5)
      
      **位置**: `requirements.txt:15,23`
      
      **修复前**:
      ```
      requests==2.28.0
      urllib3==2.0.0
      ```
      
      **修复措施**:
      修改 `requirements.txt`:
      ```
      requests==2.28.0
      urllib3==1.26.0
      ```
      
      **修复后**:
      依赖版本兼容,`pip-check` 无警告
      
      **验证方法**:
      ```bash
      pip-check
      ```
      
      **验证结果**: ✅ 通过
      
      ---
      
      ### P0-3: API 超时配置不一致(问题 1)
      
      **位置**: `src/api/client.py:23`
      
      **修复前**:
      ```python
      response = requests.get(url, timeout=20)  # 硬编码
      ```
      
      **修复措施**:
      1. 在 `config.yaml` 中定义 `api.timeout: 30`
      2. 在 `src/config.py` 中添加配置读取
      3. 修改 `client.py`:
      ```python
      from src.config import settings
      response = requests.get(url, timeout=settings.api.timeout)
      ```
      
      **修复后**:
      所有 API 调用都使用配置的超时值
      
      **验证方法**:
      ```bash
      grep -r "timeout=2" src/  # 应该无结果
      grep -r "settings.api.timeout" src/  # 应该有结果
      ```
      
      **验证结果**: ✅ 通过
      
      ---
      
      ### P1-1: 日志格式不统一(问题 2)
      
      **位置**: `src/auth/service.py:45`, `src/data/repository.py:78`
      
      **修复前**:
      ```python
      # service.py:45
      logger.info(f"User {user_id} logged in")  # f-string
      
      # repository.py:78
      print("Error: " + str(e))  # print
      ```
      
      **修复措施**:
      统一使用 `structured_log()`:
      ```python
      from src.utils.logger import structured_log
      
      structured_log("info", "User logged in", user_id=user_id)
      structured_log("error", "Data error", error=str(e))
      ```
      
      **修复后**:
      所有日志都是 JSON 格式,可被日志解析器处理
      
      **验证方法**:
      ```bash
      grep -r "logger\\.info\\|logger\\.error\\|print(" src/ --include="*.py" | grep -v "structured_log"
      ```
      
      **验证结果**: ✅ 通过(修复 80%)
      
      ---
      
      ## 问题修复统计
      
      | 优先级 | 计划修复 | 实际修复 | 修复率 |
      |--------|----------|----------|--------|
      | P0 | 3 | 3 | 100% |
      | P1 | 5 | 4 | 80% |
      | P2 | 4 | 0 | 0% |
      | **总计** | 12 | 7 | 58% |
      
      ---
      
      ## 遗留问题
      
      - **P1-2**: 错误码定义分散(问题 3) - 原因:需要大规模重构,安排在下一轮
      - **P1-3**: 循环依赖风险(问题 4) - 原因:需要架构评审,安排在下一轮
      - **P1-5**: README.md 安装命令过时(问题 8) - 原因:非阻塞,稍后修复
      - **P2-1**: 配置项未分类(问题 7) - 原因:优化项,优先级低
      - **P2-2**: API 文档不符(问题 9) - 原因:文档问题,优先级低
      - **P2-3**: 空配置文件处理(问题 10) - 原因:边缘情况,优先级低
      - **P2-4**: 异常处理不一致(问题 11) - 原因:代码风格问题,优先级低
      
      ---
      
      ## 证据文件
      
      - [依赖检查输出](_artifacts/pip-check.txt)
      - [硬编码密钥扫描](_artifacts/secrets-scan.txt)
      - [配置验证](_artifacts/config-test.yaml)
      
      ---
      
      ## 下一步建议
      
      **是否需要下一轮**: 是
      
      **重点**:
      1. **修复 P1-2**(错误码定义分散):创建 `src/common/errors.py`,集中所有错误码
      2. **修复 P1-3**(循环依赖风险):引入依赖注入,重构模块依赖
      3. **修复 P1-5**(README.md 安装命令过时):更新文档
      4. **补充 P2 问题**:处理剩余优化项
      
      ---
      
      **测试人**: Claude(auto-test-project 示例)
      **测试时间**: 2026-01-15 12:00
      **测试状态**: ✅ 通过(12 个问题,100% P0 修复率)
      
    • FAQ.md 4.7 KB
      # auto-test-project FAQ(常见问题)
      
      本文件用于存放容易反复出现的问答与细节规则,避免 `SKILL.md` 过长。
      
      ## Q: 如何检测“假计划、空报告”?
      
      优先使用验证脚本(推荐),并在需要时启用严格模式。
      
      ```bash
      TASK_ROOT=".bensz-api/task-{yyyymmdd-hhmm}-{简短描述}"
      # 1) 快速检查:是否残留模板占位符(双大括号)
      grep -r "{{" "$TASK_ROOT/auto-test-project/output/tests/vYYYYMMDDHHMM/"
      
      # 2) 验证脚本(推荐)
      python3 auto-test-project/scripts/verify_test_session.py --project-root . --task-root "$TASK_ROOT" "$TASK_ROOT/auto-test-project/output/tests/vYYYYMMDDHHMM"
      
      # 3) 严格模式(推荐在收尾/回归阶段使用)
      # - 要求 .bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/plans/vYYYYMMDDHHMM.md 存在
      # - 要求 plan 内包含形如 "#### P0-1:" 的编号,才能做计划-报告一致性检查
      python3 auto-test-project/scripts/verify_test_session.py --project-root . --task-root "$TASK_ROOT" --require-plan "$TASK_ROOT/auto-test-project/output/tests/vYYYYMMDDHHMM"
      ```
      
      ## Q: 如果发现计划与执行脱节怎么办?
      
      建议按以下顺序修复(从“可追溯性”到“可复现证据”):
      
      1. 重新阅读 `.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/plans/vYYYYMMDDHHMM.md` 的问题清单(确认每个问题都有编号,如 `P0-1`)。
      2. 检查 `.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/vYYYYMMDDHHMM/TEST_REPORT.md` 是否包含相同编号的修复记录。
      3. 对缺失项补齐“修复前 → 修复措施 → 修复后 → 验证命令/输出”。
      4. 重新运行验证脚本,直到通过。
      
      ## Q: TEST_REPORT.md 应该包含哪些证据?
      
      证据优先级从高到低:
      
      | 证据类型 | 示例 | 优先级 |
      |---------|------|--------|
      | 命令输出 | `git diff`、`pytest`、构建日志 | ⭐⭐⭐⭐⭐ |
      | 文件引用 | `src/file.py:123`、截图路径 | ⭐⭐⭐⭐ |
      | 对比结果 | 修复前后对比、通过率对比 | ⭐⭐⭐⭐ |
      | 量化指标 | 覆盖率、耗时、规模变化 | ⭐⭐⭐ |
      | 文字描述 | “已修复并验证正常” | ⭐⭐ |
      
      ## Q: 如何避免运行示例命令时污染项目根目录?
      
      - 确认自己在“目标项目根目录”执行 `create_test_session.py`,不要在仓库根目录随手运行。
      - 调用方已有本轮 task root 时,用 `--task-root` 显式复用;省略它只用于开始新逻辑任务,脚本会分配唯一的 `task-*` 根,不会自动选择最近任务。
      - 脚本会在 `--project-root` 下创建 `.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/plans/` 与 `.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/`;默认拒绝将系统根目录或用户主目录作为 project-root,如需覆盖请显式使用 `--allow-unsafe-root`。
      - 中间产物统一放进 `.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/<session>/_artifacts/` 与 `.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/<session>/_scripts/`,避免在项目根散落临时文件。
      
      旧 `.bensz-api/skills/auto-test-project/` 只能通过 `verify_test_session.py --legacy-root ...` 或 `verify_all_sessions.py --legacy-root ...` 显式只读验证;默认创建和验证不会扫描或写入它。
      
      ## Q: 项目类型识别失败怎么办?
      
      - 手动在计划文档里声明项目类型与测试边界(例如“这是一个 Agent Skill/工作流项目/脚本工具集”)。
      - 确保项目根目录存在至少一个项目指令文件(如 `CLAUDE.md`、`AGENTS.md`、`README.md`)。
      - 如需更系统的识别规则,可参考 `config.yaml:project_detection` 的启发式配置。
      
      ## Q: 测试会话太多怎么办?
      
      - 测试会话目录主要是文档与少量证据文件,通常建议保留以便追溯与复盘。
      - 如果确需清理,推荐归档而不是删除:例如将早期会话移动到 `tests_archive/`(保留关键里程碑会话)。
      
      ## Q: 如何处理跨模块问题?
      
      - 在计划文档中明确:受影响模块列表、依赖关系、预期连锁反应。
      - 在 TEST_PLAN 中增加集成验证点(例如“修改 A 后,验证 B 的调用仍正常”)。
      - 在 TEST_REPORT 中记录跨模块验证的命令与输出,避免只做描述性结论。
      
      ## Q: 项目级质量检查与 skill 级别有什么区别?
      
      项目级质量检查更强调:
      
      - 跨模块一致性(接口、命名、配置、文档)
      - 架构层面的过度设计(模块边界、抽象层次、配置复杂度)
      - 项目级安全风险(外部接口、依赖、路径与权限)
      - 全局冗余与残留(重复逻辑、僵尸文件/引用)
      
    • PROJECT_ISSUE_DISCOVERY_TECHNIQUES.md 21.3 KB
      # 项目级问题挖掘技巧
      
      本文档提供专门针对**项目级测试**的问题挖掘技巧,帮助发现跨模块、跨文件的系统性问题。
      
      ## 核心区别
      
      **skill 级别测试** vs **项目 级别测试**:
      - skill 级别:关注单个 SKILL.md、config.yaml 的质量
      - 项目级别:关注**跨模块一致性**、**架构设计**、**依赖关系**、**配置管理**
      
      ## 使用原则
      
      **⚠️ 批判性思维优先**:本文档的技巧分为两类,使用时**优先使用批判性分析框架**,再辅以技术检查技巧。
      
      | 技巧类型 | 目的 | 优先级 |
      |---------|------|--------|
      | **批判性分析框架(技巧 0)** | 质疑设计合理性、评估架构价值、挖掘问题本质 | ⭐⭐⭐ **最高** |
      | **技术检查技巧(技巧 1-8)** | 发现具体缺陷、验证一致性、检测安全隐患 | ⭐⭐ 辅助 |
      
      **为什么批判性分析优先?**
      - 技术检查容易发现"表面问题"(如配置不一致、日志格式不统一)
      - 批判性分析能发现"本质问题"(如为什么需要两个配置?日志策略是否合理?)
      - 项目级测试的核心价值在于**系统视角和架构洞察**,而非替代 linter
      
      **必读(建议每轮 A 轮先过一遍)**:
      - `references/CRITICAL_THINKING_GUIDE.md`:批判性思维框架(含“刁钻角度/边缘情况/恶意输入”)
      - `references/CONSTRUCTIVE_SUGGESTION_GUIDELINES.md`:建设性建议标准(可执行/有证据/可验证)
      - `references/ANTI_PATTERNS_LIBRARY.md`:反例库(快速识别常见反模式)
      
      **独立评估提醒**:
      - A 轮默认不查看历史 `plans/` 与 `tests/`,避免确认偏差;只基于当前项目状态做“重新审视”。
      
      ---
      
      # 技巧 0: 批判性分析框架(系统视角)⭐️ 优先使用
      
      **核心理念**:从"发现问题"升级为"质疑设计合理性",挖掘问题的本质而非表象。
      
      ## 0.1 第一性原理思考
      
      **适用场景**:评估项目是否偏离核心目标,识别"为了做而做"的功能。
      
      ### 检查维度
      
      #### 核心目标对齐度分析
      - 这个项目**真正要解决的问题**是什么?(从 CLAUDE.md/AGENTS.md 提取)
      - 当前每个模块/功能是否**对核心目标有直接贡献**?
      - 是否存在"看起来重要,但与核心目标无关"的功能?
      
      **批判性问题示例**:
      ```
      问题:项目目标是"简化 Agent Skills 开发",但包含了一个复杂的依赖注入框架
      批判性质疑:
      - 这个依赖注入框架是否对"简化开发"有直接贡献?
      - 还是增加了学习成本和复杂度?
      - 能否用更简单的方案(如配置文件)替代?
      
      影响:偏离核心目标,增加用户学习成本
      优先级:P0(架构级偏离)
      ```
      
      #### 功能必要性三问
      对每个"功能/模块/配置项",问:
      1. **如果删除它,核心功能是否还能工作?** → 如果能,为什么存在?
      2. **它解决的是真实痛点,还是假设的需求?** → 有证据表明用户需要吗?
      3. **它的存在是否引入了新的复杂度?** → 收益是否大于成本?
      
      **验证方法**:
      ```bash
      # 1. 提取项目核心目标(从项目指令文件)
      grep -A 5 "项目目标\|核心价值\|目的" CLAUDE.md AGENTS.md
      
      # 2. 列出所有模块/功能
      find . -name "*.py" -o -name "SKILL.md" | head -20
      
      # 3. 对每个模块问:它对核心目标的贡献是什么?
      # (需要人工评估,AI 无法自动化判断)
      ```
      
      ---
      
      ## 0.2 架构合理性质疑
      
      **适用场景**:评估模块划分、依赖方向、抽象层次的合理性。
      
      ### 检查维度
      
      #### 模块边界合理性
      - 模块划分是否遵循**单一职责原则**?
      - 是否存在"万能模块"(什么都做,职责不清)?
      - 是否存在"碎片化模块"(一个功能拆成多个小模块)?
      
      **批判性问题示例**:
      ```
      问题:utils.py 包含了 15 个不相关的工具函数(从字符串处理到数据库连接)
      批判性质疑:
      - 这些函数真的属于"工具"吗?还是缺乏清晰的模块定位?
      - 是否应该按领域拆分(如 string_utils.py, db_utils.py)?
      - 还是有更高层的抽象可以统一它们?
      
      影响:代码组织混乱,难以复用和测试
      优先级:P1(模块组织问题)
      ```
      
      #### 依赖方向合理性
      - 依赖方向是否**符合分层架构**(如:业务层不应依赖基础设施层)?
      - 是否存在**依赖倒置**(底层模块依赖上层模块)?
      - 是否存在**循环依赖**(A → B → A)?
      
      **批判性问题示例**:
      ```
      问题:src/auth(认证模块)依赖 src/utils(工具模块),但 utils 又依赖 auth
      批判性质疑:
      - 为什么工具模块需要认证功能?是否职责混淆?
      - 是否应该将认证相关功能提升为独立模块?
      - 还是 utils 不应该包含业务逻辑?
      
      影响:模块耦合度高,难以独立测试和复用
      优先级:P0(架构级问题)
      ```
      
      #### 抽象层次合理性
      - 配置项数量是否反映**过度设计**?(如 50+ 配置项)
      - 是否存在"为了扩展性而扩展性"的抽象?(如用户不需要的功能开关)
      - 抽象层次是否**符合项目规模**?(小项目用企业级框架)
      
      **批判性问题示例**:
      ```
      问题:config.yaml 包含 67 个配置项,但项目只有 3 个核心功能
      批判性质疑:
      - 这些配置项是否真的都需要用户配置?
      - 还是缺乏合理的默认值?
      - 是否应该提供"预设模式"(如 --mode=simple)?
      
      影响:用户配置负担重,学习成本高
      优先级:P1(配置设计问题)
      ```
      
      **验证方法**:
      ```bash
      # 1. 生成依赖关系图(Python 项目)
      pip install pydeps
      pydeps src --max-bacon=3 --cluster --show-deps
      # 检查是否有循环依赖、不合理的依赖方向
      
      # 2. 统计配置项数量
      grep -c "^[a-z_]*:" config.yaml
      # 对比项目规模(如代码行数、模块数量)
      ```
      
      ---
      
      ## 0.3 价值导向的问题分类
      
      **适用场景**:避免发现大量"噪音级问题",聚焦高价值问题。
      
      ### 分类维度
      
      #### 痛点级(P0-P1)- 用户核心功能受阻
      - **特征**:不修复就无法使用,或严重影响体验
      - **示例**:核心功能崩溃、安全漏洞、性能严重退化
      - **判断标准**:用户是否会因为这个问题放弃使用项目?
      
      #### 隐患级(P1-P2)- 当前可用但未来风险
      - **特征**:不影响当前功能,但会累积技术债务
      - **示例**:代码重复、模块耦合、缺少测试
      - **判断标准**:3 个月内是否会引发更大的问题?
      
      #### 噪音级(P2-P3)- 不影响功能的表面问题
      - **特征**:只在代码审查时有意义,用户无感知
      - **示例**:变量命名风格、注释格式、空行数量
      - **判断标准**:修复它是否会让项目更好?还是只是为了"看起来专业"?
      
      **⚠️ 使用建议**:
      - **优先记录痛点级和隐患级问题**,噪音级问题可选
      - **每轮至少有 1-2 个痛点级问题**,否则说明分析深度不够
      - **对噪音级问题标注"可选优化"**,避免浪费资源
      
      ---
      
      ## 0.4 根本原因分析(5 Whys)
      
      **适用场景**:挖掘问题的本质,而非修复表象。
      
      ### 分析方法
      
      对每个 P0/P1 问题,连续问 5 次"为什么",直到找到根本原因。
      
      **示例**:
      ```
      表面问题:配置超时值在两个文件中不一致(20秒 vs 30秒)
      
      为什么 1:为什么会有两个超时值?
      → 因为 client.py 和 server.py 各自定义了超时
      
      为什么 2:为什么各自定义,而不是共享配置?
      → 因为没有统一的配置管理模块
      
      为什么 3:为什么没有统一配置管理?
      → 因为项目初期是快速原型,后来功能增长但没重构
      
      为什么 4:为什么没重构?
      → 因为缺少配置管理的架构设计
      
      为什么 5:为什么缺少架构设计?
      → 因为项目从"脚本"演进为"框架"时,没有重新评估架构
      
      根本原因:**项目演进过程中缺少架构重构机制**
      修复建议:
      - P0:创建统一配置管理模块
      - P1:建立"架构演进检查点"(如每新增一个模块,评估是否需要重构)
      - 避免修复:只统一超时值(表象修复,未来还会出现类似问题)
      ```
      
      **验证方法**:
      - 在问题记录中增加"根本原因"字段
      - 修复建议应针对根本原因,而非表象
      - 如果无法回答 5 次"为什么",说明问题理解不够深入
      
      ---
      
      ## 0.5 批判性分析检查清单
      
      **每轮 A 轮必须回答的问题**:
      
      ### 系统视角
      - [ ] 本轮发现的问题中,至少有 **1-2 个是架构级/设计级问题**(非表面问题)
      - [ ] 本轮使用的批判性分析框架是:(如技巧 0.1 第一性原理、0.2 架构质疑)
      - [ ] 本轮发现的问题中,**痛点级:隐患级:噪音级 的比例是否合理**(推荐 2:5:3)
      
      ### 问题深度
      - [ ] 每个 P0/P1 问题都有**根本原因分析**(至少回答 3 次"为什么")
      - [ ] 每个 P0/P1 问题都有**批判性质疑**(质疑设计合理性,而非只描述现象)
      - [ ] 修复建议是否针对**根本原因**,而非表象
      
      ### 价值判断
      - [ ] 本轮发现的问题中,**是否至少有 1 个问题质疑了"为什么需要这个功能/模块/配置?"**
      - [ ] 本轮发现的问题中,**是否避免了"为了修复而修复"的噪音问题?**
      - [ ] 本轮是否对**项目架构合理性**提出了建设性质疑?
      
      **如果无法勾选以上项目,说明本轮分析深度不足,需要重新使用批判性分析框架。**
      
      ---
      
      ## 技巧 1: 跨模块一致性检查
      
      **适用场景**:验证多个模块是否遵循相同的规范
      
      **检查维度**:
      
      ### 1.1 接口一致性
      - 不同模块的 API 签名是否一致?
      - 错误码定义是否统一?
      - 返回值格式是否一致?
      
      **示例问题**:
      ```
      问题:模块 A 使用 `Result<T>` 返回类型,模块 B 使用 `Tuple[bool, T]`
      影响:调用方需要处理两种不同的返回模式
      优先级:P1
      ```
      
      **验证方法**:
      ```bash
      # 搜索函数签名模式
      grep -r "def.*-> " src/ --include="*.py" | sort | uniq -c
      
      # 检查返回类型定义
      grep -r "class.*Result\|class.*Response" src/ --include="*.py"
      ```
      
      ### 1.2 配置一致性
      - 相同的配置项在不同模块中是否有不同的值?
      - 配置项命名是否统一(camelCase vs snake_case)?
      
      **示例问题**:
      ```
      问题:`api.timeout` 在 client.py 中是 20 秒(硬编码),在 server.py 中是 30 秒(配置读取)
      影响:超时行为不一致
      优先级:P0
      ```
      
      **验证方法**:
      ```bash
      # 搜索硬编码的超时值
      grep -r "timeout=\\|timeout :" src/ --include="*.py"
      
      # 对比配置文件
      grep -r "timeout" config.yaml
      ```
      
      ### 1.3 日志格式一致性
      - 日志级别使用是否统一(INFO vs info)?
      - 日志格式是否统一(JSON vs 文本)?
      - 日志位置是否统一(文件 vs 控制台)?
      
      **验证方法**:
      ```bash
      # 搜索不同的日志调用模式
      grep -r "logger\\.\\|logging\\." src/ --include="*.py" | grep -oE "logger\\.[a-z]+" | sort | uniq -c
      ```
      
      ---
      
      ## 技巧 2: 依赖关系分析
      
      **适用场景**:发现模块间的耦合问题和依赖风险
      
      ### 2.1 循环依赖检测
      - 模块 A 是否导入模块 B,同时 B 也导入 A?
      - 间接循环依赖(A → B → C → A)?
      
      **示例问题**:
      ```
      问题:src/api/__init__.py 导入 src/auth,src/auth/__init__.py 导入 src/utils,src/utils/__init__.py 导入 src/api
      影响:模块耦合度高,难以独立测试
      优先级:P1
      ```
      
      **验证方法**:
      ```bash
      # 使用 pydeps 生成依赖图
      pip install pydeps
      pydeps src --max-bacon=3 --cluster --dot
      # 检查生成的图中是否有循环箭头
      
      # 或者使用模块分析工具
      python -c "
      import sys
      sys.path.insert(0, 'src')
      import importlib
      import pkgutil
      
      def find_dependencies(module_name, visited=None):
          if visited is None:
              visited = set()
          if module_name in visited:
              return []
          visited.add(module_name)
      
          try:
              module = importlib.import_module(module_name)
          except ImportError:
              return []
      
          deps = []
          for importer, modname, ispkg in pkgutil.walk_packages(module.__path__ if hasattr(module, '__path__') else [], prefix=module.__name__ + '.'):
              if modname not in visited:
                  deps.append(modname)
                  deps.extend(find_dependencies(modname, visited))
          return deps
      
      # 检查每个模块的依赖
      for module in ['api', 'auth', 'utils']:
          print(f'{module}: {find_dependencies(module)}')
      "
      ```
      
      ### 2.2 第三方依赖冲突检测
      - 不同模块依赖的同一库的版本是否冲突?
      - 是否有重复依赖(相同功能的不同库)?
      
      **示例问题**:
      ```
      问题:requests==2.28.0 要求 urllib3<1.27,但 requirements.txt 中 urllib3==2.0.0
      影响:安装失败或运行时不确定
      优先级:P0
      ```
      
      **验证方法**:
      ```bash
      # 使用 pip-check 检查依赖冲突
      pip install pip-check
      pip-check
      
      # 或使用 pip-audit 检查安全漏洞
      pip install pip-audit
      pip-audit
      ```
      
      ### 2.3 未使用的依赖检测
      - requirements.txt 中是否有从未导入的库?
      - 是否有被导入但未使用的功能?
      
      **验证方法**:
      ```bash
      # 使用 pip-autoremove 检查未使用的依赖
      pip install pip-autoremove
      pip-autoremove --dry-run
      ```
      
      ---
      
      ## 技巧 3: 配置管理审查
      
      **适用场景**:发现配置文件相关的问题
      
      ### 3.1 敏感信息检查
      - 配置文件中是否包含硬编码的密钥、密码?
      - 是否有敏感信息泄露到日志?
      
      **示例问题**:
      ```
      问题:config.yaml 中包含 `database.password: "password123"`
      影响:严重安全风险
      优先级:P0
      ```
      
      **验证方法**:
      ```bash
      # 搜索硬编码密钥
      grep -ri "password.*=\\|secret.*=\\|api.*key.*=" config/ src/ --include="*.yaml" --include="*.py" -i
      
      # 搜索常见的密钥模式
      grep -r "sk-.*\\|AKIA.*\\|Bearer.*" config/ src/ --include="*.yaml" --include="*.py"
      ```
      
      ### 3.2 配置项分类检查
      - 配置文件是否按功能模块分组?
      - 配置项命名是否具有描述性?
      
      **验证方法**:
      ```bash
      # 检查配置文件是否有清晰的章节
      grep -E "^#+ .*:" config.yaml
      
      # 检查配置项的嵌套层级
      python -c "
      import yaml
      with open('config.yaml') as f:
          config = yaml.safe_load(f)
      
      def print_structure(obj, prefix='', max_depth=3):
          if isinstance(obj, dict) and max_depth > 0:
              for key, value in obj.items():
                  print(f'{prefix}{key}: {type(value).__name__}')
                  if isinstance(value, dict):
                      print_structure(value, prefix + '  ', max_depth - 1)
                  elif isinstance(value, list) and value:
                      print(f'{prefix}  - List of {type(value[0]).__name__}')
      
      print_structure(config)
      "
      ```
      
      ### 3.3 环境特定配置检查
      - 是否有开发/生产环境的配置混合?
      - 是否有环境变量未正确使用?
      
      **验证方法**:
      ```bash
      # 搜索环境相关的配置
      grep -r "dev\\|prod\\|test\\|staging" config.yaml
      
      # 检查是否有环境变量但未定义
      grep -r "os\\.getenv\\|os\\.environ" src/ --include="*.py" | grep -oE 'os\\.getenv\\("([^"]+)"' | sort | uniq
      ```
      
      ---
      
      ## 技巧 4: 文档同步检查
      
      **适用场景**:发现文档与代码不一致的问题
      
      ### 4.1 README 与代码一致性
      - README 中的安装命令是否有效?
      - README 中的示例代码是否可运行?
      
      **示例问题**:
      ```
      问题:README.md 中说 `pip install -r requirements-dev.txt`,但实际文件名是 requirements.txt
      影响:新用户无法按文档成功安装
      优先级:P1
      ```
      
      **验证方法**:
      ```bash
      # 测试 README 中的安装命令
      pip install -r $(grep -oE "requirements[^ ]+" README.md | head -1)
      
      # 测试示例代码
      python -c "$(grep -A 10 "```python" README.md | grep -v "```" | head -5)"
      ```
      
      ### 4.2 API 文档与实际签名对比
      - API 文档中的参数列表与实际函数签名是否一致?
      - 返回值类型是否匹配?
      
      **验证方法**:
      ```bash
      # 提取 API 文档中的函数签名
      grep -oE "[a-z_]+\\([^)]*\\)" docs/api.md
      
      # 对比实际代码中的函数签名
      grep -oE "^def [a-z_]+\\([^)]*\\)" src/api/endpoints.py
      
      # 使用工具自动化检查
      pip install interrogate
      interrogate src/api --verbose
      ```
      
      ### 4.3 变更日志同步
      - CHANGELOG.md 是否记录了最近的变更?
      - 版本号是否在所有地方同步?
      
      **验证方法**:
      ```bash
      # 检查最近一次提交与 CHANGELOG 的日期差异
      LATEST_CHANGE=$(grep -E "^## \\[" CHANGELOG.md | head -1 | grep -oE "[0-9-]+")
      LATEST_COMMIT=$(git log -1 --format=%cs | head -1)
      echo "CHANGELOG: $LATEST_CHANGE"
      echo "Commit: $LATEST_COMMIT"
      ```
      
      ---
      
      ## 技巧 5: 边缘情况压力测试
      
      **适用场景**:发现极端情况下的处理缺陷
      
      ### 5.1 空配置/缺失配置处理
      - 配置文件为空时是否使用默认值?
      - 配置文件格式错误时是否有友好提示?
      
      **示例问题**:
      ```
      问题:config.yaml 为空时,程序直接崩溃而非使用默认值
      影响:用户误删配置后无法启动
      优先级:P2
      ```
      
      **验证方法**:
      ```bash
      # 备份配置
      cp config.yaml config.yaml.bak
      
      # 测试空配置
      echo "" > config.yaml
      python -m src.main 2>&1 | head -20
      
      # 测试格式错误
      echo "invalid: yaml: content: [" > config.yaml
      python -m src.main 2>&1 | head -20
      
      # 恢复配置
      mv config.yaml.bak config.yaml
      ```
      
      ### 5.2 资源耗尽场景
      - 内存耗尽时的处理?
      - 磁盘空间不足时的处理?
      - 网络超时的处理?
      
      **验证方法**:
      ```bash
      # 使用 ulimit 限制内存
      ulimit -v 1048576  # 限制为 1GB
      python -m src.main
      
      # 使用 fallocate 模拟磁盘满
      dd if=/dev/zero of=disk_hog.img bs=1G seek=10G 2>&1 | head -1
      ```
      
      ### 5.3 并发访问场景
      - 多个请求同时到达时的处理?
      - 数据库连接池耗尽时的处理?
      
      **验证方法**:
      ```bash
      # 使用 wrk 进行压力测试
      pip install wrk
      wrk -t10 -c100 -d30s http://localhost:8000/api/endpoint
      ```
      
      ---
      
      ## 技巧 6: 代码"模式匹配"
      
      **适用场景**:发现代码风格和不一致的模式
      
      ### 6.1 异常处理模式
      - try-except 块是否统一使用日志记录?
      - 是否有"吞掉异常"的情况?
      
      **示例问题**:
      ```
      问题:client.py 中使用 `except Exception: pass`,而 server.py 中使用 `except Exception: raise`
      影响:调试困难,错误处理不一致
      优先级:P2
      ```
      
      **验证方法**:
      ```bash
      # 搜索所有 try-except 块
      grep -r "try:" src/ --include="*.py" -A 2 | grep -E "(except|raise|return)"
      
      # 搜索吞掉异常
      grep -r "except.*:" src/ --include="*.py" -A 1 | grep "pass"
      ```
      
      ### 6.2 资源清理模式
      - 文件句柄是否正确关闭?
      - 数据库连接是否正确释放?
      - 是否使用 context managers?
      
      **验证方法**:
      ```bash
      # 搜索文件操作但没有使用 with
      grep -r "open(" src/ --include="*.py" | grep -v "with open"
      
      # 搜索数据库连接但没有使用 context manager
      grep -r "connect(" src/ --include="*.py" | grep -v "with"
      ```
      
      ### 6.3 导入顺序模式
      - 导入语句是否按标准顺序(stdlib、第三方、本地)?
      - 是否有未使用的导入?
      
      **验证方法**:
      ```bash
      # 使用 isort 检查导入顺序
      pip install isort
      isort --check-only --diff src/
      
      # 使用 pyflakes 检查未使用的导入
      pip install pyflakes
      pyflakes src/
      ```
      
      ---
      
      ## 技巧 7: 安全性扫描
      
      **适用场景**:发现潜在的安全漏洞
      
      ### 7.1 SQL 注入风险
      - 是否有字符串拼接的 SQL 查询?
      - 是否有用户输入直接拼接到查询中?
      
      **验证方法**:
      ```bash
      # 搜索字符串拼接的 SQL
      grep -r "SELECT.*FROM.*+\\|UPDATE.*SET.*+" src/ --include="*.py" -i
      
      # 使用 bandit 进行安全扫描
      pip install bandit
      bandit -r src/
      ```
      
      ### 7.2 命令注入风险
      - 是否有用户输入直接传递到 subprocess?
      - 是否有 shell=True 的调用?
      
      **验证方法**:
      ```bash
      # 搜索 subprocess 调用
      grep -r "subprocess\\.\\|os\\.system" src/ --include="*.py"
      
      # 搜索 shell=True
      grep -r "shell=True" src/ --include="*.py"
      ```
      
      ### 7.3 路径遍历风险
      - 是否有用户输入直接用于文件路径?
      - 是否有 `..` 路径的检查?
      
      **验证方法**:
      ```bash
      # 搜索路径操作
      grep -r "open(.*+\\|Path(.*+\\|os\\.path\\.join" src/ --include="*.py"
      
      # 使用 semgrep 检查路径遍历
      pip install semgrep
      semgrep --config=auto --lang=python --pattern="path_traversal" src/
      ```
      
      ---
      
      ## 技巧 8: 性能分析
      
      **适用场景**:发现性能瓶颈
      
      ### 8.1 N+1 查询问题
      - 是否有循环中的数据库查询?
      - 是否有重复的查询?
      
      **验证方法**:
      ```bash
      # 使用 Django Debug Toolbar 或类似工具
      # 或手动检查循环中的查询
      grep -r "for.*in.*:" src/ --include="*.py" -A 5 | grep -E "\\.filter\\|\\.get\\|\\.all"
      ```
      
      ### 8.2 内存泄漏风险
      - 是否有未释放的资源?
      - 是否有全局列表不断增长?
      
      **验证方法**:
      ```bash
      # 使用 memory_profiler
      pip install memory_profiler
      python -m memory_profiler src/main.py
      ```
      
      ### 8.3 算法复杂度问题
      - 是否有 O(n²) 的嵌套循环?
      - 是否有不必要的大数据集处理?
      
      **验证方法**:
      ```bash
      # 使用 vprof 进行性能分析
      pip install vprof
      vprof src/main.py
      ```
      
      ---
      
      ## 使用建议
      
      ### 组合使用技巧
      
      **每轮推荐使用 3-5 个技巧组合**,例如:
      
      **第一轮**(基础检查):
      1. 跨模块一致性检查
      2. 配置管理审查
      3. 文档同步检查
      
      **第二轮**(深度分析):
      4. 依赖关系分析
      5. 代码"模式匹配"
      6. 边缘情况压力测试
      
      **第三轮**(专项检查):
      7. 安全性扫描
      8. 性能分析
      
      ### 记录问题发现技巧
      
      在 `plans/vYYYYMMDDHHMM.md` 中,为每个问题标注使用的挖掘技巧:
      
      ```markdown
      #### P0-1: 敏感信息硬编码
      
      **发现技巧**:技巧 3.1(敏感信息检查)
      
      **位置**: `config.yaml:45`
      
      ...
      ```
      
      这样可以追踪哪些技巧最有效,调整后续轮次的策略。
      
    • PROJECT_TESTING_BEST_PRACTICES.md 4 KB
      # 项目级测试驱动优化:最佳实践
      
      本文件用于为 `auto-test-project` 提供稳定、可复用的参考原则,避免在 SKILL.md 中反复硬编码细节。
      
      ## 项目级测试的边界
      
      - 目标:验证"关键路径"与"最近修改的行为"是否正确,不追求全覆盖
      - 原则:快、明确、可重复、可追溯
      - 范围:覆盖核心模块和跨模块交互
      
      ## 测试会话的最小产出
      
      每轮测试会话目录至少包含:
      
      - `TEST_PLAN.md`:本轮验证点与通过标准
      - `TEST_REPORT.md`:本轮结果、证据与结论
      
      推荐包含:
      
      - `_artifacts/`:日志、输出、截图、对比结果等
      - `_scripts/`:必要的临时测试脚本(尽量保持小且可删)
      
      ## 命名与目录
      
      - 规划文档:`.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/plans/vYYYYMMDDHHMM.md`
      - A轮测试:`.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/vYYYYMMDDHHMM/`
      - B轮检查:`.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/plans/B轮-vYYYYMMDDHHMM.md`
      - B轮验证:`.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/B轮-vYYYYMMDDHHMM/`
      
      ## 记录原则
      
      - 一个结论必须对应至少一个可复现的证据(命令输出、文件、截图、对比结果)
      - 发现新问题时:立刻记录优先级(P0/P1/P2)与复现步骤
      - 跨模块问题:记录受影响的模块范围和依赖关系
      
      ## 项目级测试的特殊性
      
      ### 1. 跨模块验证
      
      - **模块间接口测试**:验证模块间调用是否正确
      - **集成测试**:验证多个模块协同工作是否符合预期
      - **依赖关系验证**:验证模块依赖是否正确且无循环
      
      ### 2. 测试边界管理
      
      - **核心模块识别**:优先测试项目的核心功能模块
      - **测试范围定义**:明确哪些在测试范围内,哪些排除
      - **优先级设置**:根据模块重要性设置测试优先级
      
      ### 3. 一致性检查
      
      - **接口一致性**:跨模块的接口定义是否一致
      - **配置一致性**:项目级配置与模块级配置是否一致
      - **文档一致性**:文档描述与实际实现是否一致
      
      ## 项目级问题处理
      
      ### 问题分类
      
      | 类型 | 描述 | 示例 |
      |------|------|------|
      | **单模块问题** | 仅影响单个模块 | 函数内部逻辑错误 |
      | **跨模块问题** | 影响多个模块 | 接口不匹配、调用错误 |
      | **架构问题** | 影响项目整体 | 模块边界不清、职责重叠 |
      
      ### 问题修复顺序
      
      1. **P0 问题**:立即修复,优先考虑跨模块 P0
      2. **P1 问题**:24小时内,考虑依赖关系
      3. **P2 问题**:3天内,可批量处理
      4. **P3 问题**:1周内,可延后处理
      
      ### 修复验证
      
      - 单模块问题:单元测试验证
      - 跨模块问题:集成测试验证
      - 架构问题:多场景综合验证
      
      ## 项目级质量检查
      
      ### 硬编码/AI 功能规划(项目级)
      
      - 跨模块的重复操作是否脚本化
      - 项目级配置是否集中管理
      - AI 是否仅承担启发式判断
      
      ### 冗余残留错误检查(项目级)
      
      - 跨模块的重复逻辑
      - 全局范围的残留引用
      - 僵尸模块或僵尸文件
      
      ### 安全性检查(项目级)
      
      - 外部接口安全
      - 依赖包安全性
      - 配置文件敏感信息
      
      ### 过度设计检查(项目级)
      
      - 模块边界是否清晰
      - 是否有不必要的抽象层
      - 配置是否过于复杂
      
      ### 通用性检查(项目级)
      
      - 是否过度依赖特定平台
      - 文档示例是否通用
      - 项目结构是否可移植
      
      ### 一致性检查(项目级)
      
      - 项目指令文件一致性
      - 跨模块接口一致性
      - 命名规范一致性
      
      ## 文档更新原则
      
      - 项目级变更:更新项目 CHANGELOG.md
      - 跨模块变更:更新相关模块文档
      - 单模块变更:更新模块文档
      - 配置变更:同步更新配置文件说明
      
      ## 持续优化建议
      
      - 每轮测试后,评估测试覆盖率
      - 定期审查测试计划的有效性
      - 根据项目演进调整测试边界
      - 保持测试文档与项目状态同步
      
  • scripts
    • create_test_session.py 15 KB
      #!/usr/bin/env python3
      from __future__ import annotations
      
      import argparse
      import datetime as dt
      import re
      import shutil
      import sys
      import typing
      from pathlib import Path
      
      from workspace_paths import resolve_workspace
      
      _TEST_ID_RE = re.compile(r"^v\d{12}$")
      
      _DEFAULT_DIRECTORIES = {
          "plans": "output/plans",
          "tests": "output/tests",
      }
      
      _DEFAULT_TEMPLATES = {
          "optimization_plan": "templates/OPTIMIZATION_PLAN_TEMPLATE.md",
          "b_round_check": "templates/B_ROUND_CHECK_TEMPLATE.md",
          "test_plan": "templates/TEST_PLAN_TEMPLATE.md",
          "test_report": "templates/TEST_REPORT_TEMPLATE.md",
      }
      
      
      def _generate_test_id(now: dt.datetime) -> str:
          return f"v{now:%Y%m%d%H%M}"
      
      
      def _ensure_dir(path: Path) -> None:
          path.mkdir(parents=True, exist_ok=True)
      
      
      def _safe_write(path: Path, content: str, *, overwrite: bool) -> None:
          if path.exists() and not overwrite:
              raise FileExistsError(f"Refusing to overwrite existing file: {path}")
          path.write_text(content, encoding="utf-8")
      
      
      def _render_template(template: str, *, values: dict[str, str]) -> str:
          rendered = template
          for key, value in values.items():
              rendered = rendered.replace(f"{{{{{key}}}}}", value)
          return rendered
      
      
      def _copy_or_template(
          *,
          dst_path: Path,
          src_path: Path | None,
          template_path: Path | None,
          template_values: dict[str, str] | None,
          overwrite: bool,
      ) -> None:
          if dst_path.exists() and not overwrite:
              return
      
          if src_path is not None and src_path.exists():
              if dst_path.exists():
                  dst_path.unlink()
              shutil.copyfile(src_path, dst_path)
              return
      
          if template_path is not None and template_path.exists():
              template_text = template_path.read_text(encoding="utf-8")
              if template_values:
                  template_text = _render_template(template_text, values=template_values)
              _safe_write(dst_path, template_text, overwrite=overwrite)
              return
      
          _safe_write(dst_path, "# TEST_PLAN\n\n(未找到可复制的计划文档或模板,请手动补全)\n", overwrite=overwrite)
      
      
      def _normalize_kind(kind: str) -> str:
          kind = kind.strip().lower()
          if kind in {"a", "a_round", "a-round", "a轮"}:
              return "a"
          if kind in {"b", "b_round", "b-round", "b轮"}:
              return "b"
          raise ValueError("kind must be a/b (also accepts: A轮/B轮)")
      
      
      def _fail(parser: argparse.ArgumentParser, message: str) -> typing.NoReturn:
          parser.print_usage(sys.stderr)
          print(f"error: {message}", file=sys.stderr)
          raise SystemExit(2)
      
      
      def _strip_inline_comment(value: str) -> str:
          if "#" not in value:
              return value
          return value.split("#", 1)[0].rstrip()
      
      
      def _parse_simple_yaml_sections(text: str, *, wanted_sections: set[str]) -> dict[str, dict[str, str]]:
          """
          Parse a minimal subset of YAML:
          - top-level mapping keys (no indentation)
          - one level nested key/value pairs under a wanted section (2+ spaces)
          """
          result: dict[str, dict[str, str]] = {}
          current: str | None = None
          for raw in text.splitlines():
              line = raw.rstrip("\n")
              if not line.strip() or line.lstrip().startswith("#"):
                  continue
      
              if not line.startswith(" ") and line.endswith(":"):
                  section = line[:-1].strip()
                  current = section if section in wanted_sections else None
                  continue
      
              if current is None:
                  continue
      
              if line.startswith("  ") and ":" in line:
                  key, value = line.split(":", 1)
                  key = key.strip()
                  value = _strip_inline_comment(value.strip())
                  if not value:
                      continue
                  if (value.startswith('"') and value.endswith('"')) or (value.startswith("'") and value.endswith("'")):
                      value = value[1:-1]
                  result.setdefault(current, {})[key] = value
      
          return result
      
      
      def _load_config_sections(config_path: Path) -> dict[str, dict[str, str]]:
          wanted = {"directories", "templates"}
          if not config_path.exists():
              return {}
      
          text = config_path.read_text(encoding="utf-8")
          try:
              import yaml  # type: ignore
          except Exception:
              return _parse_simple_yaml_sections(text, wanted_sections=wanted)
      
          try:
              data = yaml.safe_load(text) or {}
          except Exception:
              return _parse_simple_yaml_sections(text, wanted_sections=wanted)
      
          out: dict[str, dict[str, str]] = {}
          for section in wanted:
              v = data.get(section)
              if isinstance(v, dict):
                  out[section] = {str(k): str(vv) for k, vv in v.items() if isinstance(vv, (str, int, float))}
          return out
      
      
      def _merge_section(*, base: dict[str, str], override: dict[str, str] | None) -> dict[str, str]:
          merged = dict(base)
          if override:
              merged.update({k: v for k, v in override.items() if v})
          return merged
      
      
      def _safe_rel_path(value: str, *, default: str) -> str:
          if not value:
              return default
          p = Path(value)
          if p.is_absolute() or ".." in p.parts:
              return default
          return value
      
      
      def _resolve_template_path(*, skill_root: Path, rel_path: str) -> Path | None:
          # Safety: refuse templates that resolve outside skill root (symlink escape).
          candidate = skill_root / rel_path
          if not candidate.exists():
              return None
          resolved = candidate.resolve()
          try:
              resolved.relative_to(skill_root)
          except ValueError:
              return None
          return resolved
      
      
      def _ensure_dir_within_root(
          parser: argparse.ArgumentParser,
          *,
          root: Path,
          path: Path,
          label: str,
      ) -> None:
          """
          Ensure we only create/write under --project-root; reject symlinks for safety.
          """
          if path.exists():
              if path.is_symlink():
                  _fail(parser, f"{label} must not be a symlink: {path}")
              if not path.is_dir():
                  _fail(parser, f"{label} must be a directory: {path}")
      
          _ensure_dir(path)
          resolved = path.resolve()
          try:
              resolved.relative_to(root)
          except ValueError:
              _fail(parser, f"{label} resolves outside --project-root: {path} -> {resolved}")
      
      
      def _validate_project_root(project_root: Path) -> None:
          if not project_root.exists() or not project_root.is_dir():
              raise FileNotFoundError(f"Project root does not exist or is not a directory: {project_root}")
      
          instruction_files = ["CLAUDE.md", "AGENTS.md", "PROJECT.md", "README.md"]
          has_instruction = any((project_root / f).exists() for f in instruction_files)
          if not has_instruction:
              print(f"warning: no project instruction file found in {project_root}", file=sys.stderr)
              print(f"expected one of: {', '.join(instruction_files)}", file=sys.stderr)
      
      
      def _detect_project_type(project_root: Path) -> str:
          """
          Minimal heuristic used only for template filling.
          """
          if (project_root / "SKILL.md").exists():
              return "skill"
          if (project_root / ".github" / "workflows").exists() or (project_root / "workflows").exists():
              return "workflow"
          if (project_root / "scripts").exists() or (project_root / "bin").exists():
              return "script_collection"
          if (project_root / "docs").exists() or (project_root / "mkdocs.yml").exists():
              return "documentation"
          return "unknown"
      
      
      def main() -> int:
          parser = argparse.ArgumentParser(
              description="Create an auto-test-project test session skeleton (A round or B round).",
          )
          parser.add_argument(
              "--project-root",
              required=True,
              help="Path to project root directory (contains CLAUDE.md, AGENTS.md, or similar).",
          )
          parser.add_argument(
              "--kind",
              default="a",
              help="Session kind: a (default) or b (also accepts: A轮/B轮).",
          )
          parser.add_argument(
              "--task-root",
              default="",
              help=(
                  "Existing task root to reuse, relative to --project-root or absolute. "
                  "It must be a direct .bensz-api/task-YYYYMMDD-HHMM-<description> child."
              ),
          )
          parser.add_argument(
              "--task-description",
              default="auto-test-project",
              help="Description slug used only when allocating a new task root (default: auto-test-project).",
          )
          parser.add_argument(
              "--id",
              default="",
              help="Explicit test id like vYYYYMMDDHHMM (optional).",
          )
          parser.add_argument(
              "--a-test-id",
              default="",
              help="For B round: the corresponding A-round id (optional; defaults to --id).",
          )
          parser.add_argument(
              "--create-plan",
              action="store_true",
              help="Create missing plan doc skeleton under configured plans dir (optional).",
          )
          parser.add_argument(
              "--allow-unsafe-root",
              action="store_true",
              help="Allow using filesystem root or user home as --project-root (not recommended).",
          )
          parser.add_argument(
              "--seed-test-plan-from-plan",
              action="store_true",
              help="If plan doc exists, seed TEST_PLAN.md from it (optional).",
          )
          parser.add_argument(
              "--overwrite",
              action="store_true",
              help="Overwrite existing session files (not recommended).",
          )
          args = parser.parse_args()
      
          explicit_id = args.id.strip()
          if explicit_id and not _TEST_ID_RE.fullmatch(explicit_id):
              _fail(parser, "--id must match vYYYYMMDDHHMM (e.g. v202601151230)")
      
          try:
              kind = _normalize_kind(args.kind)
          except ValueError as exc:
              _fail(parser, str(exc))
      
          now = dt.datetime.now()
          test_id = explicit_id or _generate_test_id(now)
          if not _TEST_ID_RE.fullmatch(test_id):
              _fail(parser, "test id must match vYYYYMMDDHHMM (omit --id to auto-generate)")
      
          if kind == "a":
              session_name = test_id
              round_kind = "A轮"
              plan_rel = f"{test_id}.md"
              plan_template_key = "optimization_plan"
              a_test_id = test_id
          else:
              session_name = f"B轮-{test_id}"
              round_kind = "B轮"
              plan_rel = f"B轮-{test_id}.md"
              plan_template_key = "b_round_check"
              a_test_id = args.a_test_id.strip() or test_id
              if not _TEST_ID_RE.fullmatch(a_test_id):
                  _fail(parser, "--a-test-id must match vYYYYMMDDHHMM (e.g. v202601151230)")
      
          project_root = Path(args.project_root).expanduser().resolve()
      
          # Safety guard: prevent accidental pollution of extremely broad directories.
          anchor_root = Path(project_root.anchor) if project_root.anchor else project_root
          is_fs_root = project_root == anchor_root
          is_home = project_root == Path.home().resolve()
          if (is_fs_root or is_home) and not args.allow_unsafe_root:
              _fail(parser, f"Refusing unsafe --project-root: {project_root} (use --allow-unsafe-root to override)")
      
          skill_source_root = Path(__file__).resolve().parent.parent
          cfg = _load_config_sections(skill_source_root / "config.yaml")
          directories = _merge_section(base=_DEFAULT_DIRECTORIES, override=cfg.get("directories"))
          templates = _merge_section(base=_DEFAULT_TEMPLATES, override=cfg.get("templates"))
      
          def template_path(config_key: str) -> Path | None:
              rel = _safe_rel_path(templates.get(config_key, ""), default=_DEFAULT_TEMPLATES.get(config_key, ""))
              if not rel:
                  return None
              return _resolve_template_path(skill_root=skill_source_root, rel_path=rel)
      
          try:
              plan_template = template_path(plan_template_key)
              test_plan_template = template_path("test_plan")
              test_report_template = template_path("test_report")
          except (FileNotFoundError, ValueError) as exc:
              _fail(parser, str(exc))
      
          try:
              _validate_project_root(project_root)
          except FileNotFoundError as exc:
              _fail(parser, str(exc))
      
          try:
              workspace = resolve_workspace(
                  project_root=project_root,
                  task_root_arg=args.task_root,
                  task_description=args.task_description,
                  directories=directories,
                  create=True,
                  now=now,
              )
          except (FileExistsError, FileNotFoundError, ValueError) as exc:
              _fail(parser, str(exc))
      
          plans_dir = workspace.plans_dir
          tests_dir = workspace.tests_dir
      
          plan_src = plans_dir / plan_rel
      
          project_type = _detect_project_type(project_root)
          template_values: dict[str, str] = {
              "TEST_ID": test_id,
              "PROJECT_NAME": project_root.name,
              "PROJECT_ROOT": str(project_root),
              "TASK_ROOT": workspace.task_root.relative_to(project_root).as_posix(),
              "SKILL_WORKSPACE": workspace.skill_root.relative_to(project_root).as_posix(),
              "SESSION_NAME": session_name,
              "ROUND_KIND": round_kind,
              "TEST_TIME": now.strftime("%Y-%m-%d %H:%M:%S"),
              "TEST_DATE": now.date().isoformat(),
              "PLAN_ID": test_id,
              "PLAN_TIME": now.isoformat(timespec="minutes"),
              "PLAN_DOC_PATH": plan_src.relative_to(project_root).as_posix(),
              "PLAN_FILE": plan_src.relative_to(project_root).as_posix(),
              "PROJECT_TYPE": project_type,
          }
      
          template_values["A_TEST_ID"] = a_test_id
          template_values["A_ROUND_ID"] = a_test_id
      
          if args.create_plan and (not plan_src.exists() or args.overwrite):
              if plan_template is not None:
                  _safe_write(
                      plan_src,
                      _render_template(plan_template.read_text(encoding="utf-8"), values=template_values),
                      overwrite=args.overwrite,
                  )
              else:
                  _safe_write(plan_src, f"# 计划文档({session_name})\n\n(未找到模板,请手动补全)\n", overwrite=args.overwrite)
      
          session_dir = tests_dir / session_name
          _ensure_dir_within_root(
              parser, root=workspace.skill_root, path=session_dir, label="session directory"
          )
          _ensure_dir_within_root(
              parser, root=workspace.skill_root, path=session_dir / "_artifacts", label="artifacts directory"
          )
          _ensure_dir_within_root(
              parser, root=workspace.skill_root, path=session_dir / "_scripts", label="scripts directory"
          )
      
          _copy_or_template(
              dst_path=session_dir / "TEST_PLAN.md",
              src_path=plan_src if (args.seed_test_plan_from_plan and plan_src.exists()) else None,
              template_path=test_plan_template,
              template_values=template_values,
              overwrite=args.overwrite,
          )
      
          report_path = session_dir / "TEST_REPORT.md"
          if not report_path.exists() or args.overwrite:
              if test_report_template is not None:
                  _safe_write(
                      report_path,
                      _render_template(test_report_template.read_text(encoding="utf-8"), values=template_values),
                      overwrite=args.overwrite,
                  )
              else:
                  _safe_write(
                      report_path,
                      "# 测试报告(TEST_REPORT)\n\n"
                      f"**测试会话**: {session_name}\n"
                      f"**项目根目录**: {project_root}\n\n"
                      "## 结果\n\n"
                      "- 状态:✅ 通过 / ❌ 失败 / ⚠️ 部分通过\n\n"
                      "## 证据\n\n"
                      "- (填入命令输出、文件路径、对比结果等)\n",
                      overwrite=args.overwrite,
                  )
      
          print(str(session_dir))
          return 0
      
      
      if __name__ == "__main__":
          raise SystemExit(main())
      
    • test_workspace_cli.py 2.8 KB
      from __future__ import annotations
      
      import subprocess
      import sys
      import tempfile
      import unittest
      from pathlib import Path
      
      
      SKILL_ROOT = Path(__file__).resolve().parents[1]
      VERIFY_SCRIPT = SKILL_ROOT / "scripts" / "verify_test_session.py"
      
      
      class WorkspaceCliTest(unittest.TestCase):
          def test_legacy_verification_requires_explicit_read_only_root(self) -> None:
              with tempfile.TemporaryDirectory(prefix="auto-test-project-legacy-") as tmp:
                  project_root = Path(tmp) / "project"
                  legacy_root = project_root / ".bensz-api" / "skills" / "auto-test-project"
                  plans_dir = legacy_root / "output" / "plans"
                  session_dir = legacy_root / "output" / "tests" / "v202608082204"
                  plans_dir.mkdir(parents=True)
                  session_dir.mkdir(parents=True)
                  (plans_dir / "v202608082204.md").write_text(
                      "# Legacy plan\n\n#### P0-1: verified legacy record\n", encoding="utf-8"
                  )
                  (session_dir / "TEST_PLAN.md").write_text("# TEST_PLAN\n", encoding="utf-8")
                  (session_dir / "TEST_REPORT.md").write_text(
                      "# TEST_REPORT\n\nP0-1\n\n```text\nlegacy evidence\n```\n",
                      encoding="utf-8",
                  )
                  before = sorted(path.relative_to(project_root) for path in project_root.rglob("*"))
      
                  explicit = subprocess.run(
                      [
                          sys.executable,
                          str(VERIFY_SCRIPT),
                          "--project-root",
                          str(project_root),
                          "--legacy-root",
                          str(legacy_root),
                          "--require-plan",
                          "--min-report-length",
                          "10",
                          "--min-issue-count",
                          "1",
                          str(session_dir),
                      ],
                      stdout=subprocess.PIPE,
                      stderr=subprocess.PIPE,
                      text=True,
                      check=False,
                  )
                  self.assertEqual(explicit.returncode, 0, explicit.stdout + explicit.stderr)
                  after = sorted(path.relative_to(project_root) for path in project_root.rglob("*"))
                  self.assertEqual(before, after)
      
                  implicit = subprocess.run(
                      [
                          sys.executable,
                          str(VERIFY_SCRIPT),
                          "--project-root",
                          str(project_root),
                          str(session_dir),
                      ],
                      stdout=subprocess.PIPE,
                      stderr=subprocess.PIPE,
                      text=True,
                      check=False,
                  )
                  self.assertEqual(implicit.returncode, 2)
                  self.assertNotIn("Traceback", implicit.stdout + implicit.stderr)
      
      
      if __name__ == "__main__":
          unittest.main()
      
    • test_workspace_paths.py 6.1 KB
      from __future__ import annotations
      
      import datetime as dt
      import sys
      import tempfile
      import unittest
      from pathlib import Path
      
      
      SCRIPTS_DIR = Path(__file__).resolve().parent
      if str(SCRIPTS_DIR) not in sys.path:
          sys.path.insert(0, str(SCRIPTS_DIR))
      
      from workspace_paths import (  # noqa: E402
          infer_active_workspace_from_session,
          resolve_legacy_workspace,
          resolve_workspace,
      )
      
      
      DIRECTORIES = {"plans": "output/plans", "tests": "output/tests"}
      
      
      class WorkspacePathsTest(unittest.TestCase):
          def setUp(self) -> None:
              self.tempdir = tempfile.TemporaryDirectory(prefix="auto-test-project-paths-")
              self.project_root = Path(self.tempdir.name) / "project"
              self.project_root.mkdir()
      
          def tearDown(self) -> None:
              self.tempdir.cleanup()
      
          def test_explicit_task_root_is_reused_without_overwriting_readme(self) -> None:
              task_root = self.project_root / ".bensz-api" / "task-20260808-2204-project-test"
              first = resolve_workspace(
                  project_root=self.project_root,
                  task_root_arg=str(task_root),
                  task_description="ignored",
                  directories=DIRECTORIES,
                  create=True,
              )
              self.assertTrue(first.created_task_root)
              self.assertTrue(first.plans_dir.is_dir())
              self.assertTrue(first.tests_dir.is_dir())
      
              readme = task_root / "README.md"
              readme.write_text("custom task readme\n", encoding="utf-8")
              second = resolve_workspace(
                  project_root=self.project_root,
                  task_root_arg=str(task_root),
                  task_description="ignored",
                  directories=DIRECTORIES,
                  create=True,
              )
              self.assertFalse(second.created_task_root)
              self.assertEqual(second.task_root, first.task_root)
              self.assertEqual(readme.read_text(encoding="utf-8"), "custom task readme\n")
      
          def test_new_task_allocation_uses_short_collision_suffix(self) -> None:
              now = dt.datetime(2026, 8, 8, 22, 4)
              first = resolve_workspace(
                  project_root=self.project_root,
                  task_root_arg="",
                  task_description="项目测试",
                  directories=DIRECTORIES,
                  create=True,
                  now=now,
              )
              second = resolve_workspace(
                  project_root=self.project_root,
                  task_root_arg="",
                  task_description="项目测试",
                  directories=DIRECTORIES,
                  create=True,
                  now=now,
              )
              self.assertEqual(first.task_root.name, "task-20260808-2204-项目测试")
              self.assertEqual(second.task_root.name, "task-20260808-2204-项目测试-a")
      
          def test_invalid_or_escaping_task_roots_are_rejected(self) -> None:
              invalid = [
                  "../task-20260808-2204-escape",
                  str(Path(self.tempdir.name) / "task-20260808-2204-outside"),
                  ".bensz-api/not-a-task",
              ]
              for value in invalid:
                  with self.subTest(value=value), self.assertRaises(ValueError):
                      resolve_workspace(
                          project_root=self.project_root,
                          task_root_arg=value,
                          task_description="ignored",
                          directories=DIRECTORIES,
                          create=True,
                      )
      
          def test_invalid_directory_config_does_not_allocate_task_root(self) -> None:
              with self.assertRaises(ValueError):
                  resolve_workspace(
                      project_root=self.project_root,
                      task_root_arg="",
                      task_description="invalid-config",
                      directories={"plans": "../escape", "tests": "output/tests"},
                      create=True,
                  )
              self.assertFalse((self.project_root / ".bensz-api").exists())
      
          def test_symlink_task_root_is_rejected(self) -> None:
              bensz_root = self.project_root / ".bensz-api"
              bensz_root.mkdir()
              outside = Path(self.tempdir.name) / "outside"
              outside.mkdir()
              link = bensz_root / "task-20260808-2204-linked"
              try:
                  link.symlink_to(outside, target_is_directory=True)
              except (NotImplementedError, OSError):
                  self.skipTest("directory symlinks are unavailable")
              with self.assertRaises(ValueError):
                  resolve_workspace(
                      project_root=self.project_root,
                      task_root_arg=str(link),
                      task_description="ignored",
                      directories=DIRECTORIES,
                      create=True,
                  )
      
          def test_legacy_root_is_explicit_and_read_only(self) -> None:
              legacy = self.project_root / ".bensz-api" / "skills" / "auto-test-project"
              (legacy / "output" / "plans").mkdir(parents=True)
              (legacy / "output" / "tests").mkdir(parents=True)
              before = sorted(path.relative_to(self.project_root) for path in self.project_root.rglob("*"))
              layout = resolve_legacy_workspace(
                  project_root=self.project_root,
                  legacy_root_arg=str(legacy),
                  directories=DIRECTORIES,
              )
              after = sorted(path.relative_to(self.project_root) for path in self.project_root.rglob("*"))
              self.assertTrue(layout.legacy)
              self.assertEqual(before, after)
      
              with self.assertRaises(ValueError):
                  infer_active_workspace_from_session(
                      project_root=self.project_root,
                      session_dir=layout.tests_dir / "v202608082204",
                      directories=DIRECTORIES,
                  )
      
          def test_active_session_inference_uses_task_local_suffixes(self) -> None:
              layout = resolve_workspace(
                  project_root=self.project_root,
                  task_root_arg=".bensz-api/task-20260808-2204-infer",
                  task_description="ignored",
                  directories=DIRECTORIES,
                  create=True,
              )
              session = layout.tests_dir / "v202608082204"
              session.mkdir()
              inferred = infer_active_workspace_from_session(
                  project_root=self.project_root,
                  session_dir=session,
                  directories=DIRECTORIES,
              )
              self.assertEqual(inferred.task_root, layout.task_root)
              self.assertEqual(inferred.plans_dir, layout.plans_dir)
      
      
      if __name__ == "__main__":
          unittest.main()
      
    • verify_all_sessions.py 6.7 KB
      #!/usr/bin/env python3
      from __future__ import annotations
      
      import argparse
      import os
      import subprocess
      import sys
      from pathlib import Path
      
      from workspace_paths import resolve_legacy_workspace, resolve_workspace
      
      
      def _strip_inline_comment(value: str) -> str:
          if "#" not in value:
              return value
          return value.split("#", 1)[0].rstrip()
      
      
      def _parse_simple_yaml_sections(text: str, *, wanted_sections: set[str]) -> dict[str, dict[str, str]]:
          result: dict[str, dict[str, str]] = {}
          current: str | None = None
          for raw in text.splitlines():
              line = raw.rstrip("\n")
              if not line.strip() or line.lstrip().startswith("#"):
                  continue
      
              if not line.startswith(" ") and line.endswith(":"):
                  section = line[:-1].strip()
                  current = section if section in wanted_sections else None
                  continue
      
              if current is None:
                  continue
      
              if line.startswith("  ") and ":" in line:
                  key, value = line.split(":", 1)
                  key = key.strip()
                  value = _strip_inline_comment(value.strip())
                  if not value:
                      continue
                  if (value.startswith('"') and value.endswith('"')) or (value.startswith("'") and value.endswith("'")):
                      value = value[1:-1]
                  result.setdefault(current, {})[key] = value
      
          return result
      
      
      def _safe_rel_path(value: str, *, default: str) -> str:
          if not value:
              return default
          p = Path(value)
          if p.is_absolute() or ".." in p.parts:
              return default
          return value
      
      
      def _load_directories_from_skill_config() -> dict[str, str]:
          skill_root = Path(__file__).resolve().parent.parent
          config_path = skill_root / "config.yaml"
          if not config_path.exists():
              return {}
      
          text = config_path.read_text(encoding="utf-8", errors="replace")
          try:
              import yaml  # type: ignore
          except Exception:
              data = _parse_simple_yaml_sections(text, wanted_sections={"directories"})
              return data.get("directories") or {}
      
          try:
              obj = yaml.safe_load(text) or {}
          except Exception:
              data = _parse_simple_yaml_sections(text, wanted_sections={"directories"})
              return data.get("directories") or {}
      
          v = obj.get("directories")
          if isinstance(v, dict):
              return {str(k): str(vv) for k, vv in v.items() if isinstance(vv, (str, int, float))}
          return {}
      
      
      def _run_verify(
          *,
          skill_source_root: Path,
          project_root: Path,
          task_root: Path | None,
          legacy_root: Path | None,
          session_dir: Path,
          require_plan: bool,
      ) -> subprocess.CompletedProcess[str]:
          cmd = [
              sys.executable,
              str(skill_source_root / "scripts" / "verify_test_session.py"),
              "--project-root",
              str(project_root),
          ]
          if task_root is not None:
              cmd.extend(["--task-root", str(task_root)])
          if legacy_root is not None:
              cmd.extend(["--legacy-root", str(legacy_root)])
          if require_plan:
              cmd.append("--require-plan")
          cmd.append(str(session_dir))
          env = os.environ.copy()
          artifact_root = project_root.resolve() / ".bensz-api"
          env.setdefault("PYTHONPYCACHEPREFIX", str(artifact_root / "__pycache__"))
          env.setdefault("RUFF_CACHE_DIR", str(artifact_root / ".ruff_cache"))
          return subprocess.run(
              cmd,
              cwd=str(project_root),
              text=True,
              stdout=subprocess.PIPE,
              stderr=subprocess.PIPE,
              env=env,
          )
      
      
      def main() -> int:
          parser = argparse.ArgumentParser(
              description="Verify all sessions under one explicit active task root or legacy read-only root."
          )
          parser.add_argument("--project-root", default=".", help="Project root that owns .bensz-api (default: .).")
          workspace_group = parser.add_mutually_exclusive_group(required=True)
          workspace_group.add_argument(
              "--task-root",
              default="",
              help="Active task root to verify; the command never creates or renames it.",
          )
          workspace_group.add_argument(
              "--legacy-root",
              default="",
              help="Explicit read-only .bensz-api/skills/auto-test-project compatibility root.",
          )
          parser.add_argument(
              "--require-plan",
              action="store_true",
              help="Run verify in strict mode (requires configured plans dir/<session_name>.md with P0-1 ids).",
          )
          parser.add_argument(
              "--skip-missing-plan",
              action="store_true",
              help="When --require-plan is enabled, skip sessions that lack the matching plan file instead of failing.",
          )
          args = parser.parse_args()
      
          project_root = Path(args.project_root).expanduser().resolve()
          dirs = _load_directories_from_skill_config()
          try:
              if args.task_root:
                  workspace = resolve_workspace(
                      project_root=project_root,
                      task_root_arg=args.task_root,
                      task_description="",
                      directories=dirs,
                      create=False,
                  )
              else:
                  workspace = resolve_legacy_workspace(
                      project_root=project_root,
                      legacy_root_arg=args.legacy_root,
                      directories=dirs,
                  )
          except (FileNotFoundError, ValueError) as exc:
              print(f"error: {exc}", file=sys.stderr)
              return 2
          tests_dir = workspace.tests_dir
          plans_dir = workspace.plans_dir
      
          sessions = sorted([p for p in tests_dir.iterdir() if p.is_dir()])
          if not sessions:
              print(f"warning: no sessions under {tests_dir}", file=sys.stderr)
              return 0
      
          failed = 0
          skipped = 0
          for session_dir in sessions:
              plan_path = plans_dir / f"{session_dir.name}.md"
              if args.require_plan and args.skip_missing_plan and not plan_path.exists():
                  skipped += 1
                  print(f"⏭️  SKIP (missing plan): {session_dir}")
                  continue
      
              proc = _run_verify(
                  skill_source_root=Path(__file__).resolve().parent.parent,
                  project_root=project_root,
                  task_root=None if workspace.legacy else workspace.task_root,
                  legacy_root=workspace.skill_root if workspace.legacy else None,
                  session_dir=session_dir,
                  require_plan=args.require_plan,
              )
              if proc.returncode == 0:
                  print(f"✅ PASS: {session_dir}")
              else:
                  failed += 1
                  print(f"❌ FAIL: {session_dir}")
                  if proc.stdout.strip():
                      print(proc.stdout.rstrip())
                  if proc.stderr.strip():
                      print(proc.stderr.rstrip(), file=sys.stderr)
      
          if args.require_plan and args.skip_missing_plan and skipped:
              print(f"note: skipped {skipped} sessions missing configured plans dir", file=sys.stderr)
      
          return 0 if failed == 0 else 1
      
      
      if __name__ == "__main__":
          raise SystemExit(main())
      
    • verify_skill.py 11.9 KB
      #!/usr/bin/env python3
      from __future__ import annotations
      
      import argparse
      import os
      import subprocess
      import sys
      import tempfile
      from pathlib import Path
      
      
      REQUIRED_FILES = [
          "SKILL.md",
          "README.md",
          "config.yaml",
          "CHANGELOG.md",
          "scripts/create_test_session.py",
          "scripts/verify_test_session.py",
          "scripts/verify_all_sessions.py",
          "scripts/workspace_paths.py",
          "scripts/test_workspace_paths.py",
          "scripts/test_workspace_cli.py",
          "templates/OPTIMIZATION_PLAN_TEMPLATE.md",
          "templates/B_ROUND_CHECK_TEMPLATE.md",
          "templates/TEST_PLAN_TEMPLATE.md",
          "templates/TEST_REPORT_TEMPLATE.md",
          "references/A_ROUND_PLAN_TEMPLATE.md",
          "references/FAQ.md",
          "references/PROJECT_TESTING_BEST_PRACTICES.md",
          "references/PROJECT_ISSUE_DISCOVERY_TECHNIQUES.md",
          "references/CRITICAL_THINKING_GUIDE.md",
          "references/CONSTRUCTIVE_SUGGESTION_GUIDELINES.md",
          "references/ANTI_PATTERNS_LIBRARY.md",
          "references/EXAMPLE_STRICT_MINIMAL.md",
          "references/EXAMPLE_TEST_REPORT.md",
      ]
      
      
      def _check_required_files(skill_root: Path) -> list[str]:
          missing: list[str] = []
          for rel in REQUIRED_FILES:
              if not (skill_root / rel).exists():
                  missing.append(rel)
          return missing
      
      
      def _run(cmd: list[str], *, cwd: Path) -> subprocess.CompletedProcess[str]:
          env = os.environ.copy()
          artifact_root = cwd.resolve() / ".bensz-api"
          env.setdefault("PYTHONPYCACHEPREFIX", str(artifact_root / "__pycache__"))
          env.setdefault("RUFF_CACHE_DIR", str(artifact_root / ".ruff_cache"))
          return subprocess.run(
              cmd,
              cwd=str(cwd),
              text=True,
              stdout=subprocess.PIPE,
              stderr=subprocess.PIPE,
              env=env,
          )
      
      
      def _assert_placeholders_replaced(path: Path, *, keys: list[str]) -> list[str]:
          text = path.read_text(encoding="utf-8", errors="replace")
          issues: list[str] = []
          for key in keys:
              token = f"{{{{{key}}}}}"
              if token in text:
                  issues.append(f"{path}: placeholder not replaced: {token}")
          return issues
      
      
      def _fill_valid_session(*, plan_path: Path, session_dir: Path) -> None:
          issue_ids = [f"P0-{index}" for index in range(1, 11)]
          plan_path.write_text(
              "# Self-check plan\n\n"
              + "\n".join(f"#### {issue_id}: deterministic check" for issue_id in issue_ids)
              + "\n",
              encoding="utf-8",
          )
          evidence = "\n".join(
              f"- {issue_id}: verified by scripts/verify_skill.py:1 with reproducible output."
              for issue_id in issue_ids
          )
          (session_dir / "TEST_PLAN.md").write_text(
              "# TEST_PLAN\n\n" + "\n".join(issue_ids) + "\n",
              encoding="utf-8",
          )
          (session_dir / "TEST_REPORT.md").write_text(
              "# TEST_REPORT\n\n## Evidence\n\n"
              + evidence
              + "\n\n## Verification\n\n"
              + ("The task-local workspace contract was verified. " * 20)
              + "\n",
              encoding="utf-8",
          )
      
      
      def main() -> int:
          parser = argparse.ArgumentParser(description="Verify auto-test-project skill integrity (deterministic checks).")
          parser.add_argument(
              "--skill-root",
              default=".",
              help="Path to auto-test-project skill root (default: current directory).",
          )
          args = parser.parse_args()
      
          skill_root = Path(args.skill_root).expanduser().resolve()
          if not (skill_root / "SKILL.md").exists():
              print(f"error: --skill-root does not look like a skill dir (missing SKILL.md): {skill_root}", file=sys.stderr)
              return 2
      
          failures: list[str] = []
      
          missing = _check_required_files(skill_root)
          if missing:
              failures.append("missing required files:\n  - " + "\n  - ".join(missing))
      
          # Basic syntax check for deterministic scripts.
          py_compile = _run(
              [
                  sys.executable,
                  "-m",
                  "py_compile",
                  "scripts/create_test_session.py",
                  "scripts/verify_test_session.py",
                  "scripts/verify_all_sessions.py",
                  "scripts/workspace_paths.py",
                  "scripts/test_workspace_paths.py",
                  "scripts/test_workspace_cli.py",
              ],
              cwd=skill_root,
          )
          if py_compile.returncode != 0:
              failures.append("py_compile failed:\n" + py_compile.stderr.strip())
      
          for test_script in [
              "scripts/test_workspace_paths.py",
              "scripts/test_workspace_cli.py",
          ]:
              proc = _run([sys.executable, test_script], cwd=skill_root)
              if proc.returncode != 0:
                  failures.append(
                      f"{test_script} failed:\n"
                      + (proc.stderr or proc.stdout).strip()
                  )
      
          # CLI sanity.
          for script in [
              "scripts/create_test_session.py",
              "scripts/verify_test_session.py",
              "scripts/verify_all_sessions.py",
          ]:
              proc = _run([sys.executable, script, "--help"], cwd=skill_root)
              if proc.returncode != 0:
                  failures.append(f"{script} --help failed:\n{proc.stderr.strip()}")
      
          # Regression guards for this Skill's own project-testing entrypoints.
          strict_templates = [
              skill_root / "templates/TEST_PLAN_TEMPLATE.md",
              skill_root / "templates/TEST_REPORT_TEMPLATE.md",
          ]
          for path in strict_templates:
              text = path.read_text(encoding="utf-8", errors="replace")
              if "--require-plan" not in text:
                  failures.append(f"{path}: missing strict mode example (--require-plan)")
      
          skill_md = (skill_root / "SKILL.md").read_text(encoding="utf-8", errors="replace")
          readme_md = (skill_root / "README.md").read_text(encoding="utf-8", errors="replace")
          for needle in ["references/FAQ.md", "references/EXAMPLE_STRICT_MINIMAL.md", "references/CRITICAL_THINKING_GUIDE.md"]:
              if needle not in skill_md and needle not in readme_md:
                  failures.append(f"docs missing reference to {needle} (expected in SKILL.md or README.md)")
      
          config_text = (skill_root / "config.yaml").read_text(encoding="utf-8", errors="replace")
          for key in ["skill_info:", "test_rounds:", "a_round_check:", "b_round_check:", "verification:"]:
              if key not in config_text:
                  failures.append(f"config.yaml missing key block: {key}")
      
          # Template auto-fill sanity: generate A/B sessions under a temp project root (no repo pollution).
          with tempfile.TemporaryDirectory(prefix="auto-test-project-selfcheck-") as tmp:
              dummy_root = Path(tmp) / "dummy_project"
              dummy_root.mkdir(parents=True, exist_ok=True)
              (dummy_root / "README.md").write_text("# Dummy Project\n", encoding="utf-8")
      
              a_id = "v200001010000"
              b_id = "v200001010001"
              task_root = dummy_root / ".bensz-api" / "task-20000101-0000-selfcheck"
      
              a_create = _run(
                  [
                      sys.executable,
                      str(skill_root / "scripts/create_test_session.py"),
                      "--project-root",
                      str(dummy_root),
                      "--task-root",
                      str(task_root),
                      "--kind",
                      "a",
                      "--id",
                      a_id,
                      "--create-plan",
                      "--overwrite",
                  ],
                  cwd=skill_root,
              )
              if a_create.returncode != 0:
                  failures.append("dummy A session creation failed:\n" + (a_create.stderr or a_create.stdout).strip())
              else:
                  a_plan = task_root / "auto-test-project" / "output" / "plans" / f"{a_id}.md"
                  a_session = task_root / "auto-test-project" / "output" / "tests" / a_id
                  failures.extend(
                      _assert_placeholders_replaced(
                          a_plan,
                          keys=["PLAN_ID", "PROJECT_ROOT", "PLAN_TIME", "SESSION_NAME"],
                      )
                  )
                  failures.extend(
                      _assert_placeholders_replaced(
                          a_session / "TEST_PLAN.md",
                          keys=["TEST_ID", "PROJECT_ROOT", "PROJECT_TYPE", "TEST_TIME", "PLAN_DOC_PATH", "ROUND_KIND", "SESSION_NAME", "TASK_ROOT", "SKILL_WORKSPACE"],
                      )
                  )
                  failures.extend(
                      _assert_placeholders_replaced(
                          a_session / "TEST_REPORT.md",
                          keys=["ROUND_KIND", "SESSION_NAME", "PROJECT_ROOT", "TEST_TIME", "PLAN_DOC_PATH", "TASK_ROOT", "SKILL_WORKSPACE"],
                      )
                  )
      
              b_create = _run(
                  [
                      sys.executable,
                      str(skill_root / "scripts/create_test_session.py"),
                      "--project-root",
                      str(dummy_root),
                      "--task-root",
                      str(task_root),
                      "--kind",
                      "b",
                      "--id",
                      b_id,
                      "--a-test-id",
                      a_id,
                      "--create-plan",
                      "--overwrite",
                  ],
                  cwd=skill_root,
              )
              if b_create.returncode != 0:
                  failures.append("dummy B session creation failed:\n" + (b_create.stderr or b_create.stdout).strip())
              else:
                  b_plan = task_root / "auto-test-project" / "output" / "plans" / f"B轮-{b_id}.md"
                  b_session = task_root / "auto-test-project" / "output" / "tests" / f"B轮-{b_id}"
                  failures.extend(
                      _assert_placeholders_replaced(
                          b_plan,
                          keys=["SESSION_NAME", "PLAN_TIME", "PROJECT_NAME", "PROJECT_ROOT", "PROJECT_TYPE", "A_TEST_ID"],
                      )
                  )
      
              if a_create.returncode == 0 and b_create.returncode == 0:
                  _fill_valid_session(plan_path=a_plan, session_dir=a_session)
                  _fill_valid_session(plan_path=b_plan, session_dir=b_session)
                  for session_dir in (a_session, b_session):
                      verify = _run(
                          [
                              sys.executable,
                              str(skill_root / "scripts/verify_test_session.py"),
                              "--project-root",
                              str(dummy_root),
                              "--task-root",
                              str(task_root),
                              "--require-plan",
                              str(session_dir),
                          ],
                          cwd=skill_root,
                      )
                      if verify.returncode != 0:
                          failures.append(
                              "task-local session verification failed:\n"
                              + (verify.stderr or verify.stdout).strip()
                          )
      
                  verify_all = _run(
                      [
                          sys.executable,
                          str(skill_root / "scripts/verify_all_sessions.py"),
                          "--project-root",
                          str(dummy_root),
                          "--task-root",
                          str(task_root),
                          "--require-plan",
                      ],
                      cwd=skill_root,
                  )
                  if verify_all.returncode != 0:
                      failures.append(
                          "task-local batch verification failed:\n"
                          + (verify_all.stderr or verify_all.stdout).strip()
                      )
      
              if (dummy_root / ".bensz-api" / "skills").exists():
                  failures.append("default self-check created the disabled .bensz-api/skills directory")
      
              missing_root = dummy_root / ".bensz-api" / "task-20000101-0000-missing"
              missing_verify = _run(
                  [
                      sys.executable,
                      str(skill_root / "scripts/verify_all_sessions.py"),
                      "--project-root",
                      str(dummy_root),
                      "--task-root",
                      str(missing_root),
                  ],
                  cwd=skill_root,
              )
              if missing_verify.returncode != 2 or "Traceback" in (missing_verify.stderr + missing_verify.stdout):
                  failures.append("missing task root did not produce a structured verification error")
      
          if failures:
              print("❌ verify_skill failed", file=sys.stderr)
              for item in failures:
                  print("\n" + item, file=sys.stderr)
              return 1
      
          print("✅ verify_skill passed")
          return 0
      
      
      if __name__ == "__main__":
          raise SystemExit(main())
      
    • verify_test_session.py 15.1 KB
      #!/usr/bin/env python3
      """
      验证测试会话的完整性
      
      用途:防止 auto-test-project 流程中出现"假计划、空报告"问题
      
      检查项:
      1. 必需文件是否存在(TEST_PLAN.md、TEST_REPORT.md)
      2. 模板占位符是否被替换(不允许出现 {{...}})
      3. 报告内容是否充实(不少于 500 字)
      4. 是否包含具体证据(命令输出、文件路径等)
      5. 计划与报告的一致性(新增)
      
      用法:
          python3 verify_test_session.py <session_dir>
          python3 verify_test_session.py --task-root .bensz-api/task-20260115-1400-project-test <session_dir>
          python3 verify_test_session.py --legacy-root .bensz-api/skills/auto-test-project <legacy_session_dir>
      
      退出码:
          0 - 验证通过
          1 - 验证失败
          2 - 参数错误
      """
      
      from __future__ import annotations
      
      import sys
      import re
      import argparse
      from pathlib import Path
      from typing import List, Tuple
      
      from workspace_paths import (
          infer_active_workspace_from_session,
          resolve_legacy_workspace,
          resolve_workspace,
      )
      
      REQUIRED_FILES = ["TEST_PLAN.md", "TEST_REPORT.md"]
      
      
      def _strip_inline_comment(value: str) -> str:
          if "#" not in value:
              return value
          return value.split("#", 1)[0].rstrip()
      
      
      def _parse_simple_yaml_sections(text: str, *, wanted_sections: set[str]) -> dict[str, dict[str, str]]:
          """
          Parse a minimal subset of YAML:
          - top-level mapping keys (no indentation)
          - one level nested key/value pairs under a wanted section (2+ spaces)
          """
          result: dict[str, dict[str, str]] = {}
          current: str | None = None
          for raw in text.splitlines():
              line = raw.rstrip("\n")
              if not line.strip() or line.lstrip().startswith("#"):
                  continue
      
              if not line.startswith(" ") and line.endswith(":"):
                  section = line[:-1].strip()
                  current = section if section in wanted_sections else None
                  continue
      
              if current is None:
                  continue
      
              if line.startswith("  ") and ":" in line:
                  key, value = line.split(":", 1)
                  key = key.strip()
                  value = _strip_inline_comment(value.strip())
                  if not value:
                      continue
                  if (value.startswith('"') and value.endswith('"')) or (value.startswith("'") and value.endswith("'")):
                      value = value[1:-1]
                  result.setdefault(current, {})[key] = value
      
          return result
      
      
      def _safe_rel_path(value: str, *, default: str) -> str:
          if not value:
              return default
          p = Path(value)
          if p.is_absolute() or ".." in p.parts:
              return default
          return value
      
      
      def _load_skill_config() -> dict[str, dict[str, str]]:
          skill_root = Path(__file__).resolve().parent.parent
          config_path = skill_root / "config.yaml"
          if not config_path.exists():
              return {}
      
          text = config_path.read_text(encoding="utf-8", errors="replace")
          try:
              import yaml  # type: ignore
          except Exception:
              return _parse_simple_yaml_sections(text, wanted_sections={"directories", "verification"})
      
          try:
              data = yaml.safe_load(text) or {}
          except Exception:
              return _parse_simple_yaml_sections(text, wanted_sections={"directories", "verification"})
      
          out: dict[str, dict[str, str]] = {}
          for section in ("directories", "verification"):
              v = data.get(section)
              if isinstance(v, dict):
                  out[section] = {str(k): str(vv) for k, vv in v.items() if isinstance(vv, (str, int, float))}
          return out
      
      
      _CFG = _load_skill_config()
      _CFG_DIRS = _CFG.get("directories") or {}
      _CFG_VER = _CFG.get("verification") or {}
      
      def _int_or(default: int, value: str | None) -> int:
          if value is None:
              return default
          try:
              return int(value)
          except Exception:
              return default
      
      
      # Defaults (overrideable by CLI flags)
      MIN_REPORT_LENGTH = _int_or(500, _CFG_VER.get("min_report_length"))
      MIN_ISSUE_COUNT = _int_or(10, _CFG_VER.get("min_issue_count"))
      PLANS_DIRNAME = _safe_rel_path(_CFG_DIRS.get("plans", ""), default="output/plans")
      TESTS_DIRNAME = _safe_rel_path(_CFG_DIRS.get("tests", ""), default="output/tests")
      
      def _read_text(path: Path) -> str:
          # Be tolerant to non-UTF8 files in real projects; verification should not crash.
          return path.read_text(encoding="utf-8", errors="replace")
      
      
      def check_required_files(session_dir: Path) -> Tuple[bool, List[str]]:
          """检查必需文件是否存在"""
          issues = []
          for file_name in REQUIRED_FILES:
              file_path = session_dir / file_name
              if not file_path.exists():
                  issues.append(f"缺少必需文件: {file_name}")
          return len(issues) == 0, issues
      
      
      def check_template_placeholders(session_dir: Path) -> Tuple[bool, List[str]]:
          """检查模板占位符是否被替换"""
          issues = []
          placeholder_pattern = re.compile(r'\{\{[^}]+\}\}')
      
          for file_name in REQUIRED_FILES:
              file_path = session_dir / file_name
              if file_path.exists():
                  content = _read_text(file_path)
                  placeholders = placeholder_pattern.findall(content)
      
                  if placeholders:
                      # 去重并限制显示数量
                      unique_placeholders = list(set(placeholders))[:5]
                      issues.append(
                          f"{file_name} 包含未替换的模板占位符: {', '.join(unique_placeholders)}"
                      )
      
          return len(issues) == 0, issues
      
      
      def check_report_content(session_dir: Path, *, min_report_length: int) -> Tuple[bool, List[str]]:
          """检查报告内容是否充实"""
          issues = []
          report_file = session_dir / "TEST_REPORT.md"
      
          if not report_file.exists():
              return True, issues  # 已在 check_required_files 中处理
      
          content = _read_text(report_file)
      
          # 检查是否包含未填写的占位文本
          placeholder_patterns = [
              r'(在此[处处]填写[^)]*)',
              r'(在此[处处]填入[^)]*)',
              r'(描述[^)]*)',
              r'(填入[^)]*)',
              r'(待填写[^)]*)',
              r'\[TODO[^\]]*\]',
              r'\[待[^\]]*\]',
              # Catch common variants even when punctuation is missing.
              r'待(?:补充|填写|添加)(?:[^。\n]{0,80})',
          ]
      
          for pattern in placeholder_patterns:
              matches = re.findall(pattern, content)
              if matches:
                  # 取前 3 个示例
                  examples = matches[:3]
                  issues.append(
                      f"TEST_REPORT.md 包含未填写的占位文本: {', '.join(examples)}"
                  )
                  break  # 找到一个类型就够了
      
          # 移除占位文本后计算实际内容长度
          cleaned_content = content
          for pattern in placeholder_patterns:
              cleaned_content = re.sub(pattern, '', cleaned_content, flags=re.DOTALL)
      
          actual_length = len(cleaned_content.strip())
      
          if actual_length < min_report_length:
              issues.append(
                  f"TEST_REPORT.md 内容过短({actual_length} 字符,要求 ≥ {min_report_length} 字符),"
                  "可能未填充实际内容"
              )
      
          return len(issues) == 0, issues
      
      
      def check_evidence_presence(session_dir: Path) -> Tuple[bool, List[str]]:
          """检查是否包含具体证据"""
          issues = []
          report_file = session_dir / "TEST_REPORT.md"
      
          if not report_file.exists():
              return True, issues  # 已在 check_required_files 中处理
      
          content = _read_text(report_file)
      
          # 检查证据类型的标记(命令输出、文件路径、对比结果等)
          evidence_patterns = [
              r'```[a-z]*\n',  # 代码块(命令输出)
              r'\[.*?\]\([^)]+\)',  # Markdown 链接(文件路径)
              # 文件引用(如 src/file.py:123 / auto-test-project/scripts/x.py:10 / ./path/to/a.md:3)
              r'(?<!\w)(?:\./)?[A-Za-z0-9_.-]+(?:/[A-Za-z0-9_.-]+)*\.[A-Za-z0-9_]+:\d+',
              # Windows 路径引用(如 C:\path\to\file.py:123)
              r'(?<!\w)[A-Za-z]:\\[^:\n]+:\d+',
              r'✅|❌|⚠️',  # 状态标记
              r'修复前|修复后|对比|验证',  # 对比关键词
          ]
      
          has_evidence = any(re.search(pattern, content) for pattern in evidence_patterns)
      
          if not has_evidence:
              issues.append(
                  "TEST_REPORT.md 缺少具体证据(命令输出、文件路径、对比结果、状态标记等)"
              )
      
          return len(issues) == 0, issues
      
      
      def check_plan_report_consistency(
          session_dir: Path,
          *,
          plans_dir: Path,
          min_issue_count: int,
          require_plan: bool,
      ) -> Tuple[bool, List[str]]:
          """
          检查计划与报告的一致性
      
          验证:
          - configured plans directory 中的每个问题是否在 TEST_REPORT.md 中有对应记录
          - 成功标准是否在报告中有验证结论
          """
          issues = []
      
          # 尝试找到对应的 plan 文件
          session_name = session_dir.name
          plan_file = plans_dir / f"{session_name}.md"
      
          if not plan_file.exists():
              if require_plan:
                  issues.append(f"缺少规划文档: {plan_file}")
                  return False, issues
              # 如果 plan 文件不存在且未强制要求,跳过一致性检查
              return True, issues
      
          report_file = session_dir / "TEST_REPORT.md"
          if not report_file.exists():
              return True, issues  # 已在 check_required_files 中处理
      
          plan_content = _read_text(plan_file)
          report_content = _read_text(report_file)
      
          # 提取计划中的问题编号(如 P0-1, P1-2)
          plan_issues = re.findall(r'#### ([Pp][012]-\d+):', plan_content)
      
          if not plan_issues:
              # 计划中没有可引用的问题编号时,一致性检查无法落地
              if require_plan:
                  issues.append("规划文档未包含形如 '#### P0-1:' 的问题编号,无法执行计划-报告一致性检查")
                  return False, issues
              return True, issues
      
          # 检查每个问题是否在报告中有记录
          missing_issues = []
          for issue_id in plan_issues:
              # 检查问题编号是否出现在报告中
              if issue_id not in report_content:
                  missing_issues.append(issue_id)
      
          if missing_issues:
              issues.append(
                  f"计划中的问题在测试报告中无对应记录: {', '.join(missing_issues[:5])}"
                  + (f" ... (共 {len(missing_issues)} 个)" if len(missing_issues) > 5 else "")
              )
      
          # 检查问题数量是否达到最低要求
          total_issues = len(plan_issues)
          if total_issues < min_issue_count:
              issues.append(
                  f"计划中的问题数量不足:发现 {total_issues} 个,要求 ≥ {min_issue_count} 个"
              )
      
          return len(issues) == 0, issues
      
      
      def verify_test_session(
          session_dir: Path,
          *,
          plans_dir: Path,
          min_report_length: int,
          min_issue_count: int,
          require_plan: bool,
      ) -> Tuple[bool, List[str]]:
          """验证测试会话目录的完整性"""
          all_issues = []
      
          # 检查 1: 必需文件
          passed, issues = check_required_files(session_dir)
          all_issues.extend(issues)
      
          # 检查 2: 模板占位符
          passed, issues = check_template_placeholders(session_dir)
          all_issues.extend(issues)
      
          # 检查 3: 报告内容长度
          passed, issues = check_report_content(session_dir, min_report_length=min_report_length)
          all_issues.extend(issues)
      
          # 检查 4: 证据存在性
          passed, issues = check_evidence_presence(session_dir)
          all_issues.extend(issues)
      
          # 检查 5: 计划与报告一致性
          passed, issues = check_plan_report_consistency(
              session_dir,
              plans_dir=plans_dir,
              min_issue_count=min_issue_count,
              require_plan=require_plan,
          )
          all_issues.extend(issues)
      
          return len(all_issues) == 0, all_issues
      
      
      def print_summary(session_dir: Path, is_valid: bool, issues: List[str]):
          """打印验证结果摘要"""
          if is_valid:
              print(f"✅ 验证通过: {session_dir}")
              print(f"   所有检查项均满足要求")
          else:
              print(f"❌ 验证失败: {session_dir}")
              print(f"   发现 {len(issues)} 个问题:")
              for issue in issues:
                  print(f"   - {issue}")
      
      
      def main() -> int:
          parser = argparse.ArgumentParser(description="Verify an auto-test-project test session directory.")
          parser.add_argument(
              "session_dir",
              help="Explicit test session directory inside an active task workspace or --legacy-root.",
          )
          parser.add_argument(
              "--project-root",
              default=".",
              help="Project root that owns .bensz-api (default: current directory).",
          )
          workspace_group = parser.add_mutually_exclusive_group()
          workspace_group.add_argument(
              "--task-root",
              default="",
              help="Active task root to verify; it is never created or renamed by this command.",
          )
          workspace_group.add_argument(
              "--legacy-root",
              default="",
              help=(
                  "Explicit read-only compatibility root. Only "
                  ".bensz-api/skills/auto-test-project is accepted."
              ),
          )
          parser.add_argument(
              "--require-plan",
              action="store_true",
              help="Fail if the task-local plans/<session_name>.md is missing or lacks issue ids like '#### P0-1:'.",
          )
          parser.add_argument(
              "--min-report-length",
              type=int,
              default=MIN_REPORT_LENGTH,
              help=f"Minimum TEST_REPORT.md length after cleaning placeholders (default: {MIN_REPORT_LENGTH}).",
          )
          parser.add_argument(
              "--min-issue-count",
              type=int,
              default=MIN_ISSUE_COUNT,
              help=f"Minimum issue ids in plan (default: {MIN_ISSUE_COUNT}).",
          )
          args = parser.parse_args()
      
          project_root = Path(args.project_root).expanduser().resolve()
          session_dir = Path(args.session_dir).expanduser().resolve()
          if not session_dir.exists():
              print(f"错误: 目录不存在: {session_dir}", file=sys.stderr)
              return 1
          if not session_dir.is_dir():
              print(f"错误: 不是目录: {session_dir}", file=sys.stderr)
              return 1
      
          try:
              if args.task_root:
                  workspace = resolve_workspace(
                      project_root=project_root,
                      task_root_arg=args.task_root,
                      task_description="",
                      directories=_CFG_DIRS,
                      create=False,
                  )
              elif args.legacy_root:
                  workspace = resolve_legacy_workspace(
                      project_root=project_root,
                      legacy_root_arg=args.legacy_root,
                      directories=_CFG_DIRS,
                  )
              else:
                  workspace = infer_active_workspace_from_session(
                      project_root=project_root,
                      session_dir=session_dir,
                      directories=_CFG_DIRS,
                  )
              if session_dir.parent != workspace.tests_dir:
                  raise ValueError(
                      f"session must be a direct child of configured tests directory: {workspace.tests_dir}"
                  )
          except (FileNotFoundError, ValueError) as exc:
              print(f"错误: {exc}", file=sys.stderr)
              return 2
      
          is_valid, issues = verify_test_session(
              session_dir,
              plans_dir=workspace.plans_dir,
              min_report_length=args.min_report_length,
              min_issue_count=args.min_issue_count,
              require_plan=args.require_plan,
          )
          print_summary(session_dir, is_valid, issues)
          return 0 if is_valid else 1
      
      
      if __name__ == "__main__":
          raise SystemExit(main())
      
    • workspace_paths.py 9.8 KB
      #!/usr/bin/env python3
      """Resolve auto-test-project task workspaces without guessing continuation state."""
      
      from __future__ import annotations
      
      import datetime as dt
      import re
      from dataclasses import dataclass
      from pathlib import Path
      from typing import Mapping
      
      
      SKILL_NAME = "auto-test-project"
      _TASK_NAME_RE = re.compile(r"^task-\d{8}-\d{4}-[^\s/]+$")
      
      
      @dataclass(frozen=True)
      class WorkspaceLayout:
          project_root: Path
          task_root: Path
          skill_root: Path
          plans_dir: Path
          tests_dir: Path
          created_task_root: bool = False
          legacy: bool = False
      
      
      def _safe_relative(value: str, *, default: str, label: str) -> Path:
          raw = value.strip() or default
          path = Path(raw)
          if path.is_absolute() or ".." in path.parts:
              raise ValueError(f"{label} must be a task-local relative path: {raw}")
          return path
      
      
      def _directory_paths(directories: Mapping[str, str]) -> tuple[Path, Path]:
          plans_rel = _safe_relative(
              directories.get("plans", ""), default="output/plans", label="directories.plans"
          )
          tests_rel = _safe_relative(
              directories.get("tests", ""), default="output/tests", label="directories.tests"
          )
          return plans_rel, tests_rel
      
      
      def _require_directory(path: Path, *, label: str) -> None:
          if not path.exists():
              raise FileNotFoundError(f"{label} does not exist: {path}")
          if path.is_symlink() or not path.is_dir():
              raise ValueError(f"{label} must be a real directory, not a file or symlink: {path}")
      
      
      def _require_direct_child(path: Path, *, parent: Path, label: str) -> None:
          if path.parent != parent:
              raise ValueError(f"{label} must be a direct child of {parent}: {path}")
      
      
      def _reject_existing_symlinks(base: Path, path: Path, *, label: str) -> None:
          try:
              relative = path.relative_to(base)
          except ValueError as exc:
              raise ValueError(f"{label} must stay inside project root: {path}") from exc
      
          current = base
          for part in relative.parts:
              current = current / part
              if current.exists() and current.is_symlink():
                  raise ValueError(f"{label} must not traverse a symlink: {current}")
      
      
      def _resolve_task_input(project_root: Path, raw: str) -> Path:
          source = Path(raw).expanduser()
          if ".." in source.parts:
              raise ValueError("--task-root must not contain '..'")
          candidate = source if source.is_absolute() else project_root / source
          if candidate.exists() and candidate.is_symlink():
              raise ValueError(f"task root must not be a symlink: {candidate}")
          candidate = candidate.resolve()
          _reject_existing_symlinks(project_root, candidate, label="task root")
          return candidate
      
      
      def _slugify_description(value: str) -> str:
          slug = re.sub(r"[^\w-]+", "-", value.strip(), flags=re.UNICODE).strip("-_")
          return slug or SKILL_NAME
      
      
      def _task_readme(task_root: Path) -> str:
          return (
              "# auto-test-project Task Workspace\n\n"
              "This task root was allocated by `create_test_session.py`. "
              "Pass it back with `--task-root` for A/B rounds and continuations.\n"
          )
      
      
      def _allocate_task_root(project_root: Path, *, description: str, now: dt.datetime | None) -> Path:
          bensz_root = project_root / ".bensz-api"
          if bensz_root.exists() and (bensz_root.is_symlink() or not bensz_root.is_dir()):
              raise ValueError(f".bensz-api must be a real directory: {bensz_root}")
          bensz_root.mkdir(parents=True, exist_ok=True)
      
          current = now or dt.datetime.now()
          base_name = f"task-{current:%Y%m%d-%H%M}-{_slugify_description(description)}"
          suffixes = [""] + [f"-{chr(code)}" for code in range(ord("a"), ord("z") + 1)]
          for suffix in suffixes:
              candidate = bensz_root / f"{base_name}{suffix}"
              try:
                  candidate.mkdir()
              except FileExistsError:
                  continue
              return candidate.resolve()
          raise FileExistsError(f"Unable to allocate a unique task root for {base_name}")
      
      
      def _explicit_task_root(project_root: Path, *, raw: str, create: bool) -> tuple[Path, bool]:
          bensz_root = project_root / ".bensz-api"
          if bensz_root.exists() and (bensz_root.is_symlink() or not bensz_root.is_dir()):
              raise ValueError(f".bensz-api must be a real directory: {bensz_root}")
      
          candidate = _resolve_task_input(project_root, raw)
          expected_parent = bensz_root.resolve()
          _require_direct_child(candidate, parent=expected_parent, label="task root")
          if not _TASK_NAME_RE.fullmatch(candidate.name):
              raise ValueError(
                  "--task-root directory name must match task-YYYYMMDD-HHMM-<description>"
              )
      
          created = False
          if candidate.exists():
              _require_directory(candidate, label="task root")
          elif create:
              bensz_root.mkdir(parents=True, exist_ok=True)
              candidate.mkdir()
              created = True
          else:
              raise FileNotFoundError(f"task root does not exist: {candidate}")
          return candidate, created
      
      
      def _layout_from_skill_root(
          *,
          project_root: Path,
          task_root: Path,
          skill_root: Path,
          directories: Mapping[str, str],
          create: bool,
          created_task_root: bool = False,
          legacy: bool = False,
      ) -> WorkspaceLayout:
          plans_rel, tests_rel = _directory_paths(directories)
          plans_dir = skill_root / plans_rel
          tests_dir = skill_root / tests_rel
      
          if create:
              for path in (skill_root / "input", skill_root / "output", skill_root / "log", plans_dir, tests_dir):
                  _reject_existing_symlinks(task_root, path, label="skill workspace path")
                  if path.exists() and (path.is_symlink() or not path.is_dir()):
                      raise ValueError(f"skill workspace path must be a real directory: {path}")
                  path.mkdir(parents=True, exist_ok=True)
          else:
              _require_directory(skill_root, label="skill workspace")
              _require_directory(plans_dir, label="plans directory")
              _require_directory(tests_dir, label="tests directory")
      
          return WorkspaceLayout(
              project_root=project_root,
              task_root=task_root,
              skill_root=skill_root,
              plans_dir=plans_dir,
              tests_dir=tests_dir,
              created_task_root=created_task_root,
              legacy=legacy,
          )
      
      
      def resolve_workspace(
          *,
          project_root: Path,
          task_root_arg: str,
          task_description: str,
          directories: Mapping[str, str],
          create: bool,
          now: dt.datetime | None = None,
      ) -> WorkspaceLayout:
          """Resolve or allocate an active task workspace."""
          project_root = project_root.expanduser().resolve()
          _require_directory(project_root, label="project root")
          _directory_paths(directories)
      
          if task_root_arg.strip():
              task_root, created = _explicit_task_root(
                  project_root, raw=task_root_arg.strip(), create=create
              )
          elif create:
              task_root = _allocate_task_root(
                  project_root, description=task_description, now=now
              )
              created = True
          else:
              raise ValueError("--task-root is required when verifying an active task workspace")
      
          if created:
              readme = task_root / "README.md"
              readme.write_text(_task_readme(task_root), encoding="utf-8")
      
          return _layout_from_skill_root(
              project_root=project_root,
              task_root=task_root,
              skill_root=task_root / SKILL_NAME,
              directories=directories,
              create=create,
              created_task_root=created,
          )
      
      
      def resolve_legacy_workspace(
          *, project_root: Path, legacy_root_arg: str, directories: Mapping[str, str]
      ) -> WorkspaceLayout:
          """Resolve the one supported legacy root for read-only verification."""
          project_root = project_root.expanduser().resolve()
          _require_directory(project_root, label="project root")
          source = Path(legacy_root_arg).expanduser()
          if ".." in source.parts:
              raise ValueError("--legacy-root must not contain '..'")
          candidate = source if source.is_absolute() else project_root / source
          if candidate.exists() and candidate.is_symlink():
              raise ValueError(f"legacy root must not be a symlink: {candidate}")
          candidate = candidate.resolve()
          _reject_existing_symlinks(project_root, candidate, label="legacy root")
          expected = (project_root / ".bensz-api" / "skills" / SKILL_NAME).resolve()
          if candidate != expected:
              raise ValueError(f"--legacy-root must resolve to {expected}")
          _require_directory(candidate, label="legacy root")
          return _layout_from_skill_root(
              project_root=project_root,
              task_root=candidate.parent.parent,
              skill_root=candidate,
              directories=directories,
              create=False,
              legacy=True,
          )
      
      
      def infer_active_workspace_from_session(
          *, project_root: Path, session_dir: Path, directories: Mapping[str, str]
      ) -> WorkspaceLayout:
          """Infer only a task-* workspace from an explicit session path."""
          project_root = project_root.expanduser().resolve()
          session_dir = session_dir.expanduser().resolve()
          tests_rel = _safe_relative(
              directories.get("tests", ""), default="output/tests", label="directories.tests"
          )
          try:
              skill_root = session_dir.parents[len(tests_rel.parts)]
          except IndexError as exc:
              raise ValueError(f"session path is too shallow: {session_dir}") from exc
          task_root = skill_root.parent
          expected_parent = (project_root / ".bensz-api").resolve()
          _require_direct_child(task_root, parent=expected_parent, label="task root")
          if not _TASK_NAME_RE.fullmatch(task_root.name) or skill_root.name != SKILL_NAME:
              raise ValueError("session is not inside an active auto-test-project task workspace")
          layout = _layout_from_skill_root(
              project_root=project_root,
              task_root=task_root,
              skill_root=skill_root,
              directories=directories,
              create=False,
          )
          try:
              session_dir.relative_to(layout.tests_dir)
          except ValueError as exc:
              raise ValueError(f"session is outside configured tests directory: {session_dir}") from exc
          return layout
      
  • templates
    • BUG_REPORT_TEMPLATE.md 1.7 KB
      # 项目级 Bug 报告
      
      **报告ID**: {{REPORT_ID}}
      **项目根目录**: {{PROJECT_ROOT}}
      **报告时间**: {{REPORT_TIME}}
      **A轮测试ID**: {{A_ROUND_ID}}
      
      ---
      
      ## 问题汇总
      
      | ID | 问题描述 | 位置 | 严重程度 | 状态 |
      |----|---------|------|----------|------|
      | {{BUG_1_ID}} | {{BUG_1_DESC}} | {{BUG_1_LOC}} | P0/P1/P2/P3 | {{BUG_1_STATUS}} |
      | {{BUG_2_ID}} | {{BUG_2_DESC}} | {{BUG_2_LOC}} | P0/P1/P2/P3 | {{BUG_2_STATUS}} |
      
      ---
      
      ## 问题详情
      
      ### 问题 1: {{BUG_1_TITLE}}
      
      **ID**: {{BUG_1_ID}}
      **严重程度**: P0/P1/P2/P3
      **状态**: 待修复 / 修复中 / 已验证 / 已关闭
      
      **描述**:
      {{BUG_1_DESCRIPTION}}
      
      **位置**:
      - 文件: {{BUG_1_FILE}}
      - 行号: {{BUG_1_LINE}}
      - 模块: {{BUG_1_MODULE}}
      
      **复现步骤**:
      1. {{STEP_1}}
      2. {{STEP_2}}
      3. {{STEP_3}}
      
      **期望行为**:
      {{BUG_1_EXPECTED}}
      
      **实际行为**:
      {{BUG_1_ACTUAL}}
      
      **影响范围**:
      - {{IMPACT_MODULE_1}}
      - {{IMPACT_MODULE_2}}
      
      **修复建议**:
      {{BUG_1_SUGGESTION}}
      
      **验证方法**:
      {{BUG_1_VERIFICATION}}
      
      ---
      
      ### 问题 2: {{BUG_2_TITLE}}
      
      **ID**: {{BUG_2_ID}}
      **严重程度**: P0/P1/P2/P3
      **状态**: 待修复 / 修复中 / 已验证 / 已关闭
      
      **描述**:
      {{BUG_2_DESCRIPTION}}
      
      **位置**:
      - 文件: {{BUG_2_FILE}}
      - 行号: {{BUG_2_LINE}}
      - 模块: {{BUG_2_MODULE}}
      
      **复现步骤**:
      1. {{STEP_1}}
      2. {{STEP_2}}
      3. {{STEP_3}}
      
      **期望行为**:
      {{BUG_2_EXPECTED}}
      
      **实际行为**:
      {{BUG_2_ACTUAL}}
      
      **影响范围**:
      - {{IMPACT_MODULE_1}}
      - {{IMPACT_MODULE_2}}
      
      **修复建议**:
      {{BUG_2_SUGGESTION}}
      
      **验证方法**:
      {{BUG_2_VERIFICATION}}
      
      ---
      
      ## 优先级修复建议
      
      ### P0(立即修复)
      - {{P0_ITEM_1}}
      
      ### P1(24小时内)
      - {{P1_ITEM_1}}
      
      ### P2(3天内)
      - {{P2_ITEM_1}}
      
    • B_ROUND_CHECK_TEMPLATE.md 2.5 KB
      # B轮质量检查报告({{SESSION_NAME}})
      
      **检查ID**: {{SESSION_NAME}}  
      **检查时间**: {{PLAN_TIME}}  
      **目标项目**: {{PROJECT_NAME}}  
      **项目根目录**: {{PROJECT_ROOT}}  
      **项目类型**: {{PROJECT_TYPE}}  
      **对应A轮测试**: {{A_TEST_ID}}(推荐创建 B 轮时显式传入 `--a-test-id`;如不正确请手动修正)  
      
      ---
      
      ## 检查结果总览(质量原则;以 config.yaml:b_round_check.dimensions 为准)
      
      | 维度 | 状态 | 关键发现(一句话) |
      |------|------|--------------------|
      | 硬编码/AI功能规划 | ✅ / ⚠️ / ❌ | {{NOTE_1}} |
      | 冗余残留错误检查 | ✅ / ⚠️ / ❌ | {{NOTE_2}} |
      | 安全性检查 | ✅ / ⚠️ / ❌ | {{NOTE_3}} |
      | 过度设计检查 | ✅ / ⚠️ / ❌ | {{NOTE_4}} |
      | 通用性检查 | ✅ / ⚠️ / ❌ | {{NOTE_5}} |
      | 一致性检查 | ✅ / ⚠️ / ❌ | {{NOTE_6}} |
      | 项目指令文件瘦身检查 | ✅ / ⚠️ / ❌ | {{NOTE_7}} |
      | 配置集中化检查 | ✅ / ⚠️ / ❌ | {{NOTE_8}} |
      
      ---
      
      ## 问题与建议清单(P0-P2)
      
      > 说明:
      > - 为支持验证脚本的“计划-报告一致性检查”,请使用可引用编号(如 `P0-1`),并确保 B 轮验证会话的 TEST_REPORT 中出现相同编号。
      > - 每条建议需注明所属维度(建议使用 `config.yaml:b_round_check.dimensions` 的 name)。
      
      ### P0(必须修复)
      
      #### P0-1: {{P0_1_TITLE}}
      
      **维度**: {{P0_1_DIMENSION}}
      
      **位置/范围**: {{P0_1_LOCATION}}
      
      **影响**:
      {{P0_1_IMPACT}}
      
      **修复建议**:
      {{P0_1_FIX}}
      
      **验证方法**:
      {{P0_1_VERIFY}}
      
      ---
      
      #### P0-2: {{P0_2_TITLE}}
      
      **维度**: {{P0_2_DIMENSION}}
      
      **位置/范围**: {{P0_2_LOCATION}}
      
      **影响**:
      {{P0_2_IMPACT}}
      
      **修复建议**:
      {{P0_2_FIX}}
      
      **验证方法**:
      {{P0_2_VERIFY}}
      
      ---
      
      ### P1(应修复或给出明确不修复理由)
      
      #### P1-1: {{P1_1_TITLE}}
      
      **维度**: {{P1_1_DIMENSION}}
      
      **位置/范围**: {{P1_1_LOCATION}}
      
      **影响**:
      {{P1_1_IMPACT}}
      
      **修复建议**:
      {{P1_1_FIX}}
      
      **验证方法**:
      {{P1_1_VERIFY}}
      
      ---
      
      ### P2(可延后,但建议记录原因)
      
      #### P2-1: {{P2_1_TITLE}}
      
      **维度**: {{P2_1_DIMENSION}}
      
      **位置/范围**: {{P2_1_LOCATION}}
      
      **影响**:
      {{P2_1_IMPACT}}
      
      **修复建议**:
      {{P2_1_FIX}}
      
      **验证方法**:
      {{P2_1_VERIFY}}
      
      ---
      
      ## 验收门槛(默认口径以 config.yaml 为准)
      
      - 建议数量:`config.yaml:b_round_check.min_suggestions` / `config.yaml:b_round_check.target_suggestions_range`
      - 修复率门槛:`config.yaml:b_round_check.p0_fix_rate_required` / `config.yaml:b_round_check.p1_fix_rate_required`
      
    • FINAL_SUMMARY_TEMPLATE.md 3.2 KB
      # 项目级优化总结报告
      
      **报告时间**: {{REPORT_TIME}}
      **项目根目录**: {{PROJECT_ROOT}}
      **项目名称**: {{PROJECT_NAME}}
      
      ---
      
      ## 执行摘要
      
      本次项目级优化共完成 **{{A_ROUNDS}}** 轮 A 轮测试和 **{{B_ROUNDS}}** 轮 B 轮质量检查。
      
      **关键成果**:
      - {{KEY_ACHIEVEMENT_1}}
      - {{KEY_ACHIEVEMENT_2}}
      
      ---
      
      ## A轮测试汇总
      
      ### A轮迭代历程
      
      | 轮次 | 测试ID | 主要问题 | 修复状态 |
      |------|--------|---------|---------|
      | 1 | v{{TIMESTAMP_1}} | {{MAIN_ISSUES_1}} | {{STATUS_1}} |
      | 2 | v{{TIMESTAMP_2}} | {{MAIN_ISSUES_2}} | {{STATUS_2}} |
      | 3 | v{{TIMESTAMP_3}} | {{MAIN_ISSUES_3}} | {{STATUS_3}} |
      
      ### 问题解决统计
      
      | 严重程度 | 初始数量 | 已修复 | 进行中 | 待处理 |
      |---------|---------|--------|--------|--------|
      | P0 | {{P0_INITIAL}} | {{P0_FIXED}} | {{P0_IN_PROGRESS}} | {{P0_PENDING}} |
      | P1 | {{P1_INITIAL}} | {{P1_FIXED}} | {{P1_IN_PROGRESS}} | {{P1_PENDING}} |
      | P2 | {{P2_INITIAL}} | {{P2_FIXED}} | {{P2_IN_PROGRESS}} | {{P2_PENDING}} |
      
      ---
      
      ## B轮质量检查汇总
      
      ### B轮检查结果
      
      | 维度 | 状态 | 主要发现 |
      |------|------|---------|
      | 硬编码/AI功能规划 | ✅ / ⚠️ / ❌ | {{FINDING_1}} |
      | 冗余残留错误检查 | ✅ / ⚠️ / ❌ | {{FINDING_2}} |
      | 安全性检查 | ✅ / ⚠️ / ❌ | {{FINDING_3}} |
      | 过度设计检查 | ✅ / ⚠️ / ❌ | {{FINDING_4}} |
      | 通用性检查 | ✅ / ⚠️ / ❌ | {{FINDING_5}} |
      | 一致性检查 | ✅ / ⚠️ / ❌ | {{FINDING_6}} |
      
      ---
      
      ## 跨模块改进
      
      ### 模块间优化
      
      | 模块组合 | 优化内容 | 效果 |
      |---------|---------|------|
      | {{MODULE_PAIR_1}} | {{OPTIMIZATION_1}} | {{EFFECT_1}} |
      | {{MODULE_PAIR_2}} | {{OPTIMIZATION_2}} | {{EFFECT_2}} |
      
      ### 架构级改进
      
      - {{ARCHITECTURE_IMPROVEMENT_1}}
      - {{ARCHITECTURE_IMPROVEMENT_2}}
      
      ---
      
      ## 质量指标变化
      
      | 指标 | 优化前 | 优化后 | 变化 |
      |------|--------|--------|------|
      | 测试覆盖率 | {{COVERAGE_BEFORE}}% | {{COVERAGE_AFTER}}% | {{COVERAGE_CHANGE}} |
      | P0问题数 | {{P0_BEFORE}} | {{P0_AFTER}} | {{P0_CHANGE}} |
      | P1问题数 | {{P1_BEFORE}} | {{P1_AFTER}} | {{P1_CHANGE}} |
      | 代码重复率 | {{DUP_BEFORE}}% | {{DUP_AFTER}}% | {{DUP_CHANGE}} |
      
      ---
      
      ## 文档更新
      
      ### 项目级文档
      
      - [x] `CHANGELOG.md` 已更新
      - [x] `CLAUDE.md` 已同步(如适用)
      - [x] `README.md` 已更新(如适用)
      
      ### 模块级文档
      
      - [x] {{MODULE_1}} 文档已更新
      - [x] {{MODULE_2}} 文档已更新
      
      ---
      
      ## 遗留问题与后续计划
      
      ### 遗留问题
      
      | ID | 问题描述 | 严重程度 | 建议处理时间 |
      |----|---------|----------|-------------|
      | {{PENDING_1_ID}} | {{PENDING_1_DESC}} | P1/P2/P3 | {{PENDING_1_TIME}} |
      | {{PENDING_2_ID}} | {{PENDING_2_DESC}} | P1/P2/P3 | {{PENDING_2_TIME}} |
      
      ### 后续优化建议
      
      - {{FUTURE_OPTIMIZATION_1}}
      - {{FUTURE_OPTIMIZATION_2}}
      
      ---
      
      ## 测试会话归档
      
      所有测试会话已保存在 `tests/` 目录:
      - {{TEST_SESSION_1}}
      - {{TEST_SESSION_2}}
      - {{TEST_SESSION_3}}
      
      所有规划文档已保存在 `plans/` 目录:
      - {{PLAN_1}}
      - {{PLAN_2}}
      - {{PLAN_3}}
      
      ---
      
      ## 经验总结
      
      ### 成功经验
      
      - {{SUCCESS_1}}
      - {{SUCCESS_2}}
      
      ### 改进空间
      
      - {{IMPROVEMENT_AREA_1}}
      - {{IMPROVEMENT_AREA_2}}
      
      ---
      
      **报告生成**: {{GENERATION_TIME}}
      **报告版本**: {{REPORT_VERSION}}
      
    • OPTIMIZATION_PLAN_TEMPLATE.md 5 KB
      # 项目级优化计划({{SESSION_NAME}})
      
      **计划ID**: {{PLAN_ID}}
      **项目根目录**: {{PROJECT_ROOT}}
      **计划时间**: {{PLAN_TIME}}
      **关联测试会话**: {{SESSION_NAME}}
      
      ---
      
      ## 优化目标
      
      {{OPTIMIZATION_GOAL}}
      
      ---
      
      ## 系统视角与批判性分析(⭐️ 核心章节)
      
      **⚠️ 重要说明**:本项目级测试的核心价值在于**系统视角和批判性思维**,而非替代 linter 进行表面检查。
      
      **独立评估提示(默认)**:本轮建议先不查看历史 `plans/` 与 `tests/`,只基于当前项目状态重新审视;如用户明确要求“沿上轮跟进”,可在计划中说明例外。
      
      ### 本轮使用的批判性分析框架
      
      **请明确本轮使用的批判性分析技巧**(从 `references/PROJECT_ISSUE_DISCOVERY_TECHNIQUES.md` 技巧 0 选择):
      
      - [ ] **技巧 0.1:第一性原理思考**(评估项目是否偏离核心目标)
      - [ ] **技巧 0.2:架构合理性质疑**(评估模块划分、依赖方向、抽象层次)
      - [ ] **技巧 0.3:价值导向的问题分类**(避免噪音级问题,聚焦痛点级/隐患级)
      - [ ] **技巧 0.4:根本原因分析(5 Whys)**(挖掘问题本质,而非修复表象)
      
      **本轮主要使用**:{{CRITICAL_FRAMEWORK_USED}}(例:"技巧 0.1 + 0.2")
      
      ### 本轮核心目标对齐度分析
      
      **项目核心目标**(从 CLAUDE.md/AGENTS.md 提取):
      
      {{PROJECT_CORE_GOAL}}
      
      **本轮优化目标与核心目标的关系**:
      
      {{ALIGNMENT_WITH_CORE_GOAL}}
      
      ### 本轮问题类型分布预期
      
      | 问题类型 | 预期数量 | 占比 | 说明 |
      |---------|---------|------|------|
      | **痛点级**(用户核心功能受阻) | {{PAIN_POINT_COUNT}} | ~20% | 不修复就无法使用的问题 |
      | **隐患级**(未来风险) | {{HIDDEN_RISK_COUNT}} | ~50% | 技术债务、架构问题 |
      | **噪音级**(表面问题) | {{NOISE_COUNT}} | ~30% | 可选优化,避免过多 |
      
      **⚠️ 质量门槛**:
      - 每轮至少有 **1-2 个痛点级问题**(架构级/设计级),否则说明分析深度不足
      - 每轮至少有 **3 个系统性问题**(架构/过度设计/一致/安全等;项目级优先关注跨模块矛盾)
      - **P0 + P1 占比必须 ≥ 60%**(确保问题有价值;如达不到需说明理由)
      
      ---
      
      ## 问题清单(P0-P2)
      
      > 说明:
      > 1. 为支持"计划-执行一致性检查",请使用 `P0-1 / P1-2 / P2-3` 这种可引用的编号,并确保测试报告中出现同样的编号。
      > 2. **每个 P0/P1 问题必须包含批判性思维字段**(批判性质疑、根本原因、价值判断)
      
      ### P0(阻塞/安全/核心)
      
      #### P0-1: {{P0_1_TITLE}}
      
      **位置**: `{{P0_1_LOCATION}}`
      
      **涉及模块**: {{P0_1_MODULES}}
      
      **影响**:
      {{P0_1_IMPACT}}
      
      **批判性质疑**(⭐️ 必填):
      {{P0_1_CRITICAL_QUESTIONING}}
      
      **根本原因**(5 Whys 分析):
      {{P0_1_ROOT_CAUSE}}
      
      **修复建议**(针对根本原因,而非表象):
      {{P0_1_FIX}}
      
      **价值判断**:为什么这是 P0?修复它的价值是什么?
      {{P0_1_VALUE_JUDGEMENT}}
      
      **验证方法**:
      {{P0_1_VERIFY}}
      
      ---
      
      #### P0-2: {{P0_2_TITLE}}
      
      **位置**: `{{P0_2_LOCATION}}`
      
      **涉及模块**: {{P0_2_MODULES}}
      
      **影响**:
      {{P0_2_IMPACT}}
      
      **批判性质疑**(⭐️ 必填):
      {{P0_2_CRITICAL_QUESTIONING}}
      
      **根本原因**(5 Whys 分析):
      {{P0_2_ROOT_CAUSE}}
      
      **修复建议**(针对根本原因,而非表象):
      {{P0_2_FIX}}
      
      **价值判断**:为什么这是 P0?修复它的价值是什么?
      {{P0_2_VALUE_JUDGEMENT}}
      
      **验证方法**:
      {{P0_2_VERIFY}}
      
      ---
      
      ### P1(重要优化)
      
      #### P1-1: {{P1_1_TITLE}}
      
      **位置**: `{{P1_1_LOCATION}}`
      
      **涉及模块**: {{P1_1_MODULES}}
      
      **影响**:
      {{P1_1_IMPACT}}
      
      **批判性质疑**(⭐️ 必填):
      {{P1_1_CRITICAL_QUESTIONING}}
      
      **根本原因**(5 Whys 分析):
      {{P1_1_ROOT_CAUSE}}
      
      **修复建议**(针对根本原因,而非表象):
      {{P1_1_FIX}}
      
      **价值判断**:为什么这是 P1?修复它的价值是什么?
      {{P1_1_VALUE_JUDGEMENT}}
      
      **验证方法**:
      {{P1_1_VERIFY}}
      
      ---
      
      #### P1-2: {{P1_2_TITLE}}
      
      **位置**: `{{P1_2_LOCATION}}`
      
      **涉及模块**: {{P1_2_MODULES}}
      
      **影响**:
      {{P1_2_IMPACT}}
      
      **批判性质疑**(⭐️ 必填):
      {{P1_2_CRITICAL_QUESTIONING}}
      
      **根本原因**(5 Whys 分析):
      {{P1_2_ROOT_CAUSE}}
      
      **修复建议**(针对根本原因,而非表象):
      {{P1_2_FIX}}
      
      **价值判断**:为什么这是 P1?修复它的价值是什么?
      {{P1_2_VALUE_JUDGEMENT}}
      
      **验证方法**:
      {{P1_2_VERIFY}}
      
      ---
      
      ### P2(锦上添花)
      
      #### P2-1: {{P2_1_TITLE}}
      
      **位置**: `{{P2_1_LOCATION}}`
      
      **涉及模块**: {{P2_1_MODULES}}
      
      **影响**:
      {{P2_1_IMPACT}}
      
      **修复建议**:
      {{P2_1_FIX}}
      
      **验证方法**:
      {{P2_1_VERIFY}}
      
      ---
      
      #### P2-2: {{P2_2_TITLE}}
      
      **位置**: `{{P2_2_LOCATION}}`
      
      **涉及模块**: {{P2_2_MODULES}}
      
      **影响**:
      {{P2_2_IMPACT}}
      
      **修复建议**:
      {{P2_2_FIX}}
      
      **验证方法**:
      {{P2_2_VERIFY}}
      
      ---
      
      ## 轻量测试计划(本轮)
      
      ### 验证点
      
      - {{VERIFY_POINT_1}}
      - {{VERIFY_POINT_2}}
      - {{VERIFY_POINT_3}}
      
      ### 通过标准
      
      - [ ] {{CRITERION_1}}
      - [ ] {{CRITERION_2}}
      - [ ] {{CRITERION_3}}
      
    • PROJECT_TYPE_ANALYSIS_TEMPLATE.md 1.2 KB
      # 项目类型分析报告
      
      **分析时间**: {{ANALYSIS_TIME}}
      **项目根目录**: {{PROJECT_ROOT}}
      
      ---
      
      ## 项目基本信息
      
      **项目名称**: {{PROJECT_NAME}}
      **项目路径**: {{PROJECT_PATH}}
      
      ---
      
      ## 项目类型识别
      
      ### 识别结果
      
      **项目类型**: {{PROJECT_TYPE}}
      
      **识别依据**:
      - {{EVIDENCE_1}}
      - {{EVIDENCE_2}}
      
      ---
      
      ## 项目结构分析
      
      ### 核心模块
      
      | 模块/目录 | 功能 | 优先级 |
      |----------|------|--------|
      | {{MODULE_1}} | {{FUNCTION_1}} | {{PRIORITY_1}} |
      | {{MODULE_2}} | {{FUNCTION_2}} | {{PRIORITY_2}} |
      
      ### 配置文件
      
      - {{CONFIG_FILE_1}}: {{CONFIG_DESCRIPTION_1}}
      - {{CONFIG_FILE_2}}: {{CONFIG_DESCRIPTION_2}}
      
      ### 指令文件
      
      - {{INSTRUCTION_FILE}}: {{INSTRUCTION_DESCRIPTION}}
      
      ---
      
      ## 测试边界建议
      
      ### 核心测试路径
      
      - {{CORE_PATH_1}}
      - {{CORE_PATH_2}}
      
      ### 集成测试需求
      
      | 模块组合 | 测试重点 |
      |---------|---------|
      | {{MODULE_PAIR_1}} | {{TEST_FOCUS_1}} |
      | {{MODULE_PAIR_2}} | {{TEST_FOCUS_2}} |
      
      ### 排除的测试路径
      
      - {{EXCLUDE_PATH_1}}
      - {{EXCLUDE_PATH_2}}
      
      ---
      
      ## 依赖关系分析
      
      ### 模块间依赖
      
      ```
      {{DEPENDENCY_GRAPH}}
      ```
      
      ### 外部依赖
      
      - {{EXTERNAL_DEPENDENCY_1}}
      - {{EXTERNAL_DEPENDENCY_2}}
      
    • TEST_PLAN_TEMPLATE.md 1.2 KB
      # 测试计划({{ROUND_KIND}})
      
      **测试ID**: {{TEST_ID}}
      **项目根目录**: {{PROJECT_ROOT}}
      **项目类型**: {{PROJECT_TYPE}}
      **测试时间**: {{TEST_TIME}}
      **对应计划**: {{PLAN_DOC_PATH}}
      
      ---
      
      ## 测试目标
      
      {{TEST_OBJECTIVE}}
      
      ---
      
      ## 测试范围
      
      ### 核心模块
      
      - {{MODULE_1}}: {{SCOPE_1}}
      - {{MODULE_2}}: {{SCOPE_2}}
      
      ### 测试边界
      
      - **包含**: {{INCLUDE_SCOPE}}
      - **排除**: {{EXCLUDE_SCOPE}}
      
      ---
      
      ## 验证点
      
      ### 验证点 1: {{VERIFICATION_POINT_1}}
      
      **描述**: {{DESCRIPTION_1}}
      
      **验证方法**:
      - {{METHOD_1}}
      
      **预期结果**: {{EXPECTED_RESULT_1}}
      
      ---
      
      ### 验证点 2: {{VERIFICATION_POINT_2}}
      
      **描述**: {{DESCRIPTION_2}}
      
      **验证方法**:
      - {{METHOD_2}}
      
      **预期结果**: {{EXPECTED_RESULT_2}}
      
      ---
      
      ## 测试步骤
      
      1. {{STEP_1}}
      2. {{STEP_2}}
      3. {{STEP_3}}
      
      ---
      
      ## 通过标准
      
      - [ ] {{CRITERION_1}}
      - [ ] {{CRITERION_2}}
      - [ ] {{CRITERION_3}}
      
      ---
      
      ## 风险与注意事项
      
      - {{RISK_1}}
      - {{RISK_2}}
      
      ---
      
      完成后建议运行验证脚本检查会话质量:
      
      ```bash
      python3 auto-test-project/scripts/verify_test_session.py \
        --project-root "{{PROJECT_ROOT}}" \
        --task-root "{{TASK_ROOT}}" \
        --require-plan \
        "{{SKILL_WORKSPACE}}/output/tests/{{SESSION_NAME}}"
      ```
      
    • TEST_REPORT_TEMPLATE.md 3.1 KB
      # 测试报告({{ROUND_KIND}}测试)
      
      **测试会话**: {{SESSION_NAME}}
      **项目根目录**: {{PROJECT_ROOT}}
      **测试时间**: {{TEST_TIME}}
      **关联规划文档**: {{PLAN_DOC_PATH}}
      
      ---
      
      ## 执行摘要
      
      **状态**: (在此填写:✅ 通过 / ❌ 失败 / ⚠️ 部分通过)
      
      **简要说明**: (在此填写本轮测试的总体结论,不超过 3 句话)
      
      ---
      
      ## 验证点执行情况
      
      ### 验证点 1: (验证点名称)
      
      **状态**: ✅ 通过 / ❌ 失败
      
      **执行过程**:
      ```bash
      # 在此填入实际执行的命令
      ```
      
      **输出结果**:
      ```
      # 在此填入命令的实际输出
      ```
      
      **结论**: (在此填入验证结论)
      
      ---
      
      ## 问题修复记录
      
      ### P0-1: (问题标题)
      
      **位置**: (文件路径:行号)
      
      **修复前**: (描述修复前的状态)
      
      **修复措施**: (描述具体做了什么)
      
      **修复后**: (描述修复后的状态)
      
      **验证方法**:
      ```bash
      # 在此填入验证命令
      ```
      
      **验证结果**: (验证是否通过)
      
      ---
      
      ### P0-2: (问题标题)
      
      **位置**: (文件路径:行号)
      
      **修复前**: (描述修复前的状态)
      
      **修复措施**: (描述具体做了什么)
      
      **修复后**: (描述修复后的状态)
      
      **验证方法**:
      ```bash
      # 在此填入验证命令
      ```
      
      **验证结果**: (验证是否通过)
      
      ---
      
      ### P1-1: (问题标题)
      
      (按照 P0 的格式记录)
      
      ---
      
      ## 问题修复统计
      
      | 优先级 | 计划修复 | 实际修复 | 修复率 |
      |--------|----------|----------|--------|
      | P0 | X | Y | Z% |
      | P1 | X | Y | Z% |
      | P2 | X | Y | Z% |
      | **总计** | X | Y | Z% |
      
      ---
      
      ## 遗留问题
      
      - **P0-1**: (问题描述) - 原因:(未修复原因)
      - **P1-2**: (问题描述) - 原因:(未修复原因)
      - **P2-3**: (问题描述) - 原因:(未修复原因)
      
      ---
      
      ## 证据文件
      
      - [测试日志](_artifacts/test.log)
      - [截图对比](_artifacts/before_after.png)
      - [性能数据](_artifacts/performance.csv)
      
      ---
      
      ## 下一步建议
      
      **是否需要下一轮**: 是 / 否
      
      **如果需要,重点是什么**:
      1. (重点 1)
      2. (重点 2)
      3. (重点 3)
      
      ---
      
      **⚠️ 重要提醒**:
      
      1. 本报告必须完全替换上述占位文本,不得保留任何「在此处填写」或「(...)」的内容
      2. 每个问题修复必须包含:位置、修复前、修复措施、修复后、验证方法、验证结果
      3. 证据必须可复现(命令输出、文件路径、截图等)
      4. 报告内容不得少于 500 字(排除模板结构)
      
      **验证方法**: 运行以下命令检查报告完整性:
      
      ```bash
      # 方法 1: 检查模板占位符
      grep -r "{{" "{{SKILL_WORKSPACE}}/output/tests/{{SESSION_NAME}}/"
      
      # 方法 2: 使用验证脚本(推荐)
      python3 auto-test-project/scripts/verify_test_session.py --project-root "{{PROJECT_ROOT}}" --task-root "{{TASK_ROOT}}" "{{SKILL_WORKSPACE}}/output/tests/{{SESSION_NAME}}"
      
      # 方法 3: 严格模式(推荐在收尾/回归阶段使用)
      python3 auto-test-project/scripts/verify_test_session.py --project-root "{{PROJECT_ROOT}}" --task-root "{{TASK_ROOT}}" --require-plan "{{SKILL_WORKSPACE}}/output/tests/{{SESSION_NAME}}"
      ```
      
  • CHANGELOG.md 13.6 KB
    # Changelog
    
    本文件记录 `auto-test-project` 技能的变更历史。
    
    格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。
    
    ## [Unreleased]
    
    ### Fixed(修复)
    
    - 移除 Skill 结构检查器与公共约束同步器,避免项目级循环优化 Skill 托管仓库级 Skill 开发治理逻辑;相关入口已迁移至仓库根级 `tests/`。
    
    - 修复任务工作区迁移只更新文档、配置与脚本仍写入 `.bensz-api/skills/auto-test-project/` 的问题:新增统一运行态 task-root 解析器,创建、单会话验证、批量验证与自检现共用 `<task-root>/auto-test-project/output/{plans,tests}`;显式 task root 支持续跑原样复用,缺省时只分配新任务,legacy 根仅允许显式只读验证。
    - 补充 task root 越界、`..`、symlink、同分钟冲突、A/B continuation、legacy 只读和缺失路径结构化失败测试;默认流程新增否定断言,确保不再创建 `.bensz-api/skills/`。
    
    ### Changed(变更)
    
    - 版本升级:`1.3.2 → 1.4.0`;新增 `scripts/check_skill_structure.py`,支持 alpha/beta Skill 结构报告与 strict 门禁,并将 `auto-test-project` 自身迁移到四段式正文骨架。
    
    - 版本升级:`1.3.1 → 1.3.2`;`config.yaml:directories` 只保留 task-local 相对后缀,并同步更新 SKILL、README、FAQ、严格模式示例、模板与 CLI help。
    - 版本升级:1.3.0 → 1.3.1;将计划与测试会话默认目录从项目根 `plans/` / `tests/` 收敛到 `.bensz-api/skills/auto-test-project/output/plans/` 与 `.bensz-api/skills/auto-test-project/output/tests/`,同步更新 `SKILL.md`、README、references 与验证脚本的路径口径。
    
    ### Added(新增)
    
    - **批判性分析框架(技巧 0)**:强化系统视角和批判性思维能力
      - `references/PROJECT_ISSUE_DISCOVERY_TECHNIQUES.md`:新增"技巧 0:批判性分析框架(系统视角)"
        - 技巧 0.1:第一性原理思考(评估项目是否偏离核心目标)
        - 技巧 0.2:架构合理性质疑(评估模块划分、依赖方向、抽象层次)
        - 技巧 0.3:价值导向的问题分类(痛点级/隐患级/噪音级,避免噪音问题)
        - 技巧 0.4:根本原因分析(5 Whys)(挖掘问题本质,而非修复表象)
        - 技巧 0.5:批判性分析检查清单(每轮 A 轮必须回答的问题)
      - **核心价值定位**:项目级测试的核心价值在于"系统视角和批判性思维",而非替代 linter
    
    - **计划模板增强**:增加"系统视角与批判性分析"章节
      - `references/A_ROUND_PLAN_TEMPLATE.md`:新增"系统视角与批判性分析(⭐️ 核心章节)"
        - 本轮使用的批判性分析框架(必须明确使用的技巧)
        - 本轮核心目标对齐度分析(与项目核心目标的关系)
        - 本轮问题类型分布预期(痛点级/隐患级/噪音级比例)
        - 本轮最具洞察力的发现(非表面问题)
      - `templates/OPTIMIZATION_PLAN_TEMPLATE.md`:同步增加相同章节
    
    - **问题记录模板增强**:P0/P1 问题必须包含批判性思维字段
      - 新增"批判性质疑"(⭐️ 必填):质疑设计合理性,而非只描述现象
      - 新增"根本原因"(5 Whys 分析):至少回答 3 次"为什么"
      - 新增"价值判断":为什么这是 P0/P1?修复它的价值是什么?
    
    - **SKILL.md A.2 节强化**:
      - 新增"⭐️ 核心价值"说明:强调系统视角和批判性思维
      - 新增"⚠️ 质量要求"(批判性思维门槛):
        - 每轮至少有 1-2 个痛点级问题(架构级/设计级问题)
        - 每轮问题类型分布推荐:痛点级 ~20%、隐患级 ~50%、噪音级 ~30%
        - 每个 P0/P1 问题必须包含批判性思维字段
      - 新增"批判性思维优先"要求:**优先使用技巧 0**,再辅以技术检查技巧(技巧 1-8)
      - 更新"问题深度"要求:每个 P0/P1 问题必须有批判性质疑和根本原因分析
    
    - **模板自动替换机制**:从技术上消除"占位符未替换"问题
      - `scripts/create_test_session.py`:新增 `_render_template()` 函数,自动替换 `{{KEY}}` 格式的占位符
      - 支持模板变量:`TEST_ID`、`PROJECT_ROOT`、`SESSION_NAME`、`TEST_TIME`、`ROUND_KIND` 等
      - 新增 `--create-plan` 参数:自动创建计划文档骨架
      - TEST_REPORT_TEMPLATE.md:重构为详细的结构化模板
    
    - **强制数量要求**:迫使 AI 深入挖掘问题
      - A.2 节新增"数量要求"(强制):每轮至少 10 个问题(P0 + P1 + P2 总和)
      - 鼓励达到 15-20 个问题,项目级测试建议 15-25 个
      - A.4 节新增"数量验证"(强制检查):进入下一轮前必须确认问题数量 ≥ 10
    
    - **计划-执行一致性检查**:确保计划中的问题在报告中有对应记录
      - `scripts/verify_test_session.py`:新增 `check_plan_report_consistency()` 函数
      - 检查计划中的问题编号(P0-1, P1-2)是否在报告中有对应记录
      - 检查问题数量是否达到最低要求(≥ 10 个)
      - 新增配置常量 `MIN_ISSUE_COUNT = 10`
    
    - **新增参考文档**:
      - `references/PROJECT_ISSUE_DISCOVERY_TECHNIQUES.md`:项目级问题挖掘技巧(8 大技巧)
        - 跨模块一致性检查、依赖关系分析、配置管理审查
        - 文档同步检查、边缘情况压力测试、代码"模式匹配"
        - 安全性扫描、性能分析
      - `references/EXAMPLE_TEST_REPORT.md`:完整的测试报告示例(12 个问题)
        - 展示期望的输出质量
        - 每个问题都有:位置、影响、修复建议、验证方法
        - 使用多种问题挖掘技巧
    
    - `SKILL.md`:
      - 新增关键词:`template rendering`、`issue discovery techniques`
      - 引用 `references/PROJECT_ISSUE_DISCOVERY_TECHNIQUES.md`
      - 引用 `references/EXAMPLE_TEST_REPORT.md`
    
    - **批判性思维/建设性建议/反模式资源补齐**:
      - 新增 `references/CRITICAL_THINKING_GUIDE.md`、`references/CONSTRUCTIVE_SUGGESTION_GUIDELINES.md`、`references/ANTI_PATTERNS_LIBRARY.md`,用于提升 A 轮问题质量与可执行性
      - `references/PROJECT_ISSUE_DISCOVERY_TECHNIQUES.md` 补充“必读/独立评估提醒”入口
    
    - **A 轮独立评估与质量门槛配置化**:
      - `config.yaml` 新增 `a_round_check.independent_review.*` 与 `test_rounds.min_p0_p1_ratio/min_systemic_issues` 口径
      - `SKILL.md` 与 `templates/OPTIMIZATION_PLAN_TEMPLATE.md` 同步强调独立评估与门槛(系统性问题/占比)
    
    ### Changed(变更)
    
    - `scripts/create_test_session.py`:**重大重构**
      - 新增 `_render_template()` 函数
      - `_copy_or_template()` 函数新增 `template_values` 参数,支持模板变量替换
      - 新增 `--create-plan` 命令行参数
      - 内联模板改为使用 `{{ROUND_KIND}}` 等变量
    
    - `scripts/verify_test_session.py`:增强验证能力
      - 新增 `check_plan_report_consistency()` 函数
      - 新增配置常量 `MIN_ISSUE_COUNT`
      - 验证项从 4 项增加到 5 项
    
    - `templates/TEST_REPORT_TEMPLATE.md`:**完全重写**
      - 从简单的 115 行模板重构为详细的 137 行结构化模板
      - 包含:执行摘要、验证点执行情况、问题修复记录、问题修复统计、遗留问题、证据文件、下一步建议
      - 在模板末尾添加验证命令提示
    
    - 版本号升级为 `1.3.0`(`config.yaml: skill_info.version` 同步到 `SKILL.md` YAML frontmatter)
    
    ### Technical Details(技术细节)
    
    - 项目识别支持多种指令文件(CLAUDE.md、AGENTS.md、PROJECT.md 等)
    - 测试边界配置支持核心模块识别和排除路径设置
    - 优先级配置扩展为项目级示例(跨模块问题、架构问题等)
    - B轮质量检查增加 `project_level` 和 `scope` 字段
    
    ### Fixed(修复)
    
    - **模板占位符未替换问题**:通过 `_render_template()` 函数从技术上解决
    - **计划与执行脱节问题**:通过 `check_plan_report_consistency()` 检测
    - **报告内容过短问题**:通过 `MIN_ISSUE_COUNT` 和 `MIN_REPORT_LENGTH` 双重门槛
    
    - **B 轮可追溯性与口径漂移问题**:
      - `templates/B_ROUND_CHECK_TEMPLATE.md` 改为使用 `A_TEST_ID` 表达“对应 A 轮测试”,避免把 B 轮自身 ID 误当成 A 轮
      - B 轮相关文档/模板口径统一为“维度以 `config.yaml:b_round_check.dimensions` 为准”,避免硬编码“七大”导致漂移
    
    - **确定性自检兼容性问题**:
      - `scripts/verify_test_session.py` 增加 `from __future__ import annotations`,避免在 Python 3.9 下因 `X | Y` 注解运行时求值导致 CLI 崩溃
    
    - `scripts/create_test_session.py`:
      - 修复 B 轮创建会话时潜在的未定义变量问题(`--kind b` 可用)
      - 修复 plan 文件存在时误把 plan 复制到 `TEST_PLAN.md` 的行为;新增 `--seed-test-plan-from-plan`(默认关闭)
      - 统一 `--create-plan` 生成/引用的 plan 路径,避免 B 轮漏掉 `.md`
      - 补齐模板变量(`PLAN_ID/PLAN_TIME/PROJECT_TYPE/PLAN_DOC_PATH` 等)用于头字段自动填充
    
    - `scripts/verify_test_session.py`:
      - 放宽“文件:行号”证据正则,支持常见小写路径与 Windows 路径,减少误判
    
    - 文档与模板一致性:
      - 将“B 轮质量检查”口径统一为“维度以 `config.yaml:b_round_check.dimensions` 为准”(避免硬编码数量导致漂移)
      - 重写 `templates/OPTIMIZATION_PLAN_TEMPLATE.md` 为 `P0-1/P1-1/P2-1` 编号结构,确保计划-报告一致性检查可落地
      - `templates/TEST_PLAN_TEMPLATE.md` 标题改为按轮次变量渲染,并统一使用 `PLAN_DOC_PATH`
    
    - CLI 可用性与严格模式:
      - `scripts/create_test_session.py` 增加 `--id` 格式校验(强制 `vYYYYMMDDHHMM`),并将常见错误统一为 argparse 报错(无堆栈)
      - `scripts/verify_test_session.py` 改为 argparse CLI,新增 `--require-plan`(严格模式)以及 `--min-report-length`/`--min-issue-count`(阈值可配置)
    
    - `config.yaml` 口径精简与对齐:
      - 明确标注 scripts 仅解析必要配置段(如 `directories/templates/verification`),其余字段作为规划口径参考
      - 去除重复字段与通用但未被本 skill 使用的段落(testing/reporting/quality/acceptance)
      - 补齐结构化门槛字段:A 轮最少问题数/目标范围、B 轮建议数与修复率门槛、verify 默认阈值
      - 同步更新 `README.md` 与 `SKILL.md` 对配置字段的引用口径
    
    - 文档有机瘦身与模块化:
      - `SKILL.md` 增加 Quick Start,并将 FAQ/最佳实践正文下沉到 `references/`
      - 新增 `references/FAQ.md`,集中维护常见问题、证据标准与严格模式用法
      - `README.md` 增加 Quick Start,移除重复的长篇最佳实践/FAQ,改为引用 references
    
    - 模板与示例对齐严格模式:
      - 重写 `templates/B_ROUND_CHECK_TEMPLATE.md` 为可填写骨架,并使用 `P0-1` 编号以支持一致性检查
      - `templates/TEST_PLAN_TEMPLATE.md` 与 `templates/TEST_REPORT_TEMPLATE.md` 补充严格模式验证入口
      - 新增 `references/EXAMPLE_STRICT_MINIMAL.md`,用于演示编号对齐与严格验证
      - `references/A_ROUND_PLAN_TEMPLATE.md` 统一使用 `P0-1/P1-1/P2-1` 编号口径
    
    - 确定性自检与批量验证:
      - 新增 `scripts/verify_skill.py`:一键自检本 skill(必需文件、脚本可用性、模板关键占位符自动填充回归)
      - 新增 `scripts/verify_all_sessions.py`:批量验证 `tests/` 下会话(支持 `--require-plan` 与 `--skip-missing-plan`)
      - 清理误生成的 `plans/B轮-v202601152999.md`,避免严格模式批量验证被历史残留阻塞
    
    - 安全性与跨平台鲁棒性:
      - `scripts/create_test_session.py` 默认拒绝系统根目录/用户主目录作为 project-root(需显式 `--allow-unsafe-root` 才可覆盖)
      - 将缺少指令文件的 warning 输出到 stderr,避免污染 stdout 的“会话路径输出”
      - `scripts/verify_test_session.py` 与 `scripts/verify_skill.py` 采用容错读取,避免非 UTF-8 文件导致验证崩溃
      - Quick Start/FAQ 补充安全提示,降低误用风险
    
    ## [1.0.0] - 2026-01-12
    
    ### Added(新增)
    - 新增 `auto-test-project` 技能:项目级自动化测试驱动优化
    - 从 `auto-test-skill` 迁移核心能力并扩展为项目级支持
    - 新增项目类型识别功能:自动识别 Agent Skill、工作流项目等
    - 新增项目初始化流程:验证项目结构、识别测试边界
    - 新增跨模块问题分析和处理能力
    - 新增项目级六大质量原则检查(扩展自 skill 级别)
    - 新增项目级测试模板:
      - `B_ROUND_CHECK_TEMPLATE.md`:B轮质量检查模板(项目级扩展)
      - `TEST_PLAN_TEMPLATE.md`:测试计划模板
      - `PROJECT_TYPE_ANALYSIS_TEMPLATE.md`:项目类型分析模板
      - `BUG_REPORT_TEMPLATE.md`:Bug 报告模板(支持跨模块问题)
      - `OPTIMIZATION_PLAN_TEMPLATE.md`:优化计划模板
      - `TEST_REPORT_TEMPLATE.md`:测试报告模板
      - `FINAL_SUMMARY_TEMPLATE.md`:最终总结模板
    - 新增辅助脚本:`scripts/create_test_session.py`(适配项目级)
    - 新增配置文件:`config.yaml`(项目级配置,包含项目识别、测试边界等)
    - 新增参考文档:`references/PROJECT_TESTING_BEST_PRACTICES.md`
    
    ### Changed(变更)
    - 将测试对象从"单个 skill"扩展为"完整项目"
    - 将质量检查维度从 skill 级别扩展为项目级别
    - 将输出目录从 skill 内部扩展为项目根目录
    - 将问题分析从单文件扩展为跨模块、跨文件
    - 将 CHANGELOG 更新目标从 skill 级别扩展为项目级别
    
    ### Technical Details(技术细节)
    - 项目识别支持多种指令文件(CLAUDE.md、AGENTS.md、PROJECT.md 等)
    - 测试边界配置支持核心模块识别和排除路径设置
    - 优先级配置扩展为项目级示例(跨模块问题、架构问题等)
    - B轮质量检查增加 `project_level` 和 `scope` 字段
    
  • config.yaml 10.2 KB
    # auto-test-project 配置文件
    # 用于配置项目级测试驱动优化工作流的参数(尽量避免在 SKILL.md 中硬编码)
    #
    # 注意:
    # - `directories.*` 只保存 `<task-root>/auto-test-project/` 内的相对后缀;运行态 task root 由 CLI 参数或分配器提供。
    # - 创建、单会话验证与批量验证共同使用 `scripts/workspace_paths.py`,拒绝绝对越界、`..` 与 symlink 逃逸。
    # - 其余字段主要用于 AI/人类在 A/B 轮规划时复用默认参数与检查维度,并作为文档的“单一口径”来源。
    
    # ============================================================================
    # Skill 信息
    # ============================================================================
    
    skill_info:
      name: "auto-test-project"
      version: "1.4.1"
      description: "项目级自动化测试驱动优化技能 - 支持多轮A轮迭代与B轮质量检查(维度以 b_round_check.dimensions 为准)+ A轮独立评估 + 批判性思维门槛(P0+P1占比/系统性问题)+ 强制验证机制 + 模板自动替换 + 强制数量要求"
      author: "Bensz Conan"
      category: "normal"
    
    # ============================================================================
    # 目录与命名规范
    # ============================================================================
    
    directories:
      # 规划文档目录(相对于本轮 <task-root>/auto-test-project/)
      plans: "output/plans"
      # 测试目录(相对于本轮 <task-root>/auto-test-project/)
      tests: "output/tests"
    
    # ============================================================================
    # 项目识别
    # ============================================================================
    
    project_detection:
      # 项目指令文件候选列表(按优先级排序)
      instruction_files:
        - "CLAUDE.md"
        - "AGENTS.md"
        - "PROJECT.md"
        - "README.md"
      # 项目配置文件候选列表
      config_files:
        - "config.yaml"
        - "package.json"
        - "pyproject.toml"
        - "Cargo.toml"
        - "go.mod"
        - "pom.xml"
        - "build.gradle"
      # 项目类型识别标志
      type_markers:
        skill:
          - "SKILL.md"
          - ".claude/skills/"
        workflow:
          - "workflows/"
          - ".github/workflows/"
        script_collection:
          - "bin/"
          - "scripts/"
        documentation:
          - "docs/"
          - "mkdocs.yml"
          - "docusaurus.config.js"
    
    # ============================================================================
    # 轮次控制
    # ============================================================================
    
    test_rounds:
      # A轮测试默认轮次
      default_a_rounds: 1
      # 最大A轮测试轮次(防止无限循环)
      max_a_rounds: 10
      # A轮每轮最少问题数量(P0+P1+P2,总和)
      min_issues_per_round: 10
      # 项目级建议目标范围(考虑跨模块复杂性)
      target_issues_range: [15, 25]
      # A轮 P0+P1 最小占比(百分比)- 确保问题有价值
      min_p0_p1_ratio: 60
      # A轮系统性问题最小数量(架构/过度设计/一致/安全等)
      min_systemic_issues: 3
    
    # ============================================================================
    # A轮审查范围(独立评估;规划口径)
    # ============================================================================
    
    a_round_check:
      independent_review:
        # 是否启用独立评估模式(A轮不看历史 plans/ 与 tests/,避免确认偏差/路径依赖)
        enabled: true
        # 排除的历史/产物路径(Glob)
        exclude_patterns:
          - "tests/**"
          - "plans/**"
          - ".bensz-api/task-*/auto-test-project/**"
          - "**/.git/**"
          - "**/node_modules/**"
          - "**/__pycache__/**"
          - "**/_artifacts/**"
          - "**/*.bak"
    
    # ============================================================================
    # B轮质量检查(项目级质量原则检查;维度以 b_round_check.dimensions 为准)
    # ============================================================================
    
    b_round_check:
      # B轮是否为强制环节(默认true,除非用户明确跳过)
      mandatory: true
      # B轮必须提出的最小建议数量(P0 + P1 + P2 总和)
      min_suggestions: 10
      # 建议数量目标范围
      target_suggestions_range: [10, 20]
      # P0/P1 修复率要求(百分比,作为质量门槛)
      p0_fix_rate_required: 100
      p1_fix_rate_required: 80
      dimensions:
        - name: "hardcoded_ai_plan"
          label: "硬编码/AI功能规划"
          description: "检查确定性操作是否应硬编码,启发式判断是否由AI处理"
          project_level: true
          scope: "跨模块分析:识别可脚本化的重复操作"
        - name: "redundancy_check"
          label: "冗余残留错误检查"
          description: "检查是否存在重复逻辑、残留引用、僵尸文件"
          project_level: true
          scope: "跨文件检查:模块间的重复逻辑、全局的残留引用"
        - name: "security_check"
          label: "安全性检查"
          description: "检查输入验证、路径处理、敏感信息、权限控制"
          project_level: true
          scope: "项目级安全:外部接口、依赖安全性、配置文件安全"
        - name: "overdesign_check"
          label: "过度设计检查"
          description: "检查是否存在YAGNI违反、不必要的抽象、配置臃肿"
          project_level: true
          scope: "架构级检查:模块边界、抽象层次、配置复杂度"
        - name: "generality_check"
          label: "通用性检查"
          description: "检查是否包含不必要的年份/场景限制/平台依赖等"
          project_level: true
          scope: "跨平台检查:项目是否过度依赖特定平台或场景"
        - name: "consistency_check"
          label: "一致性检查"
          description: "检查项目指令文件、配置、文档与代码的一致性"
          project_level: true
          scope: "全局一致性:模块间接口、命名规范、文档同步"
        - name: "project_instruction_weight_check"
          label: "项目指令文件瘦身检查"
          description: "检查项目指令文件(CLAUDE.md等)是否过于冗长,应将详细内容模块化"
          project_level: true
          scope: "项目级文档瘦身:核心原则保留,详细内容下沉到模块"
        - name: "config_centralization_check"
          label: "配置集中化检查"
          description: "检查可配置参数是否集中到配置文件作为单一真相来源,避免散落在文档/脚本/示例中造成口径不一致"
          project_level: true
          scope: "项目级配置治理:精确端(配置)与模糊端(文档/Prompt)分离,避免重复与漂移"
    
    # ============================================================================
    # 测试会话配置
    # ============================================================================
    
    test_session:
      # 时间戳格式
      timestamp_format: "%Y%m%d%H%M"
      # 最大迭代轮数(默认10轮,防止无限循环)
      max_iterations: 10
    
    # ============================================================================
    # 问题优先级配置
    # ============================================================================
    
    priority:
      # 优先级定义
      levels:
        P0:
          name: "Critical"
          description: "阻塞性问题,完全无法使用"
          response_time: "立即"
          color: "red"
          examples:
            - "项目完全无法初始化"
            - "核心功能完全失效"
            - "数据丢失或损坏"
            - "严重安全漏洞"
        P1:
          name: "High"
          description: "严重问题,核心功能受影响"
          response_time: "24小时内"
          color: "orange"
          examples:
            - "主要功能失效"
            - "性能严重退化"
            - "跨模块兼容性问题"
            - "用户体验严重受损"
        P2:
          name: "Medium"
          description: "中等问题,部分功能受限"
          response_time: "3天内"
          color: "yellow"
          examples:
            - "边缘功能失效"
            - "性能轻微退化"
            - "文档不一致"
            - "配置文件问题"
        P3:
          name: "Low"
          description: "轻微问题,不影响主要功能"
          response_time: "1周内"
          color: "green"
          examples:
            - "文档错误"
            - "UI瑕疵"
            - "代码风格问题"
            - "体验优化建议"
    
    # ============================================================================
    # 文档模板配置
    # ============================================================================
    
    templates:
      # Bug报告模板
      bug_report: "templates/BUG_REPORT_TEMPLATE.md"
      # 优化计划模板
      optimization_plan: "templates/OPTIMIZATION_PLAN_TEMPLATE.md"
      # 测试计划模板
      test_plan: "templates/TEST_PLAN_TEMPLATE.md"
      # 测试报告模板
      test_report: "templates/TEST_REPORT_TEMPLATE.md"
      # 最终总结模板
      final_summary: "templates/FINAL_SUMMARY_TEMPLATE.md"
      # B轮质量检查模板
      b_round_check: "templates/B_ROUND_CHECK_TEMPLATE.md"
      # 项目类型分析模板
      project_type_analysis: "templates/PROJECT_TYPE_ANALYSIS_TEMPLATE.md"
    
    # ============================================================================
    # 验证脚本默认值(scripts/verify_test_session.py)
    # ============================================================================
    
    verification:
      # TEST_REPORT.md 的最小长度(字符数,扣除占位文本后)
      min_report_length: 500
      # plans/<session_name>.md 中最少的问题编号数量(形如 "#### P0-1:")
      min_issue_count: 10
      # 是否默认要求计划文档存在并可执行一致性检查(脚本可通过 --require-plan 开启)
      require_plan_default: false
    
    # ============================================================================
    # 项目级测试边界配置
    # ============================================================================
    
    project_testing:
      # 核心模块识别(通过目录/文件模式)
      core_modules:
        - pattern: "src/"
          priority: "critical"
        - pattern: "lib/"
          priority: "high"
        - pattern: "scripts/"
          priority: "high"
        - pattern: "SKILL.md"
          priority: "critical"
        - pattern: "CLAUDE.md"
          priority: "critical"
      # 跨模块测试配置
      cross_module_testing:
        enabled: true
        # 需要集成测试的模块组合
        integration_pairs:
          - ["src/", "lib/"]
          - ["scripts/", "config.yaml"]
      # 排除的测试路径
      exclude_paths:
        - "node_modules/"
        - "__pycache__/"
        - ".git/"
        - "tests/"
        - "plans/"
        - "_artifacts/"
    
  • README.md 5.6 KB
    # auto-test-project
    
    这个 skill 用来对完整项目做 A 轮批判性测试和 B 轮质量检查,适合“项目级测试驱动优化”;如果你只是想修一个明确功能点,通常不该直接用它。
    
    ## 用法
    
    ### 最推荐用法
    
    ```text
    请使用 auto-test-project skill 对本项目进行项目级测试驱动优化。
    输入:项目根目录 `.`,以及要重点检查的问题或优化目标
    输出:`.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/plans/` 与 `.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/` 下的 A 轮/B 轮计划、测试记录和验证结果
    ```
    
    ### 进阶用法
    
    ```text
    请使用 auto-test-project skill 对这个项目做完整测试闭环。
    输入:项目根目录 `.`,重点关注认证流程、文档一致性和配置安全
    输出:多轮 A 轮计划、测试记录、B 轮质量检查结果
    另外,还有下列参数约束:
    - A 轮要求:至少发现 10 个问题
    - B 轮要求:必须执行
    - 输出要求:所有证据都写入 `.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/plans/` 和 `.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/`
    ```
    
    ## 能做什么
    
    - 把一次“项目测试”拆成可追溯的 A 轮问题发现和 B 轮质量复检。
    - 为技能项目、工作流项目、脚本工具集、文档项目提供统一的测试闭环。
    - 强制把测试计划、问题清单、验证结果写入文件,而不是只给口头建议。
    - 默认把 B 轮质量检查视为必做步骤。
    - 不适合替代单个 bugfix、单个功能开发或日常答疑。
    
    ## 使用示例
    
    ### 示例 1:测试一个技能仓库
    
    ```text
    请使用 auto-test-project skill 测试这个 skill 项目。
    输入:项目根目录 `.`,重点关注 README、SKILL.md、config.yaml 和 scripts 的一致性
    输出:A 轮问题计划、测试记录和 B 轮质量检查结果
    ```
    
    ### 示例 2:做一轮带重点的项目审查
    
    ```text
    请使用 auto-test-project skill 对这个项目做项目级测试。
    输入:项目根目录 `.`,重点检查路径安全、输出目录隔离和文档口径
    输出:测试报告与修复建议
    ```
    
    ### 示例 3:要求完整闭环
    
    ```text
    请使用 auto-test-project skill 对本项目做完整测试驱动优化。
    输入:项目根目录 `.`
    输出:`.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/plans/`、`.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/`、B 轮质量检查结果
    另外,还有下列参数约束:
    - 至少执行 1 轮 A 轮
    - 不跳过 B 轮
    - 收尾时验证测试会话完整性
    ```
    
    ## 输出
    
    - `.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/plans/vYYYYMMDDHHMM.md`:A 轮问题分析与改进计划。
    - `.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/vYYYYMMDDHHMM/`:A 轮测试会话目录,至少包含 `TEST_PLAN.md` 和 `TEST_REPORT.md`。
    - `.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/plans/B轮-vYYYYMMDDHHMM.md`:B 轮质量检查报告。
    - `.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/B轮-vYYYYMMDDHHMM/`:B 轮验证会话目录。
    - README 不会替你“自动通过测试”;它强调的是问题发现、证据沉淀和闭环验证。
    
    ## 配置
    
    - 配置文件:`auto-test-project/config.yaml`
    - 默认 A 轮轮次:`1`
    - 单轮最少问题数:`10`
    - A 轮建议目标问题数:`15-25`
    - B 轮默认:`mandatory: true`
    - 关键配置节:
      - `directories`
      - `test_rounds`
      - `b_round_check.dimensions`
      - `verification`
    
    ## 备选用法(脚本/硬编码)
    
    如果你想先创建标准会话骨架,再人工补计划和报告,可以直接走脚本。
    
    ### 创建 A 轮或 B 轮会话
    
    ```bash
    TASK_ROOT=".bensz-api/task-{yyyymmdd-hhmm}-{简短描述}"
    python3 auto-test-project/scripts/create_test_session.py \
      --project-root . \
      --task-root "$TASK_ROOT" \
      --kind a \
      --create-plan
    ```
    
    省略 `--task-root` 会分配一个全新的任务根;A/B 轮与 continuation 应始终回传同一个 task root,不能靠脚本猜测最近任务。
    
    ### 用现有计划填充 TEST_PLAN
    
    ```bash
    python3 auto-test-project/scripts/create_test_session.py \
      --project-root . \
      --task-root "$TASK_ROOT" \
      --kind a \
      --create-plan \
      --seed-test-plan-from-plan
    ```
    
    ### 校验测试会话完整性
    
    ```bash
    python3 auto-test-project/scripts/verify_test_session.py \
      --project-root . \
      --task-root "$TASK_ROOT" \
      --require-plan \
      "$TASK_ROOT/auto-test-project/output/tests/v202603241200"
    ```
    
    旧 `.bensz-api/skills/auto-test-project/` 只支持显式只读验证:传入 `--legacy-root .bensz-api/skills/auto-test-project`。创建脚本没有 legacy 写入模式。
    
    ## 常见问题
    
    ### Q:它和 `auto-test-code` 有什么区别?
    
    A:`auto-test-project` 面向整个项目,强调跨模块、跨文档、跨配置的一致性和闭环;不是单文件或单函数测试器。
    
    ### Q:我只想修一个功能,还要用它吗?
    
    A:通常不用。只有当你需要系统性测试、质量复检、沉淀计划与证据时,它才最有价值。
    
    ### Q:为什么一定要写 `.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/plans/` 和 `.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/auto-test-project/output/tests/`?
    
    A:这是它的核心价值之一。没有计划和证据,项目级测试很难复现、比较和收尾。
    
    ### Q:可以跳过 B 轮吗?
    
    A:默认不建议。B 轮负责检查一致性、安全性、过度设计和配置集中化,缺了它就不算完整闭环。
    
  • SKILL.md 9.3 KB
    ---
    name: auto-test-project
    category: normal
    description: 当用户明确要求“测试项目”、 “运行 auto-test-project”或“进行项目级测试”时使用。对完整项目执行多轮 A 轮批判性测试与 B 轮质量检查,发现、记录、修复并验证问题。⚠️ 不适用:用户只是想优化功能、询问项目问题,或没有明确测试意图。
    metadata:
      author: Bensz Conan
      short-description: 多轮 A 轮测试 + B 轮质量检查的项目级测试驱动优化流水线
      keywords:
        - auto-test-project
        - 项目级测试
        - project QA
    ---
    
    # auto-test-project(项目级自动化测试驱动优化)
    
    ## 目标
    
    为具备明确目录和可执行入口的完整项目提供可追溯的项目级测试与优化流水线:项目初始化、A 轮问题发现与修复、B 轮质量原则检查、验证和交付总结。仅在用户明确要求项目级测试时触发;单个 Agent Skill 使用 `auto-test-skill`。
    
    本 Skill 将“项目”定义为具有指令文件或等价入口、明确目录结构和功能模块,并包含可执行代码、脚本或流程定义的项目,包括 Agent Skills、工作流项目、脚本工具集和结构化文档项目。本 Skill 不替代领域业务判断,不默认修改远程系统,不把报告模式误当作发布阻断,也不负责单 Skill 测试。
    
    ## 流程
    
    ### 输入
    
    - 项目根目录、用户指定的测试范围、优化目标或 A 轮次数。
    - 项目指令文件、配置文件、模块目录、可执行代码/脚本和已有测试入口。
    - 需要关注的历史问题、已知约束和可接受的修改边界。
    
    排除历史任务产物、缓存、依赖目录和敏感信息。先验证项目结构与可执行入口,再确定项目类型、核心模块和跨模块测试边界;配置中的 `project_testing`、`a_round` 和 `b_round_check` 是规划口径的单一来源。
    
    ### 执行步骤
    
    1. **初始化会话**:使用宿主已经公开并锁定的任务根;调用 `scripts/create_test_session.py` 创建 A/B 会话及模板文件。缺省 `--task-root` 时才分配新任务;A/B 轮和 continuation 必须显式复用同一 task root,不猜测最近任务。
    2. **识别项目**:检查指令文件、目录结构、功能模块、入口和测试边界,排除 `node_modules/`、`__pycache__/`、`.git/`、`tests/`、`plans/` 和 `_artifacts/` 等配置的排除路径。
    3. **执行 A 轮(可重复 N 次)**:结合 `references/CRITICAL_THINKING_GUIDE.md` 与配置的审查范围独立发现问题;为每个问题记录证据、影响、优先级、修复建议和验收标准,形成可引用的 `P0-1` 等编号。
    4. **修复并轻量测试**:按计划优先修复 P0/P1,再处理其它问题;只做最小必要修改,补充 `TEST_PLAN.md` 和 `TEST_REPORT.md` 中的可复现命令、结果和证据。项目已有测试时优先运行受影响范围,再按风险扩大验证。
    5. **判断下一轮**:检查计划中的问题是否均有报告对应项、成功标准是否有验证结论,以及 P0/P1 是否闭环;达到用户指定轮数或明确的停止条件后进入 B 轮。
    6. **执行 B 轮质量检查**:依据 `config.yaml:b_round_check.dimensions` 检查硬编码与 AI 功能规划、冗余残留、安全性、过度设计、通用性、一致性、项目指令文件瘦身和配置集中化;对发现的 P0/P1 做针对性修复与轻量验证。
    7. **收尾验证**:每个会话运行 `scripts/verify_test_session.py`,最终运行 `scripts/verify_all_sessions.py --require-plan`;更新目标项目的 `CHANGELOG.md`,并保留失败证据与提前结束原因。
    
    ### 输出
    
    交付以下可追溯产物,具体模板由 `config.yaml:templates` 指定:
    
    - A 轮计划:`<task-root>/auto-test-project/output/plans/vYYYYMMDDHHMM.md`。
    - A 轮会话:`<task-root>/auto-test-project/output/tests/vYYYYMMDDHHMM/`,包含 `TEST_PLAN.md` 和 `TEST_REPORT.md`。
    - B 轮计划:`<task-root>/auto-test-project/output/plans/B轮-vYYYYMMDDHHMM.md`。
    - B 轮会话:`<task-root>/auto-test-project/output/tests/B轮-vYYYYMMDDHHMM/`,包含 `TEST_PLAN.md` 和 `TEST_REPORT.md`。
    - 正式代码、文档和项目 `CHANGELOG.md`:按目标项目原有目录约定保存。
    
    报告必须区分已修复、未修复、无法验证和不确定问题,并提供复现命令或后续人工动作;不得用空报告或口头结论代替证据。
    
    ### 输出管理
    
    所有计划草案、测试报告、会话元数据、命令输出和验证证据写入当前任务根下的 `auto-test-project/`,不得写入旧的 `.bensz-api/skills/auto-test-project/`。规划文档放在 `output/plans/`,会话放在 `output/tests/`,B 轮会话名统一使用 `B轮-` 前缀。
    
    旧目录仅允许验证脚本通过 `--legacy-root` 显式只读检查;创建脚本不得写入。不得覆盖用户已有文件,不得把缓存、依赖或测试运行产物写入源码目录。
    
    ### 校验
    
    使用分钟级会话 ID `vYYYYMMDDHHMM`。典型 A 轮初始化与验证命令如下:
    
    ```bash
    TASK_ROOT=".bensz-api/task-{yyyymmdd-hhmm}-{简短描述}"
    python3 auto-test-project/scripts/create_test_session.py \
      --project-root . --task-root "$TASK_ROOT" --kind a --create-plan
    python3 auto-test-project/scripts/verify_test_session.py \
      --project-root . --task-root "$TASK_ROOT" --require-plan \
      "$TASK_ROOT/auto-test-project/output/tests/vYYYYMMDDHHMM"
    ```
    
    B 轮创建时显式关联 A 轮:
    
    ```bash
    python3 auto-test-project/scripts/create_test_session.py \
      --project-root . --task-root "$TASK_ROOT" --kind b \
      --id vYYYYMMDDHHMM --a-test-id vYYYYMMDDHHMM
    ```
    
    最终验证:
    
    ```bash
    python3 auto-test-project/scripts/verify_all_sessions.py \
      --project-root . --task-root "$TASK_ROOT" --require-plan
    ```
    
    通过标准:每轮会话均有非空计划和报告,模板占位符已替换,计划与报告的问题编号和成功标准可对应,验证脚本通过,P0 修复率为 100%,P1 修复率达到配置门槛,且 B 轮强制完成或明确记录无法完成的原因。仓库级 Skill 结构和公共约束由仓库治理检查器负责,不由本 Skill 的会话验证脚本替代。
    
    ### 失败与恢复
    
    将失败分类为输入缺失、项目结构无效、脚本错误、测试失败、外部依赖不可用和结果不确定;保存命令、输出、错误和已生成证据,不伪造通过、不删除失败记录。
    
    可在同一任务根重试未完成阶段;A/B continuation 必须复用原 task root 和关联 ID。输入或环境问题先停止并给出补充项/复现命令;测试失败保留失败报告并允许针对性修复后重跑;无法取得可靠证据时标记为不确定并转人工复核。达到用户指定轮数后不得擅自扩展范围。
    
    ## 约束
    
    <!-- BEGIN COMMON CONSTRAINTS -->
    <!-- Source-Hash: sha256:15120201e9e0c7569517261d57ecefb63ac279c26ed13876f8e95b6dc35854d3 -->
    <!-- Template-ID: skill-common-constraints; Template-Version: 1; Sync-Policy: exact-block -->
    
    ### 公共硬约束
    
    本块由 `docs/templates/skill-common-constraints.md` 统一维护;每个 `SKILL.md` 的 `## 约束` 必须逐字同步本块,不得在副本中改写公共规则。
    
    - 任务需要落盘时,使用唯一的 `./.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/` 根目录;共享材料放入 `shared/`,Skill 专属材料放入该 Skill 的 `input/`、`output/`、`log/`。
    - 正式交付物、源代码和正式计划按项目约定保存,不写入任务工作区;未经授权不覆盖、删除、迁移或远程写入。
    - 项目维护变更检查 BAC 可用性并记录需求、AI 产出、工具结果、文件改动和验证摘要;BAC 只做过程审计,不替代署名、责任或合规判断。
    - 不记录 API Key、访问令牌、密码、Cookie、环境/凭据文件、私有 Prompt、身份信息、本地用户名、主机名或不必要的大体积原始数据。
    - 文件路径必须规范化并限制在授权项目范围内;外部 URL、子进程和网络访问遵循最小权限,防止路径遍历、SSRF 和命令注入。
    - Skill 版本唯一记录在自身 `config.yaml:skill_info.version`;公开 API、协议、目录或配置变更同步文档与 `CHANGELOG.md`。
    - `bensz-collect-bugs` 是一个 Agent Skill;仅将 Bensz Agent Skill 或 Bensz 基础设施本身的设计缺陷交给它。先脱敏写入 `~/.bensz-skills/bugs/`,当前任务不中断,只有用户明确要求才公开上报,禁止直接修改用户已安装的 Skill 源码。
    
    <!-- End of canonical common constraints. -->
    <!-- END COMMON CONSTRAINTS -->
    
    ### Skill 专属约束
    
    - A 轮至少按配置完成用户指定次数;若提前结束,必须说明原因和未完成范围。
    - B 轮质量检查为强制阶段,维度以 `config.yaml:b_round_check.dimensions` 为准,不得在正文另设易漂移的默认清单。
    - 计划、修复、测试、证据和结论必须形成闭环;P0/P1 不得仅以建议或口头判断结案。
    - 与 `auto-test-skill` 的边界固定为:本 Skill 面向完整项目及跨模块关系,`auto-test-skill` 面向单个 Skill 目录。
    - 可复用的 FAQ(`references/FAQ.md`)、最佳实践、问题挖掘技巧、反例、严格示例(`references/EXAMPLE_STRICT_MINIMAL.md`)和报告示例放在 `references/`;会话创建、单会话验证、批量验证和 Skill 自检使用 `scripts/`,不得把这些详细材料重新堆回正文。
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related