关于 generate-judge-problems
用于在仓库中编写 Exercode Judge v2 编程题的技能,指导 Agent 完成题目设计、可复现环境、答案与测试用例编写、判题和验证。
主要功能
- 01指导 Judge v2 题目格式
- 02限定题目在课程内存放
- 03固定运行时与依赖版本
- 04编写答案并生成测试用例
- 05验证判题器和模板
- 06执行题目验证并汇报
适用场景
- 01创建课程专属 Judge 题目
- 02构建可复现的题目仓库
- 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.mdproblem-authoring.mdproblem-format.md详细介绍
生成 judge v2 题目
编写任何文件前,请先阅读以下参考资料:
- references/problem-format.md — judge v2 目录结构和
problem.md前置元数据架构。 - references/problem-authoring.md — 题面撰写规则、难度级别和机器评测限制。
- references/example-problem.md — 一个完整的最简 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 进行报告。
-
确认输入:学习目标或源材料、目标课程、目标编程语言、题目数量和难度范围。将每道题放在
<courseId>/problems/<problemId>/下;绝不要创建全仓库通用题目,也不要引用其他课程的题目。根据输入拟定题目草稿(id、name、overview、objective、difficulty);生成多道题目时,按照 references/problem-authoring.md 中的难度级别从易到难排序。 -
确保题目文件可在仓库中复现。保留现有的 mise 配置和兼容的固定版本。如果不存在
mise.toml、.mise.toml或.tool-versions,则创建.tool-versions;否则保留现有格式。为运行已跟踪的模型答案、模板、判题程序和调试器所需的每个运行时、包管理器和命令行工具指定精确版本。在各语言的标准项目清单中声明其导入的第三方库,并在支持时指定精确的直接依赖版本,同时更新相应的标准锁定文件。对于 mise TOML 配置,运行mise trust --yes,然后运行mise install,之后正常使用已安装的命令。不要依赖全局安装、临时下载或临时依赖环境,也不要添加仅用于编写或验证题目的工具。 -
对于每道题,先在
model_answers/<languageId>/下编写主要模型答案(例如python/main.py)。在编写题面前亲自解题,有助于及早发现歧义。只能使用学习者已经学过的概念;如果未提供已学概念列表,则根据学习目标作出合理假设,并在最终报告中说明。 -
按照 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。 -
在
test_cases/下创建测试输入:至少 1 个example_*测试用例(展示给学习者,并在题面中对应列出入力例/出力例;对于仅输入文件的题目,出力例展示判定的输出文件,见 references/problem-format.md),以及至少 1 个隐藏测试用例,总数至少 4 个。测试输入应具有多样性,并涵盖边界情况(约束的最小值/最大值、允许时的 0 或负数等边界条件)。所有输入都必须不同。读取文件而非 stdin 的程序从<id>.fin/读取;写入文件的程序使用<id>.fout/进行判定(见 references/problem-format.md)。 -
运行模型答案,在本地生成每个
.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 而打印输出文件名之类的标记。 -
使用第二个模型答案交叉检查:在
model_answers/下用另一种目标语言编写一个独立的模型答案(对于单语言问题,将第二个实现用任意语言写在<problemDir>/.tmp/cross_check/下,使用相同的命令进行判定,并在完成后删除,而不要提交),然后从问题目录使用exercode-problemCLI 判定每个模型答案。该 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文件。 -
如果在
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)表示语言参数与模板目录中的任何文件都不匹配,因此应使用目录中文件的语言重新运行;并确认没有任何模板目录原样包含模型答案判定所依据的全部文件(标准问题为源文件,自定义判定器则为所有文件;共享辅助模块可以相同)。 -
如果题目描述限制了语法(参见 references/problem-authoring.md 中的制约规则),请在 frontmatter 的
requiredRegExpsInCode/forbiddenRegExpsInCode/forbiddenTextsInCode中体现这些限制。必需模式必须匹配每个模型答案;禁止模式不得匹配任何内容,也绝不能匹配输入解析辅助代码(Scanner、map、int、split、正则表达式)。这些模式适用于每种目标语言的提交,因此只能使用与语言无关的模式;语言特定的限制(例如 Python 的range)应仅写在题目描述正文中。 -
验证并修复,直至没有问题:
bunx exercode-problem validate-problem <problemDir>...修复所有报告的错误并重新运行。重复此过程,直到命令报告成功。也要处理警告,除非它们是有意保留的;如果保留警告,请在最终报告中告知用户原因。
-
报告创建的问题目录及其难度,以及任何尚未确定的事项(例如你选择的约束值、有意保留的警告)。