mcpField
G
generate-judge-problems

generate-judge-problems

github.com/willbooster開発0213 回インストール2 日前に更新
リポジトリ

generate-judge-problems について

リポジトリ内で Exercode Judge v2 のプログラミング問題を作成するスキルです。問題設計、再現可能な実行環境、解答とテストケースの作成、判定、検証をエージェントに案内します。

主な機能

  1. 01Judge 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 という命名は使用しないでください。

  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 に従い、ステートメント作成ルールは references/problem-authoring.md に従って、problem.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 があれば入力として渡し、標準出力を取得して、.fout/ をプログラムが出力した内容で置き換えます。以下を <problemDir>/.tmp/expected.sh に保存し(インデントされたヒアドキュメントは貼り付けても終了しません)、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
    

    プログラムが書き込む相対パスをすべてスクリプトの引数として渡します。標準出力のみの問題では、引数は渡しません(このループは、引数なしで .fout/ を置き換えようとすると拒否し、まず各ファイルの存在を確認します。また、bash -euo pipefail は、対話型シェルの種類にかかわらず最初の失敗で停止します)。この手順は、すべてのケースで同じパスにファイルが書き込まれることを前提としているため、ケースごとに異なる場合は調整してください。ファイルで判定するケースごとに、あらかじめ test_cases/<id>.fout/ を作成してください。このループは既存のディレクトリを内容で置き換え、その他のすべてのケースでは .out を書き込みます。また、ファイルケースの既存の .out も更新します(ファイルケースの標準出力も比較対象となる場合は、あらかじめ空の test_cases/<id>.out を作成してください)。コマンドはモデル回答の言語に合わせて調整してください。独自の judge.ts を使う場合は、代わりにその仕様に従ってください。.in、.fin/、および _shared.fin/ のうち、読み取るものはそのまま残し、標準出力を比較しない場合(たとえば、requiredOutputFilePaths に記載されたファイルを比較する場合)は .out を作成しないでください。標準出力に何か表示させるためだけに、出力ファイル名のようなマーカーを出力してはいけません。

  7. 2つ目のモデル回答で相互検証します。別の対象言語で独立したモデル回答を作成し、model_answers/ に保存してください(単一言語の問題では、<problemDir>/.tmp/cross_check/ に任意の言語で2つ目の実装を作成し、同じコマンドで判定した後、コミットせずに削除してください)。次に、問題ディレクトリから exercode-problem CLI ですべてのモデル回答を判定します(この CLI は @exercode/problem-utils に同梱されています。このパッケージはリポジトリの依存関係として宣言する必要があります。詳細は setup-exercode-course-repository を参照してください)。

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

    必須のハーネスパラメーターは、JSON 形式の2番目の引数として渡します(たとえば、LLM ジャッジでは '{ "model": "<modelId>" }')。実行が成功するのは、コマンドの終了コードが0で、TEST_CASE_RESULT 行が1行以上出力され、出力されたすべての行の判定コードが許容値の 2000 である場合のみです(比較ルールについては references/problem-format.md を参照してください)。失敗した場合は、判定コードと標準エラー出力を調べ、原因を修正してください。出力の不一致は、問題文が曖昧であるか、回答が誤っていることを意味します。期待される出力が変わった場合に限り、.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>" }' として渡してください)。実際に実行した判定で、少なくとも1つのテストケースが不正解になることを確認してください。main file not found(判定コード 1201)が返された場合は、指定した language パラメーターがテンプレートディレクトリ内のどのファイルにも一致していません。ディレクトリ内のファイルに対応する言語を指定して再実行してください。また、標準問題の場合は解答判定に使われるソースファイルすべて、カスタムジャッジの場合はすべてのファイルが、変更されずにテンプレートディレクトリに含まれていないことを確認してください。共通のヘルパーモジュールは同一でも構いません。

  9. 問題文で構文を制約する場合は(references/problem-authoring.md の制約ルールを参照)、その制約をフロントマターの requiredRegExpsInCode/forbiddenRegExpsInCode/forbiddenTextsInCode にも反映してください。必須パターンはすべてのモデル解答に一致し、禁止パターンはどのモデル解答にも一致しないようにしてください。また、入力解析用ヘルパー(Scanner、map、int、split、正規表現)には決して一致させないでください。パターンはすべての対象言語の提出コードに適用されるため、言語に依存しないパターンのみを使用してください。言語固有の制約(例:Python の range)は、代わりに問題文で説明してください。

  10. 問題の検証が成功するまで、修正と検証を繰り返してください。

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

    報告されたエラーをすべて修正して再実行してください。コマンドが成功を報告するまで繰り返します。警告にも、意図的なものを除いて対処してください。警告を残す場合は、最終報告でその理由を説明してください。

  11. 作成した問題ディレクトリとその難易度、および未決定事項(例:選択した制約値、意図的に残した警告)を報告してください。

コメント