mcpField
G
generate-judge-problems

generate-judge-problems

github.com/willbooster开发0213 次安装2 天前更新
代码仓库

关于 generate-judge-problems

用于在仓库中编写 Exercode Judge v2 编程题的技能,指导 Agent 完成题目设计、可复现环境、答案与测试用例编写、判题和验证。

主要功能

  1. 01指导 Judge v2 题目格式
  2. 02限定题目在课程内存放
  3. 03固定运行时与依赖版本
  4. 04编写答案并生成测试用例
  5. 05验证判题器和模板
  6. 06执行题目验证并汇报

适用场景

  1. 01创建课程专属 Judge 题目
  2. 02构建可复现的题目仓库
  3. 03用生成的测试用例检查答案

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/willbooster/exercode-problem-utils --skill generate-judge-problems

安装后在 Claude Code 中输入/generate-judge-problems确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
example-problem.md
problem-authoring.md
problem-format.md

详细介绍

生成 judge v2 题目

编写任何文件前,请先阅读以下参考资料:

仅生成 v2 题目:题面文件必须是题目目录中的 problem.md。绝不要使用旧版 <id>.problem.md 命名方式。

对于 Agent 挑战(学习者通过让 AI Agent 调用 Exercode playground API 来解题的题目;参见 references/problem-format.md 中的 agentChallenge),跳过 references/problem-authoring.md 的步骤 2–9,以及有关 stdin/stdout、测试用例、## 制約、模板和代码规则的要求;这些要求均以提交程序为前提。难度级别和该文档中的其他题面规则(仅使用学习者已学过的概念、使用简洁明确的日语)仍然适用。编写带有 agentChallenge 前置元数据的 problem.md,并用日语撰写题面,不包含 ## 制約 / ## 入力 / ## 出力 小节:写明要练习的技巧和要使用的 playground API,并以 ## 正解条件 小节结尾。在 model_answers/explanation/explanation.md 中编写解题说明(解法、示例提示词和常见错误),然后按照步骤 10 进行验证,并按照步骤 11 进行报告。

  1. 确认输入:学习目标或源材料、目标课程、目标编程语言、题目数量和难度范围。将每道题放在 <courseId>/problems/<problemId>/ 下;绝不要创建全仓库通用题目,也不要引用其他课程的题目。根据输入拟定题目草稿(id、name、overview、objective、difficulty);生成多道题目时,按照 references/problem-authoring.md 中的难度级别从易到难排序。

  2. 确保题目文件可在仓库中复现。保留现有的 mise 配置和兼容的固定版本。如果不存在 mise.toml、.mise.toml 或 .tool-versions,则创建 .tool-versions;否则保留现有格式。为运行已跟踪的模型答案、模板、判题程序和调试器所需的每个运行时、包管理器和命令行工具指定精确版本。在各语言的标准项目清单中声明其导入的第三方库,并在支持时指定精确的直接依赖版本,同时更新相应的标准锁定文件。对于 mise TOML 配置,运行 mise trust --yes,然后运行 mise install,之后正常使用已安装的命令。不要依赖全局安装、临时下载或临时依赖环境,也不要添加仅用于编写或验证题目的工具。

  3. 对于每道题,先在 model_answers/<languageId>/ 下编写主要模型答案(例如 python/main.py)。在编写题面前亲自解题,有助于及早发现歧义。只能使用学习者已经学过的概念;如果未提供已学概念列表,则根据学习目标作出合理假设,并在最终报告中说明。

  4. 按照 references/problem-format.md 编写 problem.md(frontmatter + 日文题面),并遵循 references/problem-authoring.md 中的题面规则。对于标准 stdin/stdout 题目,不要创建 judge.ts 或 debug.ts——判题服务会自动生成这两个文件,而提交默认内容的副本会被验证器拒绝。只有题目需要特殊判题(GUI、LLM、命令行等)时,才编写自定义 judge.ts(并配套编写 debug.ts);如果题目需要定制调试,也可以仅为标准 stdio 题目额外编写真正自定义的 debug.ts。

  5. 在 test_cases/ 下创建测试输入:至少 1 个 example_* 测试用例(展示给学习者,并在题面中对应列出入力例/出力例;对于仅输入文件的题目,出力例展示判定的输出文件,见 references/problem-format.md),以及至少 1 个隐藏测试用例,总数至少 4 个。测试输入应具有多样性,并涵盖边界情况(约束的最小值/最大值、允许时的 0 或负数等边界条件)。所有输入都必须不同。读取文件而非 stdin 的程序从 <id>.fin/ 读取;写入文件的程序使用 <id>.fout/ 进行判定(见 references/problem-format.md)。

  6. 运行模型答案,在本地生成每个 .out 文件以及 .fout/ 下的所有文件——绝不要手写预期输出。对于纯 stdin/stdout 题目:

    cd <problemDir> && for f in test_cases/*.in; do python3 model_answers/python/main.py < "$f" > "${f%.in}.out"; done
    

    如果任一测试用例使用 .fin/、_shared.fin/ 或 .fout/,或者缺少 .in,则在题目目录内的临时目录中运行每个测试用例(以便使用仓库固定的工具),在 <id>.in 存在时将其作为输入,捕获标准输出,并用程序写出的内容替换 <id>.fout/。将以下内容保存为 <problemDir>/.tmp/expected.sh(粘贴时,缩进的 heredoc 不会终止),然后运行 bash -euo pipefail <problemDir>/.tmp/expected.sh [<outputFile> ...]:

    # The produced file paths are the arguments; stdout-only problems pass none.
    cd <problemDir>
    root=$PWD
    # A scratch directory inside the problem keeps the pinned tools; it is removed on success and kept on failure.
    mkdir -p .tmp && scratch=$(mktemp -d .tmp/expected.XXXXXX)
    trap 'st=$?; if [ $st -eq 0 ]; then rm -rf "$scratch"; else echo "kept $scratch for inspection" >&2; fi; exit $st' EXIT
    # Every test case id (the shared name of its entries), excluding the shared input directory.
    for id in $(ls -1 test_cases | sed -E -n 's/\.(in|out|fin|fout)$//p' | sort -u | grep -vx _shared); do
      # Stage the case's working directory: shared inputs first, then the case's own inputs.
      w=$scratch/$id && mkdir -p "$w"
      if [ -d test_cases/_shared.fin ]; then cp -R test_cases/_shared.fin/. "$w"/; fi
      if [ -d "test_cases/$id.fin" ]; then cp -R "test_cases/$id.fin/." "$w"/; fi
      # Run the model answer with the case's stdin (if any) and capture its stdout.
      in=/dev/null && if [ -f "test_cases/$id.in" ]; then in="$root/test_cases/$id.in"; fi
      (cd "$w" && python3 "$root/model_answers/python/main.py" < "$in" > "$root/$scratch/$id.stdout")
      if [ -d "test_cases/$id.fout" ]; then
        # A file case: the produced file paths (the script arguments) are required and every file must exist
        # before .fout/ is refilled, which protects committed contents; .out is refreshed only when the case has one.
        [ $# -gt 0 ] || { echo "usage: expected.sh <outputFile> ... (test_cases/$id.fout/ needs the produced file paths)" >&2; exit 2; }
        for f in "$@"; do test -f "$w/$f"; done
        find "test_cases/$id.fout" -mindepth 1 -delete
        for f in "$@"; do mkdir -p "test_cases/$id.fout/$(dirname "$f")" && cp "$w/$f" "test_cases/$id.fout/$f"; done
        if [ -f "test_cases/$id.out" ]; then mv "$scratch/$id.stdout" "test_cases/$id.out"; fi
      else
        # A stdout case: the captured stdout is its expectation.
        mv "$scratch/$id.stdout" "test_cases/$id.out"
      fi
    done
    

    将程序写入的所有相对路径都作为脚本参数传入;仅输出到 stdout 的问题不传任何参数(该循环在没有参数时会拒绝替换 .fout/,并会先验证每个文件是否存在;bash -euo pipefail 无论交互式 shell 是什么,都会在首次失败时停止);此配方假定每个文件测试用例写入相同路径,因此路径不同时要按用例调整。对于每个按文件判定的用例,预先创建 test_cases/<id>.fout/:循环会填充已有目录,为其他每个用例写入 .out,并刷新文件用例已有的 .out(如果文件用例也会比较 stdout,则预先创建空的 test_cases/<id>.out)。根据模型答案所用的语言调整命令。对于自定义的 judge.ts,应遵循其自身约定:保留它读取的 .in、.fin/ 和 _shared.fin/ 条目;如果不比较 stdout(例如比较 requiredOutputFilePaths 中列出的文件),则不要创建 .out;切勿仅为产生 stdout 而打印输出文件名之类的标记。

  7. 使用第二个模型答案交叉检查:在 model_answers/ 下用另一种目标语言编写一个独立的模型答案(对于单语言问题,将第二个实现用任意语言写在 <problemDir>/.tmp/cross_check/ 下,使用相同的命令进行判定,并在完成后删除,而不要提交),然后从问题目录使用 exercode-problem CLI 判定每个模型答案。该 CLI 随 @exercode/problem-utils 一同提供,且必须是已声明的仓库依赖项——参见 setup-exercode-course-repository:

    cd <problemDir> && bunx exercode-problem judge model_answers/<languageId>
    

    将必需的判定器参数作为 JSON 第二个参数传入(例如,供 LLM 判定器使用的 '{ "model": "<modelId>" }')。只有当命令以 0 退出、至少打印一行 TEST_CASE_RESULT,且每一行都包含可接受的判定代码 2000 时,运行才算通过(比较规则见 references/problem-format.md)。若出现任何失败,请诊断判定代码和 stderr,并修复原因——输出不匹配意味着题目描述存在歧义或答案错误;只有在预期输出发生变化时才重新生成 .out 文件。

  8. 如果在 templates/ 下提供起始代码,确保它不能解出问题:从问题目录对每个模板目录运行 bunx exercode-problem judge templates/<languageId>(将 templates/_default 判定为 bunx exercode-problem judge templates/_default,将直接放在 templates/ 下的文件判定为 bunx exercode-problem judge templates;这两种情况都要传入目标语言 '{ "language": "<languageId>" }'),确认至少有一个测试用例在实际执行的运行中未被接受——main file not found 结果(判定代码 1201)表示语言参数与模板目录中的任何文件都不匹配,因此应使用目录中文件的语言重新运行;并确认没有任何模板目录原样包含模型答案判定所依据的全部文件(标准问题为源文件,自定义判定器则为所有文件;共享辅助模块可以相同)。

  9. 如果题目描述限制了语法(参见 references/problem-authoring.md 中的制约规则),请在 frontmatter 的 requiredRegExpsInCode / forbiddenRegExpsInCode / forbiddenTextsInCode 中体现这些限制。必需模式必须匹配每个模型答案;禁止模式不得匹配任何内容,也绝不能匹配输入解析辅助代码(Scanner、map、int、split、正则表达式)。这些模式适用于每种目标语言的提交,因此只能使用与语言无关的模式;语言特定的限制(例如 Python 的 range)应仅写在题目描述正文中。

  10. 验证并修复,直至没有问题:

    bunx exercode-problem validate-problem <problemDir>...
    

    修复所有报告的错误并重新运行。重复此过程,直到命令报告成功。也要处理警告,除非它们是有意保留的;如果保留警告,请在最终报告中告知用户原因。

  11. 报告创建的问题目录及其难度,以及任何尚未确定的事项(例如你选择的约束值、有意保留的警告)。

评论

支持 Markdown