orizu 0.5.19 → 0.5.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/archive-artifacts-cli.d.ts +50 -0
- package/dist/archive-artifacts-cli.d.ts.map +1 -0
- package/dist/artifact-archive.d.ts +48 -0
- package/dist/artifact-archive.d.ts.map +1 -0
- package/dist/artifact-pull.d.ts +22 -0
- package/dist/artifact-pull.d.ts.map +1 -0
- package/dist/banner.d.ts +3 -0
- package/dist/banner.d.ts.map +1 -0
- package/dist/connectors-cli.d.ts +31 -0
- package/dist/connectors-cli.d.ts.map +1 -0
- package/dist/credentials.d.ts +46 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/csv.d.ts +49 -0
- package/dist/csv.d.ts.map +1 -0
- package/dist/dataset-download.d.ts +6 -0
- package/dist/dataset-download.d.ts.map +1 -0
- package/dist/dataset-edit-rows.d.ts +10 -0
- package/dist/dataset-edit-rows.d.ts.map +1 -0
- package/dist/dataset-upload-snapshot.d.ts +13 -0
- package/dist/dataset-upload-snapshot.d.ts.map +1 -0
- package/dist/daytona-slice-rehearsal.d.ts +89 -0
- package/dist/daytona-slice-rehearsal.d.ts.map +1 -0
- package/dist/daytona-slice.d.ts +106 -0
- package/dist/daytona-slice.d.ts.map +1 -0
- package/dist/deploy-key-merge-core.d.ts +121 -0
- package/dist/deploy-key-merge-core.d.ts.map +1 -0
- package/dist/diff-comments-cli.d.ts +12 -0
- package/dist/diff-comments-cli.d.ts.map +1 -0
- package/dist/diff-comments-cli.js +21 -2
- package/dist/egress-allowlist-cli.d.ts +23 -0
- package/dist/egress-allowlist-cli.d.ts.map +1 -0
- package/dist/egress-policy.d.ts +108 -0
- package/dist/egress-policy.d.ts.map +1 -0
- package/dist/error-response.d.ts +9 -0
- package/dist/error-response.d.ts.map +1 -0
- package/dist/file-parser.d.ts +5 -0
- package/dist/file-parser.d.ts.map +1 -0
- package/dist/gepa-engine-dispatch.d.ts +9 -0
- package/dist/gepa-engine-dispatch.d.ts.map +1 -0
- package/dist/gepa-engine-dispatch.js +307 -0
- package/dist/gepa-python-command.d.ts +6 -0
- package/dist/gepa-python-command.d.ts.map +1 -0
- package/dist/gepa-python-command.js +26 -0
- package/dist/gepa-python-paths.d.ts +6 -0
- package/dist/gepa-python-paths.d.ts.map +1 -0
- package/dist/gepa-python-paths.js +42 -0
- package/dist/git-credential.d.ts +69 -0
- package/dist/git-credential.d.ts.map +1 -0
- package/dist/git-recovery-environment.d.ts +31 -0
- package/dist/git-recovery-environment.d.ts.map +1 -0
- package/dist/github-setup.d.ts +122 -0
- package/dist/github-setup.d.ts.map +1 -0
- package/dist/global-flags.d.ts +12 -0
- package/dist/global-flags.d.ts.map +1 -0
- package/dist/help.d.ts +46 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/help.js +93 -25
- package/dist/hosted-boot.d.ts +273 -0
- package/dist/hosted-boot.d.ts.map +1 -0
- package/dist/hosted-bootstrap-rehearsal.d.ts +86 -0
- package/dist/hosted-bootstrap-rehearsal.d.ts.map +1 -0
- package/dist/hosted-bootstrap.d.ts +188 -0
- package/dist/hosted-bootstrap.d.ts.map +1 -0
- package/dist/hosted-broker-server.d.ts +50 -0
- package/dist/hosted-broker-server.d.ts.map +1 -0
- package/dist/hosted-git-http-server.d.ts +36 -0
- package/dist/hosted-git-http-server.d.ts.map +1 -0
- package/dist/hosted-harness-claude.d.ts +195 -0
- package/dist/hosted-harness-claude.d.ts.map +1 -0
- package/dist/hosted-harness-opencode.d.ts +227 -0
- package/dist/hosted-harness-opencode.d.ts.map +1 -0
- package/dist/hosted-harness.d.ts +118 -0
- package/dist/hosted-harness.d.ts.map +1 -0
- package/dist/hosted-harvest.d.ts +68 -0
- package/dist/hosted-harvest.d.ts.map +1 -0
- package/dist/hosted-headless.d.ts +72 -0
- package/dist/hosted-headless.d.ts.map +1 -0
- package/dist/hosted-loop-lifecycle.d.ts +206 -0
- package/dist/hosted-loop-lifecycle.d.ts.map +1 -0
- package/dist/hosted-loop.d.ts +170 -0
- package/dist/hosted-loop.d.ts.map +1 -0
- package/dist/hosted-run-event-sink.d.ts +222 -0
- package/dist/hosted-run-event-sink.d.ts.map +1 -0
- package/dist/hosted-runtime-assets.d.ts +186 -0
- package/dist/hosted-runtime-assets.d.ts.map +1 -0
- package/dist/hosted-session-cli.d.ts +258 -0
- package/dist/hosted-session-cli.d.ts.map +1 -0
- package/dist/hosted-skill-staging.d.ts +73 -0
- package/dist/hosted-skill-staging.d.ts.map +1 -0
- package/dist/http.d.ts +8 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +119 -129
- package/dist/instruction-set-gepa-launch.d.ts +3 -0
- package/dist/instruction-set-gepa-launch.d.ts.map +1 -0
- package/dist/instruction-set-gepa-launch.js +15 -0
- package/dist/instruction-set-loader.d.ts +6 -0
- package/dist/instruction-set-loader.d.ts.map +1 -0
- package/dist/instruction-set-loader.js +59 -0
- package/dist/instruction-set-manifest.d.ts +14 -0
- package/dist/instruction-set-manifest.d.ts.map +1 -0
- package/dist/instruction-set-manifest.js +162 -0
- package/dist/instruction-sets-cli.d.ts +40 -0
- package/dist/instruction-sets-cli.d.ts.map +1 -0
- package/dist/instruction-sets-cli.js +361 -0
- package/dist/json-response.d.ts +4 -0
- package/dist/json-response.d.ts.map +1 -0
- package/dist/jsonl-stream.d.ts +8 -0
- package/dist/jsonl-stream.d.ts.map +1 -0
- package/dist/kill-switch-cli.d.ts +30 -0
- package/dist/kill-switch-cli.d.ts.map +1 -0
- package/dist/loopback-serve.d.ts +25 -0
- package/dist/loopback-serve.d.ts.map +1 -0
- package/dist/manifests-cli.d.ts +22 -0
- package/dist/manifests-cli.d.ts.map +1 -0
- package/dist/markdown-report-input.d.ts +7 -0
- package/dist/markdown-report-input.d.ts.map +1 -0
- package/dist/markdown-report-input.js +15 -1
- package/dist/merge-job.d.ts +155 -0
- package/dist/merge-job.d.ts.map +1 -0
- package/dist/model-configs-cli.d.ts +19 -0
- package/dist/model-configs-cli.d.ts.map +1 -0
- package/dist/model-configs-cli.js +104 -0
- package/dist/optimization-export-cli.d.ts +19 -0
- package/dist/optimization-export-cli.d.ts.map +1 -0
- package/dist/optimizations-list-cli.d.ts +20 -0
- package/dist/optimizations-list-cli.d.ts.map +1 -0
- package/dist/optimizations-promote-cli.d.ts +8 -0
- package/dist/optimizations-promote-cli.d.ts.map +1 -0
- package/dist/optimizations-promote-cli.js +34 -0
- package/dist/option-validation.d.ts +15 -0
- package/dist/option-validation.d.ts.map +1 -0
- package/dist/preview-runtime.d.ts +63 -0
- package/dist/preview-runtime.d.ts.map +1 -0
- package/dist/prompt-draft-push.d.ts +60 -0
- package/dist/prompt-draft-push.d.ts.map +1 -0
- package/dist/prompt-label-cli.d.ts +11 -0
- package/dist/prompt-label-cli.d.ts.map +1 -0
- package/dist/prompt-label-cli.js +29 -0
- package/dist/prompt-length-wire.d.ts +28 -0
- package/dist/prompt-length-wire.d.ts.map +1 -0
- package/dist/prompt-length-wire.js +2 -2
- package/dist/prompt-report-cli.d.ts +10 -0
- package/dist/prompt-report-cli.d.ts.map +1 -0
- package/dist/prompt-report-cli.js +2 -2
- package/dist/prompt-summary-table.d.ts +15 -0
- package/dist/prompt-summary-table.d.ts.map +1 -0
- package/dist/prompt-summary-table.js +6 -0
- package/dist/registration-tag-warning.d.ts +32 -0
- package/dist/registration-tag-warning.d.ts.map +1 -0
- package/dist/report-comments-cli.d.ts +16 -0
- package/dist/report-comments-cli.d.ts.map +1 -0
- package/dist/report-markdown-blankness.d.ts +2 -0
- package/dist/report-markdown-blankness.d.ts.map +1 -0
- package/dist/report-markdown-blankness.js +6 -0
- package/dist/report-replacement-warning.d.ts +17 -0
- package/dist/report-replacement-warning.d.ts.map +1 -0
- package/dist/report-replacement-warning.js +31 -0
- package/dist/repository-backup-core.d.ts +225 -0
- package/dist/repository-backup-core.d.ts.map +1 -0
- package/dist/repository-recovery-manifest.d.ts +83 -0
- package/dist/repository-recovery-manifest.d.ts.map +1 -0
- package/dist/run-event-digest.d.ts +74 -0
- package/dist/run-event-digest.d.ts.map +1 -0
- package/dist/runner-dir-verify.d.ts +66 -0
- package/dist/runner-dir-verify.d.ts.map +1 -0
- package/dist/runner-instruction-set-contract.d.ts +24 -0
- package/dist/runner-instruction-set-contract.d.ts.map +1 -0
- package/dist/runner-instruction-set-contract.js +50 -0
- package/dist/sandbox-hygiene.d.ts +45 -0
- package/dist/sandbox-hygiene.d.ts.map +1 -0
- package/dist/sandbox-provider.d.ts +193 -0
- package/dist/sandbox-provider.d.ts.map +1 -0
- package/dist/scorer-draft-push.d.ts +50 -0
- package/dist/scorer-draft-push.d.ts.map +1 -0
- package/dist/scorers-cli.d.ts +20 -0
- package/dist/scorers-cli.d.ts.map +1 -0
- package/dist/scorers-cli.js +22 -0
- package/dist/scorers-verify-parity-bridge.d.ts +16 -0
- package/dist/scorers-verify-parity-bridge.d.ts.map +1 -0
- package/dist/scorers-verify-parity-bridge.js +90 -0
- package/dist/scorers-verify-parity.d.ts +81 -0
- package/dist/scorers-verify-parity.d.ts.map +1 -0
- package/dist/scorers-verify-parity.js +502 -0
- package/dist/scores-accept-cli.d.ts +21 -0
- package/dist/scores-accept-cli.d.ts.map +1 -0
- package/dist/secret-redaction.d.ts +47 -0
- package/dist/secret-redaction.d.ts.map +1 -0
- package/dist/session-timeline.d.ts +54 -0
- package/dist/session-timeline.d.ts.map +1 -0
- package/dist/setup-prompt.d.ts +5 -0
- package/dist/setup-prompt.d.ts.map +1 -0
- package/dist/skill-installer.d.ts +68 -0
- package/dist/skill-installer.d.ts.map +1 -0
- package/dist/skilled-proposer-launch.d.ts +7 -0
- package/dist/skilled-proposer-launch.d.ts.map +1 -0
- package/dist/skilled-proposer-launch.js +60 -0
- package/dist/smoke.d.ts +102 -0
- package/dist/smoke.d.ts.map +1 -0
- package/dist/start-intent.d.ts +12 -0
- package/dist/start-intent.d.ts.map +1 -0
- package/dist/task-assignment-manifest.d.ts +15 -0
- package/dist/task-assignment-manifest.d.ts.map +1 -0
- package/dist/task-create-error.d.ts +18 -0
- package/dist/task-create-error.d.ts.map +1 -0
- package/dist/team-connectors-cli.d.ts +29 -0
- package/dist/team-connectors-cli.d.ts.map +1 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/vercel-sandbox-provider.d.ts +156 -0
- package/dist/vercel-sandbox-provider.d.ts.map +1 -0
- package/dist/workbench-cli.d.ts +233 -0
- package/dist/workbench-cli.d.ts.map +1 -0
- package/dist/workspace-sync.d.ts +207 -0
- package/dist/workspace-sync.d.ts.map +1 -0
- package/dist/workspace.d.ts +92 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/zip-draft-push.d.ts +55 -0
- package/dist/zip-draft-push.d.ts.map +1 -0
- package/package.json +15 -2
- package/requirements/README.md +138 -0
- package/requirements/skilled-proposer.in +66 -0
- package/requirements/skilled-proposer.lock +265 -0
- package/scripts/build-cli-dist.mjs +30 -0
- package/scripts/ensure-skilled-proposer-venv.mjs +5 -0
- package/scripts/orizu-source-checkout.d.mts +1 -0
- package/scripts/orizu-source-checkout.mjs +10 -0
- package/scripts/refresh-skilled-proposer-lock.mjs +256 -0
- package/scripts/vendor-gepa-python.mjs +187 -4
- package/src/diff-comments-cli.ts +41 -3
- package/src/gepa-engine-dispatch.ts +340 -0
- package/src/gepa-python-command.ts +31 -0
- package/src/gepa-python-paths.ts +56 -0
- package/src/help.ts +95 -25
- package/src/index.ts +131 -166
- package/src/instruction-set-gepa-launch.ts +28 -0
- package/src/instruction-set-loader.ts +37 -0
- package/src/instruction-set-manifest.ts +120 -0
- package/src/instruction-sets-cli.ts +306 -0
- package/src/markdown-report-input.ts +19 -1
- package/src/model-configs-cli.ts +119 -0
- package/src/optimizations-promote-cli.ts +41 -0
- package/src/prompt-label-cli.ts +55 -0
- package/src/prompt-length-wire.ts +1 -1
- package/src/prompt-report-cli.ts +2 -2
- package/src/prompt-summary-table.ts +12 -0
- package/src/report-markdown-blankness.ts +6 -0
- package/src/report-replacement-warning.ts +35 -0
- package/src/runner-instruction-set-contract.ts +64 -0
- package/src/scorers-cli.ts +40 -0
- package/src/scorers-verify-parity-bridge.ts +90 -0
- package/src/scorers-verify-parity.ts +564 -0
- package/src/skilled-proposer-launch.ts +89 -0
- package/src/skilled-proposer-venv-manager.mjs +757 -0
- package/tsconfig.json +2 -0
- package/vendor/gepa-python/LICENSE +7 -0
- package/vendor/gepa-python/manifest.json +107 -0
- package/vendor/gepa-python/src/gepa/__init__.py +22 -0
- package/vendor/gepa-python/src/gepa/adapters/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/adapters/anymaths_adapter/__init__.py +1 -0
- package/vendor/gepa-python/src/gepa/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
- package/vendor/gepa-python/src/gepa/adapters/confidence_adapter/__init__.py +26 -0
- package/vendor/gepa-python/src/gepa/adapters/confidence_adapter/confidence_adapter.py +618 -0
- package/vendor/gepa-python/src/gepa/adapters/confidence_adapter/scoring.py +206 -0
- package/vendor/gepa-python/src/gepa/adapters/default_adapter/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/adapters/default_adapter/default_adapter.py +206 -0
- package/vendor/gepa-python/src/gepa/adapters/dspy_adapter/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/adapters/dspy_adapter/dspy_adapter.py +525 -0
- package/vendor/gepa-python/src/gepa/adapters/dspy_adapter/instruction_proposal.py +500 -0
- package/vendor/gepa-python/src/gepa/adapters/dspy_full_program_adapter/__init__.py +6 -0
- package/vendor/gepa-python/src/gepa/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
- package/vendor/gepa-python/src/gepa/adapters/dspy_full_program_adapter/full_program_adapter.py +269 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/__init__.py +56 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
- package/vendor/gepa-python/src/gepa/adapters/langchain_adapter/__init__.py +11 -0
- package/vendor/gepa-python/src/gepa/adapters/langchain_adapter/langchain_adapter.py +261 -0
- package/vendor/gepa-python/src/gepa/adapters/mcp_adapter/__init__.py +37 -0
- package/vendor/gepa-python/src/gepa/adapters/mcp_adapter/mcp_adapter.py +693 -0
- package/vendor/gepa-python/src/gepa/adapters/mcp_adapter/mcp_client.py +364 -0
- package/vendor/gepa-python/src/gepa/adapters/optimize_anything_adapter/optimize_anything_adapter.py +879 -0
- package/vendor/gepa-python/src/gepa/adapters/terminal_bench_adapter/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
- package/vendor/gepa-python/src/gepa/api.py +468 -0
- package/vendor/gepa-python/src/gepa/core/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/core/adapter.py +227 -0
- package/vendor/gepa-python/src/gepa/core/callbacks.py +551 -0
- package/vendor/gepa-python/src/gepa/core/data_loader.py +74 -0
- package/vendor/gepa-python/src/gepa/core/engine.py +1025 -0
- package/vendor/gepa-python/src/gepa/core/result.py +271 -0
- package/vendor/gepa-python/src/gepa/core/state.py +706 -0
- package/vendor/gepa-python/src/gepa/examples/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/examples/aime.py +24 -0
- package/vendor/gepa-python/src/gepa/examples/anymaths-bench/eval_default.py +111 -0
- package/vendor/gepa-python/src/gepa/examples/anymaths-bench/train_anymaths.py +177 -0
- package/vendor/gepa-python/src/gepa/examples/mcp_adapter/__init__.py +4 -0
- package/vendor/gepa-python/src/gepa/examples/mcp_adapter/mcp_optimization_example.py +456 -0
- package/vendor/gepa-python/src/gepa/examples/rag_adapter/__init__.py +9 -0
- package/vendor/gepa-python/src/gepa/examples/rag_adapter/rag_optimization.py +824 -0
- package/vendor/gepa-python/src/gepa/examples/terminal-bench/train_terminus.py +161 -0
- package/vendor/gepa-python/src/gepa/gepa_utils.py +117 -0
- package/vendor/gepa-python/src/gepa/gskill/__init__.py +1 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/cost_tracker.py +194 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/claude_code.py +1117 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/claude_code_skills.py +613 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/mini_swe_agent.py +440 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/experiment_logger.py +367 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/swe_fitness_fn.py +209 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/swe_harness.py +352 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/train_optimize_anything.py +830 -0
- package/vendor/gepa-python/src/gepa/image.py +104 -0
- package/vendor/gepa-python/src/gepa/lm.py +261 -0
- package/vendor/gepa-python/src/gepa/logging/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/logging/experiment_tracker.py +457 -0
- package/vendor/gepa-python/src/gepa/logging/logger.py +75 -0
- package/vendor/gepa-python/src/gepa/logging/utils.py +130 -0
- package/vendor/gepa-python/src/gepa/optimize_anything.py +1701 -0
- package/vendor/gepa-python/src/gepa/proposer/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/proposer/base.py +53 -0
- package/vendor/gepa-python/src/gepa/proposer/merge.py +403 -0
- package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/base.py +64 -0
- package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/reflection_lm.py +178 -0
- package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/reflective_mutation.py +641 -0
- package/vendor/gepa-python/src/gepa/py.typed +0 -0
- package/vendor/gepa-python/src/gepa/strategies/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/strategies/acceptance.py +66 -0
- package/vendor/gepa-python/src/gepa/strategies/batch_sampler.py +103 -0
- package/vendor/gepa-python/src/gepa/strategies/candidate_selector.py +82 -0
- package/vendor/gepa-python/src/gepa/strategies/component_selector.py +36 -0
- package/vendor/gepa-python/src/gepa/strategies/eval_policy.py +64 -0
- package/vendor/gepa-python/src/gepa/strategies/instruction_proposal.py +153 -0
- package/vendor/gepa-python/src/gepa/strategies/proposal_sampling.py +128 -0
- package/vendor/gepa-python/src/gepa/strategies/proposal_selection.py +84 -0
- package/vendor/gepa-python/src/gepa/utils/__init__.py +63 -0
- package/vendor/gepa-python/src/gepa/utils/code_execution.py +626 -0
- package/vendor/gepa-python/src/gepa/utils/stdio_capture.py +145 -0
- package/vendor/gepa-python/src/gepa/utils/stop_condition.py +230 -0
- package/vendor/gepa-python/src/gepa/visualization.py +359 -0
- package/vendor/orizu-gepa/pyproject.toml +18 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/__init__.py +9 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/__main__.py +5 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/adapter.py +138 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/callbacks.py +666 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/cli.py +7 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/engine.py +269 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/instruction_set_loader.py +52 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/preflight.py +64 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/reflection.py +186 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +721 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/skilled_proposer_bridge.py +381 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/stop_conditions.py +55 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/translator.py +203 -0
- package/vendor/orizu-gepa-python/pyproject.toml +1 -1
- package/vendor/orizu-gepa-python/src/orizu_gepa/cli.py +1 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +52 -7
- package/vendor/orizu-gepa-python/src/orizu_gepa/local_log.py +52 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/optimizer.py +10 -5
- package/vendor/orizu-gepa-python/src/orizu_gepa/reflection.py +323 -68
- package/vendor/orizu-gepa-python/src/orizu_gepa/runner.py +207 -13
- package/vendor/skills/orizu-cli/SKILL.md +3 -1
- package/vendor/skills/orizu-cli/references/cli-reference.md +85 -1
- package/vendor/skills/orizu-cli/references/migrate-existing-gepa-setup.md +303 -0
- package/vendor/skills/orizu-cli/references/optimization-reports.md +1 -1
- package/vendor/skills/orizu-cli/references/optimization-with-gepa.md +26 -1
- package/vendor/skills/orizu-cli/references/prompt-control-plane.md +113 -10
|
@@ -0,0 +1,1117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Evaluate testset with Claude Code.
|
|
3
|
+
|
|
4
|
+
This script:
|
|
5
|
+
1. Reads a config JSON and extracts testset IDs using the exact same data loader
|
|
6
|
+
2. Outputs test IDs to a file
|
|
7
|
+
3. Evaluates problems using Claude Code by:
|
|
8
|
+
- Finding the corresponding Docker image
|
|
9
|
+
- Installing Claude Code inside and caching the image
|
|
10
|
+
- Running `claude -p "task"` inside the container
|
|
11
|
+
- Monitoring completion
|
|
12
|
+
- Running the original harness to check correctness
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import random
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
import time
|
|
22
|
+
import uuid
|
|
23
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
from datetime import datetime
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
import docker
|
|
30
|
+
from datasets import load_dataset
|
|
31
|
+
from dotenv import load_dotenv
|
|
32
|
+
from gskill.swe_harness import SWEHarness
|
|
33
|
+
|
|
34
|
+
# Load environment variables
|
|
35
|
+
load_dotenv()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Setup Docker host for rootless Docker (same as swe_harness.py)
|
|
39
|
+
def setup_docker_host():
|
|
40
|
+
"""Set DOCKER_HOST for rootless Docker if needed."""
|
|
41
|
+
if not os.getenv("DOCKER_HOST"):
|
|
42
|
+
uid = os.getuid()
|
|
43
|
+
xdg_runtime = os.getenv("XDG_RUNTIME_DIR")
|
|
44
|
+
|
|
45
|
+
if xdg_runtime:
|
|
46
|
+
rootless_socket = f"unix://{xdg_runtime}/docker.sock"
|
|
47
|
+
else:
|
|
48
|
+
rootless_socket = f"unix:///run/user/{uid}/docker.sock"
|
|
49
|
+
|
|
50
|
+
# Check if rootless socket exists
|
|
51
|
+
socket_path = rootless_socket.replace("unix://", "")
|
|
52
|
+
if Path(socket_path).exists():
|
|
53
|
+
os.environ["DOCKER_HOST"] = rootless_socket
|
|
54
|
+
print(f"Using rootless Docker: {rootless_socket}")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
setup_docker_host()
|
|
58
|
+
|
|
59
|
+
# Import SWE-smith utilities
|
|
60
|
+
from swebench.harness.constants import (
|
|
61
|
+
DOCKER_USER,
|
|
62
|
+
DOCKER_WORKDIR,
|
|
63
|
+
KEY_INSTANCE_ID,
|
|
64
|
+
)
|
|
65
|
+
from swesmith.profiles import registry
|
|
66
|
+
|
|
67
|
+
# ============================================================================
|
|
68
|
+
# Data Loading (same as train_optimize_anything.py)
|
|
69
|
+
# ============================================================================
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def load_and_split_data(repo: str, train_size: int, val_size: int, test_size: int, seed: int = 42):
|
|
73
|
+
"""
|
|
74
|
+
Load data directly from HuggingFace and split into train/val/test.
|
|
75
|
+
|
|
76
|
+
This is the EXACT same function from train_optimize_anything.py to ensure
|
|
77
|
+
identical data splits.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
repo: Repository name to filter (e.g., "pygments__pygments" or "swesmith/pygments__pygments")
|
|
81
|
+
train_size: Number of training examples
|
|
82
|
+
val_size: Number of validation examples
|
|
83
|
+
test_size: Number of test examples
|
|
84
|
+
seed: Random seed for shuffling
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
(train_data, val_data, test_data) tuple
|
|
88
|
+
"""
|
|
89
|
+
total_needed = train_size + val_size + test_size
|
|
90
|
+
print(f"Loading {repo} data from HuggingFace SWE-smith (need {total_needed})...")
|
|
91
|
+
ds = load_dataset("SWE-bench/SWE-smith", split="train")
|
|
92
|
+
|
|
93
|
+
# Normalize repo name for matching
|
|
94
|
+
# Handle both "pygments__pygments" and "swesmith/pygments__pygments"
|
|
95
|
+
repo_pattern = repo if "/" in repo else f"swesmith/{repo}"
|
|
96
|
+
|
|
97
|
+
all_data = []
|
|
98
|
+
for item in ds:
|
|
99
|
+
repo_name = item.get("repo", "")
|
|
100
|
+
if repo_pattern in repo_name:
|
|
101
|
+
task_dict = dict(item)
|
|
102
|
+
all_data.append(task_dict)
|
|
103
|
+
if len(all_data) >= total_needed:
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
print(f"Loaded {len(all_data)} {repo} examples")
|
|
107
|
+
|
|
108
|
+
# Shuffle with seed
|
|
109
|
+
random.seed(seed)
|
|
110
|
+
random.shuffle(all_data)
|
|
111
|
+
|
|
112
|
+
# Calculate split sizes
|
|
113
|
+
total_needed = train_size + val_size + test_size
|
|
114
|
+
if len(all_data) < total_needed:
|
|
115
|
+
print(f"WARNING: Only {len(all_data)} examples available, need {total_needed}")
|
|
116
|
+
# Scale down proportionally
|
|
117
|
+
ratio = len(all_data) / total_needed
|
|
118
|
+
train_size = int(train_size * ratio)
|
|
119
|
+
val_size = int(val_size * ratio)
|
|
120
|
+
test_size = len(all_data) - train_size - val_size
|
|
121
|
+
|
|
122
|
+
# Split
|
|
123
|
+
train_data = all_data[:train_size]
|
|
124
|
+
val_data = all_data[train_size : train_size + val_size]
|
|
125
|
+
test_data = all_data[train_size + val_size : train_size + val_size + test_size]
|
|
126
|
+
|
|
127
|
+
test_data_with_ps = []
|
|
128
|
+
skipped_empty_ps = 0
|
|
129
|
+
for task in test_data:
|
|
130
|
+
ps = task.get("problem_statement", "")
|
|
131
|
+
if not ps or not ps.strip():
|
|
132
|
+
skipped_empty_ps += 1
|
|
133
|
+
else:
|
|
134
|
+
test_data_with_ps.append(task)
|
|
135
|
+
|
|
136
|
+
print(f"Skipped {skipped_empty_ps} tasks with empty problem_statement")
|
|
137
|
+
print(f"Split: {len(train_data)} train, {len(val_data)} val, {len(test_data)} test")
|
|
138
|
+
|
|
139
|
+
return train_data, val_data, test_data_with_ps
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def load_testset_from_config(config_path: str) -> tuple[list[dict], dict]:
|
|
143
|
+
"""
|
|
144
|
+
Load testset using config JSON parameters.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
config_path: Path to config.json file
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
(test_data, config) tuple
|
|
151
|
+
"""
|
|
152
|
+
with open(config_path) as f:
|
|
153
|
+
config = json.load(f)
|
|
154
|
+
|
|
155
|
+
repo = config["repo"]
|
|
156
|
+
train_size = config["train_size"]
|
|
157
|
+
val_size = config["val_size"]
|
|
158
|
+
test_size = config["test_size"]
|
|
159
|
+
seed = config.get("seed", 42)
|
|
160
|
+
|
|
161
|
+
print(f"Loading data with config: repo={repo}, seed={seed}")
|
|
162
|
+
print(f" train_size={train_size}, val_size={val_size}, test_size={test_size}")
|
|
163
|
+
|
|
164
|
+
_, _, test_data = load_and_split_data(repo, train_size, val_size, test_size, seed)
|
|
165
|
+
|
|
166
|
+
print(f"Loaded {len(test_data)} test tasks (with valid problem_statement)")
|
|
167
|
+
|
|
168
|
+
return test_data, config
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def save_test_ids(test_data: list[dict], output_path: str):
|
|
172
|
+
"""Save test instance IDs to a file."""
|
|
173
|
+
ids = [task["instance_id"] for task in test_data]
|
|
174
|
+
with open(output_path, "w") as f:
|
|
175
|
+
for instance_id in ids:
|
|
176
|
+
f.write(f"{instance_id}\n")
|
|
177
|
+
print(f"Saved {len(ids)} test IDs to {output_path}")
|
|
178
|
+
return ids
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def load_test_ids(ids_file: str) -> list[str]:
|
|
182
|
+
"""Load test IDs from a file."""
|
|
183
|
+
with open(ids_file) as f:
|
|
184
|
+
return [line.strip() for line in f if line.strip()]
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def load_testset_by_ids(test_ids: list[str], repo: str) -> list[dict]:
|
|
188
|
+
"""
|
|
189
|
+
Load tasks from HuggingFace dataset by their instance IDs.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
test_ids: List of instance IDs to load
|
|
193
|
+
repo: Repository name for filtering
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
List of task dicts
|
|
197
|
+
"""
|
|
198
|
+
print(f"Loading {len(test_ids)} tasks by ID from HuggingFace...")
|
|
199
|
+
ds = load_dataset("SWE-bench/SWE-smith", split="train")
|
|
200
|
+
|
|
201
|
+
# Normalize repo name
|
|
202
|
+
|
|
203
|
+
# Create a set for fast lookup
|
|
204
|
+
id_set = set(test_ids)
|
|
205
|
+
|
|
206
|
+
tasks = []
|
|
207
|
+
skipped_empty_ps = []
|
|
208
|
+
for item in ds:
|
|
209
|
+
if item.get("instance_id") in id_set:
|
|
210
|
+
task_dict = dict(item)
|
|
211
|
+
# Skip tasks with empty problem_statement - they can't be solved
|
|
212
|
+
ps = task_dict.get("problem_statement", "")
|
|
213
|
+
if not ps or not ps.strip():
|
|
214
|
+
skipped_empty_ps.append(task_dict["instance_id"])
|
|
215
|
+
continue # Skip this task
|
|
216
|
+
tasks.append(task_dict)
|
|
217
|
+
if len(tasks) >= len(test_ids):
|
|
218
|
+
break
|
|
219
|
+
|
|
220
|
+
if skipped_empty_ps:
|
|
221
|
+
print(f" Skipped {len(skipped_empty_ps)} tasks with empty problem_statement, ids: {skipped_empty_ps}")
|
|
222
|
+
|
|
223
|
+
print(
|
|
224
|
+
f"Loaded {len(tasks)} tasks with valid problem_statement (requested {len(test_ids)}, skipped {len(skipped_empty_ps)} empty)"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Reorder to match input order
|
|
228
|
+
id_to_task = {t["instance_id"]: t for t in tasks}
|
|
229
|
+
ordered_tasks = [id_to_task[tid] for tid in test_ids if tid in id_to_task]
|
|
230
|
+
|
|
231
|
+
return ordered_tasks
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
# ============================================================================
|
|
235
|
+
# Claude Code Docker Image Management
|
|
236
|
+
# ============================================================================
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def get_base_image_name(task: dict) -> str:
|
|
240
|
+
"""Get the base Docker image name for a task.
|
|
241
|
+
|
|
242
|
+
Uses RepoProfile.image_name (from swesmith registry) instead of
|
|
243
|
+
task["image_name"] because the locally-built images use a different
|
|
244
|
+
org name (swebench/) than the HuggingFace dataset (jyangballin/).
|
|
245
|
+
"""
|
|
246
|
+
# Always use RepoProfile to get the correct local image name
|
|
247
|
+
rp = registry.get_from_inst(task)
|
|
248
|
+
return rp.image_name
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def get_claude_code_image_name(base_image: str) -> str:
|
|
252
|
+
"""Get the Claude Code enhanced image name."""
|
|
253
|
+
# Add -claude-code suffix
|
|
254
|
+
return f"{base_image}-claude-code"
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def check_image_exists(image_name: str) -> bool:
|
|
258
|
+
"""Check if a Docker image exists locally."""
|
|
259
|
+
try:
|
|
260
|
+
result = subprocess.run(["docker", "image", "inspect", image_name], capture_output=True, check=False)
|
|
261
|
+
return result.returncode == 0
|
|
262
|
+
except Exception:
|
|
263
|
+
return False
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def install_claude_code_in_image(base_image: str, claude_code_image: str) -> bool:
|
|
267
|
+
"""
|
|
268
|
+
Create a new Docker image with Claude Code installed.
|
|
269
|
+
|
|
270
|
+
This creates a cached image so Claude Code only needs to be installed once
|
|
271
|
+
per base image. Uses the locally-built image (from swesmith.build_repo.create_images)
|
|
272
|
+
which has proper git branches.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
base_image: The original SWE-smith image (locally built)
|
|
276
|
+
claude_code_image: Name for the new image with Claude Code
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
True if successful, False otherwise
|
|
280
|
+
"""
|
|
281
|
+
print(f"Installing Claude Code into image: {base_image} -> {claude_code_image}")
|
|
282
|
+
|
|
283
|
+
# Check if base image exists locally - DO NOT PULL (would overwrite local build)
|
|
284
|
+
if not check_image_exists(base_image):
|
|
285
|
+
print(f" ERROR: Base image {base_image} not found locally!")
|
|
286
|
+
print(" Please build it first with:")
|
|
287
|
+
print(
|
|
288
|
+
" DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock python -m swesmith.build_repo.create_images --repos <repo> --force -y"
|
|
289
|
+
)
|
|
290
|
+
return False
|
|
291
|
+
|
|
292
|
+
print(f" Using locally-built base image: {base_image}")
|
|
293
|
+
|
|
294
|
+
# Create Dockerfile for Claude Code installation
|
|
295
|
+
# Note: We install Node.js 20 and Claude Code globally
|
|
296
|
+
dockerfile_content = f"""FROM {base_image}
|
|
297
|
+
|
|
298
|
+
# Switch to root to install packages
|
|
299
|
+
USER root
|
|
300
|
+
|
|
301
|
+
# Install dependencies and Node.js 20 (required for Claude Code)
|
|
302
|
+
RUN apt-get update && \\
|
|
303
|
+
apt-get install -y curl gnupg ca-certificates && \\
|
|
304
|
+
mkdir -p /etc/apt/keyrings && \\
|
|
305
|
+
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \\
|
|
306
|
+
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \\
|
|
307
|
+
apt-get update && \\
|
|
308
|
+
apt-get install -y nodejs && \\
|
|
309
|
+
apt-get clean && rm -rf /var/lib/apt/lists/*
|
|
310
|
+
|
|
311
|
+
# Install Claude Code globally
|
|
312
|
+
RUN npm install -g @anthropic-ai/claude-code
|
|
313
|
+
|
|
314
|
+
# Switch back to non-root user
|
|
315
|
+
USER {DOCKER_USER}
|
|
316
|
+
|
|
317
|
+
# Verify installation
|
|
318
|
+
RUN claude --version || echo "Claude Code installed"
|
|
319
|
+
"""
|
|
320
|
+
|
|
321
|
+
# Write Dockerfile
|
|
322
|
+
tmp_dir = Path(f"/tmp/claude_code_docker_{uuid.uuid4().hex[:8]}")
|
|
323
|
+
tmp_dir.mkdir(parents=True, exist_ok=True)
|
|
324
|
+
dockerfile_path = tmp_dir / "Dockerfile"
|
|
325
|
+
dockerfile_path.write_text(dockerfile_content)
|
|
326
|
+
|
|
327
|
+
try:
|
|
328
|
+
# Build the image
|
|
329
|
+
print(" Building Claude Code image...")
|
|
330
|
+
result = subprocess.run(
|
|
331
|
+
["docker", "build", "-t", claude_code_image, "-f", str(dockerfile_path), str(tmp_dir)],
|
|
332
|
+
capture_output=True,
|
|
333
|
+
text=True,
|
|
334
|
+
timeout=600, # 10 minute timeout for build
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
if result.returncode != 0:
|
|
338
|
+
print(f" ERROR building image: {result.stderr}")
|
|
339
|
+
print(f" STDOUT: {result.stdout}")
|
|
340
|
+
return False
|
|
341
|
+
|
|
342
|
+
print(f" Successfully created image: {claude_code_image}")
|
|
343
|
+
return True
|
|
344
|
+
|
|
345
|
+
except subprocess.TimeoutExpired:
|
|
346
|
+
print(" ERROR: Docker build timed out after 600 seconds")
|
|
347
|
+
return False
|
|
348
|
+
|
|
349
|
+
finally:
|
|
350
|
+
# Cleanup
|
|
351
|
+
import shutil
|
|
352
|
+
|
|
353
|
+
shutil.rmtree(tmp_dir, ignore_errors=True)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def ensure_claude_code_image(task: dict) -> str:
|
|
357
|
+
"""
|
|
358
|
+
Ensure Claude Code image exists, creating it if necessary.
|
|
359
|
+
|
|
360
|
+
Uses the locally-built base image (from swesmith.build_repo.create_images).
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
task: Task instance dict
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
Name of the Claude Code enabled image
|
|
367
|
+
"""
|
|
368
|
+
base_image = get_base_image_name(task)
|
|
369
|
+
claude_image = get_claude_code_image_name(base_image)
|
|
370
|
+
|
|
371
|
+
if check_image_exists(claude_image):
|
|
372
|
+
print(f" Claude Code image already exists: {claude_image}")
|
|
373
|
+
return claude_image
|
|
374
|
+
|
|
375
|
+
# Create the image from locally-built base
|
|
376
|
+
if not install_claude_code_in_image(base_image, claude_image):
|
|
377
|
+
raise RuntimeError(f"Failed to create Claude Code image from {base_image}")
|
|
378
|
+
|
|
379
|
+
return claude_image
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
# ============================================================================
|
|
383
|
+
# Claude Code Execution
|
|
384
|
+
# ============================================================================
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
@dataclass
|
|
388
|
+
class ClaudeCodeResult:
|
|
389
|
+
"""Result from running Claude Code on a task."""
|
|
390
|
+
|
|
391
|
+
instance_id: str
|
|
392
|
+
success: bool
|
|
393
|
+
patch: str
|
|
394
|
+
output: str
|
|
395
|
+
duration_seconds: float
|
|
396
|
+
error: str | None = None
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def run_claude_code_in_container(
|
|
400
|
+
task: dict,
|
|
401
|
+
container: docker.models.containers.Container,
|
|
402
|
+
timeout: int = 1800, # 30 minutes default
|
|
403
|
+
poll_interval: int = 10,
|
|
404
|
+
skills: str | None = None, # Optional skills to copy as CLAUDE.md
|
|
405
|
+
) -> ClaudeCodeResult:
|
|
406
|
+
"""
|
|
407
|
+
Run Claude Code inside a container and monitor completion.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
task: Task instance dict
|
|
411
|
+
container: Running Docker container
|
|
412
|
+
timeout: Maximum time to wait (seconds)
|
|
413
|
+
poll_interval: How often to check for completion (seconds)
|
|
414
|
+
skills: Optional skills content to copy as CLAUDE.md in /testbed
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
ClaudeCodeResult with patch and status
|
|
418
|
+
"""
|
|
419
|
+
from swebench.harness.docker_utils import copy_to_container
|
|
420
|
+
|
|
421
|
+
instance_id = task[KEY_INSTANCE_ID]
|
|
422
|
+
problem_statement = task.get("problem_statement", "")
|
|
423
|
+
|
|
424
|
+
# Verify we have a problem statement
|
|
425
|
+
if not problem_statement or not problem_statement.strip():
|
|
426
|
+
return ClaudeCodeResult(
|
|
427
|
+
instance_id=instance_id,
|
|
428
|
+
success=False,
|
|
429
|
+
patch="",
|
|
430
|
+
output="",
|
|
431
|
+
duration_seconds=0,
|
|
432
|
+
error="Task has no problem statement (dataset issue)",
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
start_time = time.time()
|
|
436
|
+
|
|
437
|
+
# Get ANTHROPIC_API_KEY from environment
|
|
438
|
+
api_key = os.environ.get("ANTHROPIC_API_KEY", "")
|
|
439
|
+
if not api_key:
|
|
440
|
+
return ClaudeCodeResult(
|
|
441
|
+
instance_id=instance_id,
|
|
442
|
+
success=False,
|
|
443
|
+
patch="",
|
|
444
|
+
output="",
|
|
445
|
+
duration_seconds=0,
|
|
446
|
+
error="ANTHROPIC_API_KEY not set",
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
# Write problem statement to a temp file and copy to container
|
|
450
|
+
# This avoids shell escaping issues
|
|
451
|
+
problem_file_local = Path(f"/tmp/problem_{uuid.uuid4().hex[:8]}.txt")
|
|
452
|
+
problem_file_container = Path("/tmp/problem_statement.txt")
|
|
453
|
+
|
|
454
|
+
try:
|
|
455
|
+
problem_file_local.write_text(problem_statement)
|
|
456
|
+
local_size = problem_file_local.stat().st_size
|
|
457
|
+
if local_size == 0:
|
|
458
|
+
print(f" ERROR: Local problem file is empty for {instance_id}!")
|
|
459
|
+
|
|
460
|
+
copy_to_container(container, problem_file_local, problem_file_container)
|
|
461
|
+
# Sync filesystem and ensure file is readable
|
|
462
|
+
container.exec_run("sync", user="root")
|
|
463
|
+
container.exec_run(f"chmod 644 {problem_file_container}", user="root")
|
|
464
|
+
|
|
465
|
+
# Verify the copy worked with retries
|
|
466
|
+
for attempt in range(3):
|
|
467
|
+
# Use shell to handle redirection, and extract just the number
|
|
468
|
+
verify_result = container.exec_run(["sh", "-c", f"cat {problem_file_container} | wc -c"], user="root")
|
|
469
|
+
container_size = verify_result.output.decode().strip() if verify_result.output else "0"
|
|
470
|
+
|
|
471
|
+
if container_size != "0" and verify_result.exit_code == 0:
|
|
472
|
+
break
|
|
473
|
+
|
|
474
|
+
# File is missing or empty - wait and retry
|
|
475
|
+
if attempt < 2:
|
|
476
|
+
time.sleep(0.5)
|
|
477
|
+
copy_to_container(container, problem_file_local, problem_file_container)
|
|
478
|
+
container.exec_run("sync", user="root")
|
|
479
|
+
container.exec_run(f"chmod 644 {problem_file_container}", user="root")
|
|
480
|
+
else:
|
|
481
|
+
print(
|
|
482
|
+
f" WARNING: Problem file copy may have failed for {instance_id} (local={local_size}, container={container_size})"
|
|
483
|
+
)
|
|
484
|
+
finally:
|
|
485
|
+
problem_file_local.unlink(missing_ok=True)
|
|
486
|
+
|
|
487
|
+
# Copy skills as CLAUDE.md if provided
|
|
488
|
+
if skills:
|
|
489
|
+
skills_file_local = Path(f"/tmp/skills_{uuid.uuid4().hex[:8]}.md")
|
|
490
|
+
skills_file_container = Path("/testbed/CLAUDE.md")
|
|
491
|
+
try:
|
|
492
|
+
skills_file_local.write_text(skills)
|
|
493
|
+
copy_to_container(container, skills_file_local, skills_file_container)
|
|
494
|
+
container.exec_run("sync", user="root")
|
|
495
|
+
container.exec_run(f"chmod 644 {skills_file_container}", user="root")
|
|
496
|
+
print(f" Copied skills to CLAUDE.md ({len(skills)} chars)")
|
|
497
|
+
finally:
|
|
498
|
+
skills_file_local.unlink(missing_ok=True)
|
|
499
|
+
|
|
500
|
+
# Run Claude Code with the problem statement from file
|
|
501
|
+
# Use -p flag to pass the prompt (print mode - non-interactive)
|
|
502
|
+
# Note: --dangerously-skip-permissions cannot be used as root, so we create
|
|
503
|
+
# a non-root user and run Claude Code as that user
|
|
504
|
+
# Default model is sonnet (Claude Sonnet 4.5)
|
|
505
|
+
model = os.environ.get("CLAUDE_CODE_MODEL", "sonnet")
|
|
506
|
+
|
|
507
|
+
# Create a runner script to avoid shell escaping issues
|
|
508
|
+
# Use stdin to pass the prompt - avoids any escaping problems with special characters
|
|
509
|
+
runner_script = f"""#!/bin/bash
|
|
510
|
+
|
|
511
|
+
# Create non-root user if doesn't exist
|
|
512
|
+
id -u claude_user >/dev/null 2>&1 || useradd -m -s /bin/bash claude_user
|
|
513
|
+
|
|
514
|
+
# Setup permissions
|
|
515
|
+
chown -R claude_user:claude_user {DOCKER_WORKDIR} 2>/dev/null || true
|
|
516
|
+
|
|
517
|
+
# Copy problem statement
|
|
518
|
+
cp {problem_file_container} /home/claude_user/problem.txt
|
|
519
|
+
chown claude_user:claude_user /home/claude_user/problem.txt
|
|
520
|
+
|
|
521
|
+
# Verify problem file exists and has content
|
|
522
|
+
if [ ! -s /home/claude_user/problem.txt ]; then
|
|
523
|
+
echo "ERROR: Problem statement file is empty or missing"
|
|
524
|
+
echo "Source file status:"
|
|
525
|
+
ls -la {problem_file_container} 2>&1 || echo "Source not found"
|
|
526
|
+
exit 1
|
|
527
|
+
fi
|
|
528
|
+
|
|
529
|
+
# Create a wrapper script for claude_user that reads from files
|
|
530
|
+
cat > /home/claude_user/run_claude.sh << 'INNEREOF'
|
|
531
|
+
#!/bin/bash
|
|
532
|
+
cd {DOCKER_WORKDIR}
|
|
533
|
+
export ANTHROPIC_API_KEY=$(cat /home/claude_user/.anthropic_key)
|
|
534
|
+
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
|
|
535
|
+
cat /home/claude_user/problem.txt | claude --print --model {model} --dangerously-skip-permissions
|
|
536
|
+
INNEREOF
|
|
537
|
+
|
|
538
|
+
# Write API key to file (after creating wrapper to avoid key in heredoc)
|
|
539
|
+
# Use single quotes and escape any embedded single quotes
|
|
540
|
+
printf '%s' '{api_key}' > /home/claude_user/.anthropic_key
|
|
541
|
+
chmod 600 /home/claude_user/.anthropic_key
|
|
542
|
+
chown claude_user:claude_user /home/claude_user/.anthropic_key
|
|
543
|
+
chmod +x /home/claude_user/run_claude.sh
|
|
544
|
+
chown claude_user:claude_user /home/claude_user/run_claude.sh
|
|
545
|
+
|
|
546
|
+
# Run as claude_user
|
|
547
|
+
su - claude_user -c "/home/claude_user/run_claude.sh"
|
|
548
|
+
"""
|
|
549
|
+
|
|
550
|
+
# Write runner script to container
|
|
551
|
+
runner_script_path = "/tmp/run_claude.sh"
|
|
552
|
+
runner_script_local = Path(f"/tmp/run_claude_{uuid.uuid4().hex[:8]}.sh")
|
|
553
|
+
try:
|
|
554
|
+
runner_script_local.write_text(runner_script)
|
|
555
|
+
copy_to_container(container, runner_script_local, Path(runner_script_path))
|
|
556
|
+
container.exec_run(f"chmod +x {runner_script_path}", user="root")
|
|
557
|
+
finally:
|
|
558
|
+
runner_script_local.unlink(missing_ok=True)
|
|
559
|
+
|
|
560
|
+
claude_cmd = f"bash {runner_script_path}"
|
|
561
|
+
|
|
562
|
+
print(f" Running Claude Code for {instance_id}...")
|
|
563
|
+
|
|
564
|
+
# Execute Claude Code with timeout
|
|
565
|
+
# Note: Docker SDK doesn't support timeout directly on exec_run
|
|
566
|
+
# We'll use a background thread approach
|
|
567
|
+
import threading
|
|
568
|
+
|
|
569
|
+
result_holder = {"output": None, "exit_code": None}
|
|
570
|
+
|
|
571
|
+
def run_exec():
|
|
572
|
+
try:
|
|
573
|
+
exec_result = container.exec_run(
|
|
574
|
+
["bash", "-c", claude_cmd],
|
|
575
|
+
workdir=DOCKER_WORKDIR,
|
|
576
|
+
user=DOCKER_USER,
|
|
577
|
+
demux=True,
|
|
578
|
+
stream=False,
|
|
579
|
+
)
|
|
580
|
+
stdout, stderr = exec_result.output if exec_result.output else (b"", b"")
|
|
581
|
+
result_holder["output"] = (stdout or b"").decode() + (stderr or b"").decode()
|
|
582
|
+
result_holder["exit_code"] = exec_result.exit_code
|
|
583
|
+
except Exception as e:
|
|
584
|
+
result_holder["output"] = f"Execution error: {e!s}"
|
|
585
|
+
result_holder["exit_code"] = -1
|
|
586
|
+
|
|
587
|
+
exec_thread = threading.Thread(target=run_exec)
|
|
588
|
+
exec_thread.start()
|
|
589
|
+
exec_thread.join(timeout=timeout)
|
|
590
|
+
|
|
591
|
+
duration = time.time() - start_time
|
|
592
|
+
|
|
593
|
+
if exec_thread.is_alive():
|
|
594
|
+
# Timed out - try to kill the claude process
|
|
595
|
+
container.exec_run("pkill -f claude", user="root")
|
|
596
|
+
exec_thread.join(timeout=10) # Give it 10 more seconds to clean up
|
|
597
|
+
|
|
598
|
+
return ClaudeCodeResult(
|
|
599
|
+
instance_id=instance_id,
|
|
600
|
+
success=False,
|
|
601
|
+
patch="",
|
|
602
|
+
output=result_holder.get("output", "Timeout - no output"),
|
|
603
|
+
duration_seconds=duration,
|
|
604
|
+
error=f"Timeout after {timeout} seconds",
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
output = result_holder.get("output", "")
|
|
608
|
+
|
|
609
|
+
# Get the patch (git diff)
|
|
610
|
+
# First, add the workdir to git's safe.directory (needed because ownership changed to claude_user)
|
|
611
|
+
container.exec_run(
|
|
612
|
+
f"git config --global --add safe.directory {DOCKER_WORKDIR}",
|
|
613
|
+
user=DOCKER_USER,
|
|
614
|
+
)
|
|
615
|
+
diff_result = container.exec_run(
|
|
616
|
+
"git diff",
|
|
617
|
+
workdir=DOCKER_WORKDIR,
|
|
618
|
+
user=DOCKER_USER,
|
|
619
|
+
)
|
|
620
|
+
patch = diff_result.output.decode() if diff_result.output else ""
|
|
621
|
+
|
|
622
|
+
print(f" Claude Code finished in {duration:.1f}s, patch size: {len(patch)} chars")
|
|
623
|
+
|
|
624
|
+
return ClaudeCodeResult(
|
|
625
|
+
instance_id=instance_id,
|
|
626
|
+
success=len(patch.strip()) > 0,
|
|
627
|
+
patch=patch,
|
|
628
|
+
output=output,
|
|
629
|
+
duration_seconds=duration,
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
# ============================================================================
|
|
634
|
+
# Verification (using existing SWEHarness - same as train_optimize_anything)
|
|
635
|
+
# ============================================================================
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def verify_patch_with_harness(task: dict, patch: str, timeout: int = 300) -> tuple[bool, str]:
|
|
639
|
+
"""
|
|
640
|
+
Verify a patch using the same SWEHarness as train_optimize_anything.
|
|
641
|
+
|
|
642
|
+
This ensures identical verification logic.
|
|
643
|
+
|
|
644
|
+
Args:
|
|
645
|
+
task: Task instance dict
|
|
646
|
+
patch: The git diff patch to test
|
|
647
|
+
timeout: Test timeout in seconds
|
|
648
|
+
|
|
649
|
+
Returns:
|
|
650
|
+
(passed, test_output) tuple
|
|
651
|
+
"""
|
|
652
|
+
harness = SWEHarness()
|
|
653
|
+
|
|
654
|
+
try:
|
|
655
|
+
# Setup the task (this creates the verification container)
|
|
656
|
+
harness.setup_task(task)
|
|
657
|
+
|
|
658
|
+
# Verify using the exact same logic as swe_fitness_fn.py
|
|
659
|
+
has_patch = len(patch.strip()) > 0
|
|
660
|
+
passed = False
|
|
661
|
+
test_output = ""
|
|
662
|
+
|
|
663
|
+
if not has_patch:
|
|
664
|
+
passed = False
|
|
665
|
+
test_output = "No patch to test."
|
|
666
|
+
else:
|
|
667
|
+
# First test FAIL_TO_PASS only
|
|
668
|
+
f2p_passed, f2p_output = harness.verify_with_patch(patch, f2p_only=True, timeout=timeout)
|
|
669
|
+
test_output = f"=== FAIL_TO_PASS TESTS ===\n{f2p_output}"
|
|
670
|
+
|
|
671
|
+
if not f2p_passed:
|
|
672
|
+
passed = False
|
|
673
|
+
else:
|
|
674
|
+
# PASS_TO_PASS Tests (Regression Check) - run full test suite
|
|
675
|
+
pass_to_pass = task.get("PASS_TO_PASS", [])
|
|
676
|
+
|
|
677
|
+
if pass_to_pass:
|
|
678
|
+
# Run full test (includes both f2p and p2p)
|
|
679
|
+
p2p_passed, p2p_output = harness.verify_with_patch(patch, f2p_only=False, timeout=timeout)
|
|
680
|
+
test_output += f"\n\n=== FULL TEST SUITE ===\n{p2p_output}"
|
|
681
|
+
|
|
682
|
+
if not p2p_passed:
|
|
683
|
+
passed = False
|
|
684
|
+
else:
|
|
685
|
+
passed = True
|
|
686
|
+
else:
|
|
687
|
+
passed = True
|
|
688
|
+
|
|
689
|
+
return passed, test_output
|
|
690
|
+
|
|
691
|
+
except Exception as e:
|
|
692
|
+
import traceback
|
|
693
|
+
|
|
694
|
+
return False, f"Verification error: {e}\n{traceback.format_exc()}"
|
|
695
|
+
|
|
696
|
+
finally:
|
|
697
|
+
harness.cleanup()
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
# ============================================================================
|
|
701
|
+
# Main Evaluation Loop
|
|
702
|
+
# ============================================================================
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def evaluate_single_task(
|
|
706
|
+
task: dict,
|
|
707
|
+
claude_image: str,
|
|
708
|
+
timeout: int = 1800,
|
|
709
|
+
verify_timeout: int = 300,
|
|
710
|
+
skills: str | None = None,
|
|
711
|
+
) -> dict[str, Any]:
|
|
712
|
+
"""
|
|
713
|
+
Evaluate a single task with Claude Code.
|
|
714
|
+
|
|
715
|
+
Args:
|
|
716
|
+
task: Task instance dict
|
|
717
|
+
claude_image: Name of Claude Code enabled Docker image
|
|
718
|
+
timeout: Timeout for Claude Code execution
|
|
719
|
+
verify_timeout: Timeout for test verification
|
|
720
|
+
skills: Optional skills content to copy as CLAUDE.md
|
|
721
|
+
|
|
722
|
+
Returns:
|
|
723
|
+
Result dict with all metrics
|
|
724
|
+
"""
|
|
725
|
+
instance_id = task[KEY_INSTANCE_ID]
|
|
726
|
+
container = None
|
|
727
|
+
|
|
728
|
+
# Create Docker client with retry for connection issues
|
|
729
|
+
try:
|
|
730
|
+
client = docker.from_env()
|
|
731
|
+
client.ping() # Test connection
|
|
732
|
+
except Exception as e:
|
|
733
|
+
return {
|
|
734
|
+
"instance_id": instance_id,
|
|
735
|
+
"claude_success": False,
|
|
736
|
+
"test_passed": False,
|
|
737
|
+
"patch": "",
|
|
738
|
+
"claude_output": "",
|
|
739
|
+
"test_output": "",
|
|
740
|
+
"duration_seconds": 0,
|
|
741
|
+
"error": f"Docker connection failed: {e!s}",
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
try:
|
|
745
|
+
# Create container from Claude Code image
|
|
746
|
+
container_name = f"claude-code-{instance_id}.{uuid.uuid4().hex[:8]}"
|
|
747
|
+
container = client.containers.create(
|
|
748
|
+
image=claude_image,
|
|
749
|
+
name=container_name,
|
|
750
|
+
user=DOCKER_USER,
|
|
751
|
+
detach=True,
|
|
752
|
+
command="tail -f /dev/null",
|
|
753
|
+
platform="linux/x86_64",
|
|
754
|
+
mem_limit="10g",
|
|
755
|
+
)
|
|
756
|
+
container.start()
|
|
757
|
+
|
|
758
|
+
# Checkout the instance branch
|
|
759
|
+
# The locally-built image (from swesmith.build_repo.create_images) has all branches
|
|
760
|
+
val = container.exec_run(
|
|
761
|
+
f"git checkout {instance_id}",
|
|
762
|
+
workdir=DOCKER_WORKDIR,
|
|
763
|
+
user=DOCKER_USER,
|
|
764
|
+
)
|
|
765
|
+
if val.exit_code != 0:
|
|
766
|
+
raise RuntimeError(f"Failed to checkout {instance_id}: {val.output.decode()}")
|
|
767
|
+
|
|
768
|
+
# Run Claude Code
|
|
769
|
+
result = run_claude_code_in_container(task, container, timeout=timeout, skills=skills)
|
|
770
|
+
|
|
771
|
+
# Verify the patch
|
|
772
|
+
passed = False
|
|
773
|
+
test_output = ""
|
|
774
|
+
if result.patch.strip():
|
|
775
|
+
passed, test_output = verify_patch_with_harness(task, result.patch, timeout=verify_timeout)
|
|
776
|
+
else:
|
|
777
|
+
test_output = "No patch generated"
|
|
778
|
+
|
|
779
|
+
return {
|
|
780
|
+
"instance_id": instance_id,
|
|
781
|
+
"claude_success": result.success,
|
|
782
|
+
"test_passed": passed,
|
|
783
|
+
"patch": result.patch,
|
|
784
|
+
"claude_output": result.output[:5000], # Truncate for storage
|
|
785
|
+
"test_output": test_output[:5000],
|
|
786
|
+
"duration_seconds": result.duration_seconds,
|
|
787
|
+
"error": result.error,
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
except Exception as e:
|
|
791
|
+
import traceback
|
|
792
|
+
|
|
793
|
+
return {
|
|
794
|
+
"instance_id": instance_id,
|
|
795
|
+
"claude_success": False,
|
|
796
|
+
"test_passed": False,
|
|
797
|
+
"patch": "",
|
|
798
|
+
"claude_output": "",
|
|
799
|
+
"test_output": "",
|
|
800
|
+
"duration_seconds": 0,
|
|
801
|
+
"error": f"{e!s}\n{traceback.format_exc()}",
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
finally:
|
|
805
|
+
if container:
|
|
806
|
+
try:
|
|
807
|
+
container.stop()
|
|
808
|
+
container.remove()
|
|
809
|
+
except Exception:
|
|
810
|
+
pass
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
def evaluate_testset(
|
|
814
|
+
test_data: list[dict],
|
|
815
|
+
output_dir: Path,
|
|
816
|
+
n_workers: int = 4,
|
|
817
|
+
timeout: int = 1800,
|
|
818
|
+
verify_timeout: int = 300,
|
|
819
|
+
skills: str | None = None,
|
|
820
|
+
run_config: dict[str, Any] | None = None,
|
|
821
|
+
) -> dict[str, Any]:
|
|
822
|
+
"""
|
|
823
|
+
Evaluate all tasks in testset with Claude Code.
|
|
824
|
+
|
|
825
|
+
Args:
|
|
826
|
+
test_data: List of task instances
|
|
827
|
+
output_dir: Directory to save results
|
|
828
|
+
n_workers: Number of parallel workers
|
|
829
|
+
timeout: Timeout for Claude Code execution
|
|
830
|
+
verify_timeout: Timeout for test verification
|
|
831
|
+
skills: Optional skills content to copy as CLAUDE.md
|
|
832
|
+
run_config: Run configuration metadata for summary
|
|
833
|
+
|
|
834
|
+
Returns:
|
|
835
|
+
Summary dict with all results
|
|
836
|
+
"""
|
|
837
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
838
|
+
|
|
839
|
+
# Ensure Claude Code images exist for all unique base images
|
|
840
|
+
print("\n" + "=" * 70)
|
|
841
|
+
print("Preparing Claude Code Docker images...")
|
|
842
|
+
print("=" * 70)
|
|
843
|
+
|
|
844
|
+
image_cache = {} # base_image -> claude_image
|
|
845
|
+
for task in test_data:
|
|
846
|
+
base_image = get_base_image_name(task)
|
|
847
|
+
if base_image not in image_cache:
|
|
848
|
+
claude_image = ensure_claude_code_image(task)
|
|
849
|
+
image_cache[base_image] = claude_image
|
|
850
|
+
|
|
851
|
+
print(f"Prepared {len(image_cache)} Claude Code image(s)")
|
|
852
|
+
|
|
853
|
+
# Evaluate tasks
|
|
854
|
+
print("\n" + "=" * 70)
|
|
855
|
+
print(f"Evaluating {len(test_data)} tasks with Claude Code...")
|
|
856
|
+
print(f"Workers: {n_workers}, Timeout: {timeout}s")
|
|
857
|
+
print("=" * 70 + "\n")
|
|
858
|
+
|
|
859
|
+
results = []
|
|
860
|
+
|
|
861
|
+
def process_task(task):
|
|
862
|
+
base_image = get_base_image_name(task)
|
|
863
|
+
claude_image = image_cache[base_image]
|
|
864
|
+
return evaluate_single_task(task, claude_image, timeout, verify_timeout, skills=skills)
|
|
865
|
+
|
|
866
|
+
if n_workers <= 1:
|
|
867
|
+
# Sequential execution
|
|
868
|
+
for i, task in enumerate(test_data):
|
|
869
|
+
print(f"[{i + 1}/{len(test_data)}] {task[KEY_INSTANCE_ID][:50]}...")
|
|
870
|
+
result = process_task(task)
|
|
871
|
+
results.append(result)
|
|
872
|
+
|
|
873
|
+
status = "✓ PASS" if result["test_passed"] else "✗ FAIL"
|
|
874
|
+
print(f" {status} ({result['duration_seconds']:.1f}s)")
|
|
875
|
+
|
|
876
|
+
# Save intermediate results
|
|
877
|
+
with open(output_dir / "results.jsonl", "a") as f:
|
|
878
|
+
f.write(json.dumps(result) + "\n")
|
|
879
|
+
else:
|
|
880
|
+
# Parallel execution
|
|
881
|
+
with ThreadPoolExecutor(max_workers=n_workers) as executor:
|
|
882
|
+
futures = {executor.submit(process_task, task): task for task in test_data}
|
|
883
|
+
|
|
884
|
+
for i, future in enumerate(as_completed(futures)):
|
|
885
|
+
task = futures[future]
|
|
886
|
+
try:
|
|
887
|
+
result = future.result()
|
|
888
|
+
except Exception as e:
|
|
889
|
+
result = {
|
|
890
|
+
"instance_id": task[KEY_INSTANCE_ID],
|
|
891
|
+
"claude_success": False,
|
|
892
|
+
"test_passed": False,
|
|
893
|
+
"error": str(e),
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
results.append(result)
|
|
897
|
+
|
|
898
|
+
status = "✓ PASS" if result.get("test_passed") else "✗ FAIL"
|
|
899
|
+
print(f"[{i + 1}/{len(test_data)}] {result['instance_id'][:50]}: {status}")
|
|
900
|
+
|
|
901
|
+
# Save intermediate results
|
|
902
|
+
with open(output_dir / "results.jsonl", "a") as f:
|
|
903
|
+
f.write(json.dumps(result) + "\n")
|
|
904
|
+
|
|
905
|
+
# Compute summary
|
|
906
|
+
passed = sum(1 for r in results if r.get("test_passed"))
|
|
907
|
+
claude_success = sum(1 for r in results if r.get("claude_success"))
|
|
908
|
+
total = len(results)
|
|
909
|
+
|
|
910
|
+
summary = {
|
|
911
|
+
# Run configuration (at top for easy access)
|
|
912
|
+
"run_config": run_config or {},
|
|
913
|
+
# Results summary
|
|
914
|
+
"total": total,
|
|
915
|
+
"passed": passed,
|
|
916
|
+
"pass_rate": passed / total if total > 0 else 0,
|
|
917
|
+
"claude_generated_patches": claude_success,
|
|
918
|
+
"claude_patch_rate": claude_success / total if total > 0 else 0,
|
|
919
|
+
"avg_duration": sum(r.get("duration_seconds", 0) for r in results) / total if total > 0 else 0,
|
|
920
|
+
"results": results,
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
# Save summary
|
|
924
|
+
with open(output_dir / "summary.json", "w") as f:
|
|
925
|
+
json.dump(summary, f, indent=2)
|
|
926
|
+
|
|
927
|
+
print("\n" + "=" * 70)
|
|
928
|
+
print("EVALUATION COMPLETE")
|
|
929
|
+
print("=" * 70)
|
|
930
|
+
print(f"Total tasks: {total}")
|
|
931
|
+
print(f"Claude generated patches: {claude_success}/{total} ({summary['claude_patch_rate']:.1%})")
|
|
932
|
+
print(f"Tests passed: {passed}/{total} ({summary['pass_rate']:.1%})")
|
|
933
|
+
print(f"Average duration: {summary['avg_duration']:.1f}s")
|
|
934
|
+
print(f"Results saved to: {output_dir}")
|
|
935
|
+
|
|
936
|
+
return summary
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
# ============================================================================
|
|
940
|
+
# CLI
|
|
941
|
+
# ============================================================================
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
def main():
|
|
945
|
+
parser = argparse.ArgumentParser(
|
|
946
|
+
description="Evaluate testset with Claude Code",
|
|
947
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
948
|
+
epilog="""
|
|
949
|
+
Examples:
|
|
950
|
+
# Extract test IDs from config
|
|
951
|
+
python evaluate_claude_code.py --config gepa_results/logs/run_xxx/config.json --extract-ids test_ids.txt
|
|
952
|
+
|
|
953
|
+
# Evaluate using config (will reconstruct testset)
|
|
954
|
+
python evaluate_claude_code.py --config gepa_results/logs/run_xxx/config.json --output results/
|
|
955
|
+
|
|
956
|
+
# Evaluate with custom parameters
|
|
957
|
+
python evaluate_claude_code.py --config gepa_results/logs/run_xxx/config.json --workers 4 --timeout 1800
|
|
958
|
+
""",
|
|
959
|
+
)
|
|
960
|
+
|
|
961
|
+
parser.add_argument("--config", type=str, required=True, help="Path to config.json from a GEPA run")
|
|
962
|
+
parser.add_argument("--extract-ids", type=str, default=None, help="Extract test IDs to this file and exit")
|
|
963
|
+
parser.add_argument(
|
|
964
|
+
"--output",
|
|
965
|
+
type=str,
|
|
966
|
+
default=None,
|
|
967
|
+
help="Output directory for results (default: gepa_results/claude_code/<run_id>)",
|
|
968
|
+
)
|
|
969
|
+
parser.add_argument("--workers", type=int, default=4, help="Number of parallel workers (default: 4)")
|
|
970
|
+
parser.add_argument("--timeout", type=int, default=1800, help="Timeout for Claude Code in seconds (default: 1800)")
|
|
971
|
+
parser.add_argument(
|
|
972
|
+
"--verify-timeout", type=int, default=300, help="Timeout for test verification in seconds (default: 300)"
|
|
973
|
+
)
|
|
974
|
+
parser.add_argument(
|
|
975
|
+
"--ids-file", type=str, default=None, help="Load test IDs from file instead of reconstructing from config"
|
|
976
|
+
)
|
|
977
|
+
parser.add_argument(
|
|
978
|
+
"--dry-run", action="store_true", help="Only prepare images and show what would be run, don't execute"
|
|
979
|
+
)
|
|
980
|
+
parser.add_argument("--limit", type=int, default=None, help="Limit number of tasks to evaluate (for testing)")
|
|
981
|
+
parser.add_argument(
|
|
982
|
+
"--model",
|
|
983
|
+
type=str,
|
|
984
|
+
default="sonnet",
|
|
985
|
+
choices=["sonnet", "opus", "haiku", "default"],
|
|
986
|
+
help="Claude Code model to use (default: sonnet = Claude Sonnet 4.5)",
|
|
987
|
+
)
|
|
988
|
+
parser.add_argument(
|
|
989
|
+
"--use-skills",
|
|
990
|
+
action="store_true",
|
|
991
|
+
help="Load best_skills.txt from config directory and copy as CLAUDE.md in the repo",
|
|
992
|
+
)
|
|
993
|
+
|
|
994
|
+
args = parser.parse_args()
|
|
995
|
+
|
|
996
|
+
# Verify Docker connection first
|
|
997
|
+
try:
|
|
998
|
+
client = docker.from_env()
|
|
999
|
+
client.ping()
|
|
1000
|
+
print(f"Docker connection OK (DOCKER_HOST={os.environ.get('DOCKER_HOST', 'default')})")
|
|
1001
|
+
except Exception as e:
|
|
1002
|
+
print(f"ERROR: Cannot connect to Docker: {e}")
|
|
1003
|
+
print("Please ensure Docker is running:")
|
|
1004
|
+
print(" Rootless: systemctl --user start docker")
|
|
1005
|
+
print(" Standard: sudo systemctl start docker")
|
|
1006
|
+
sys.exit(1)
|
|
1007
|
+
|
|
1008
|
+
# Load config
|
|
1009
|
+
with open(args.config) as f:
|
|
1010
|
+
config = json.load(f)
|
|
1011
|
+
|
|
1012
|
+
run_id = config.get("run_id", Path(args.config).parent.name)
|
|
1013
|
+
repo = config["repo"]
|
|
1014
|
+
|
|
1015
|
+
# Set model environment variable for Claude Code
|
|
1016
|
+
os.environ["CLAUDE_CODE_MODEL"] = args.model
|
|
1017
|
+
print(f"Using Claude Code model: {args.model}")
|
|
1018
|
+
|
|
1019
|
+
# Load testset
|
|
1020
|
+
if args.ids_file:
|
|
1021
|
+
# Load IDs from file and fetch corresponding tasks
|
|
1022
|
+
print(f"Loading test IDs from {args.ids_file}")
|
|
1023
|
+
test_ids = load_test_ids(args.ids_file)
|
|
1024
|
+
test_data = load_testset_by_ids(test_ids, repo)
|
|
1025
|
+
print(f"Loaded {len(test_data)} tasks from IDs file")
|
|
1026
|
+
else:
|
|
1027
|
+
# Reconstruct testset from config
|
|
1028
|
+
test_data, _ = load_testset_from_config(args.config)
|
|
1029
|
+
|
|
1030
|
+
# Extract IDs mode
|
|
1031
|
+
if args.extract_ids:
|
|
1032
|
+
save_test_ids(test_data, args.extract_ids)
|
|
1033
|
+
return
|
|
1034
|
+
|
|
1035
|
+
# Set output directory
|
|
1036
|
+
if args.output:
|
|
1037
|
+
output_dir = Path(args.output)
|
|
1038
|
+
else:
|
|
1039
|
+
output_dir = Path("gepa_results/claude_code") / f"{run_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
|
1040
|
+
|
|
1041
|
+
# Apply limit if specified
|
|
1042
|
+
if args.limit and args.limit < len(test_data):
|
|
1043
|
+
print(f"Limiting to first {args.limit} tasks (out of {len(test_data)})")
|
|
1044
|
+
test_data = test_data[: args.limit]
|
|
1045
|
+
|
|
1046
|
+
# Save test IDs for reference
|
|
1047
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
1048
|
+
save_test_ids(test_data, str(output_dir / "test_ids.txt"))
|
|
1049
|
+
|
|
1050
|
+
# Save config copy
|
|
1051
|
+
with open(output_dir / "source_config.json", "w") as f:
|
|
1052
|
+
json.dump(config, f, indent=2)
|
|
1053
|
+
|
|
1054
|
+
# Load skills from config directory if requested
|
|
1055
|
+
config_dir = Path(args.config).parent
|
|
1056
|
+
skills = None
|
|
1057
|
+
if args.use_skills:
|
|
1058
|
+
skills_path = config_dir / "prompts" / "best_skills.txt"
|
|
1059
|
+
if skills_path.exists():
|
|
1060
|
+
skills = skills_path.read_text()
|
|
1061
|
+
print(f"Loaded skills from {skills_path} ({len(skills)} chars)")
|
|
1062
|
+
# Save copy of skills
|
|
1063
|
+
with open(output_dir / "skills.txt", "w") as f:
|
|
1064
|
+
f.write(skills)
|
|
1065
|
+
else:
|
|
1066
|
+
print(f"WARNING: Skills file not found: {skills_path}")
|
|
1067
|
+
|
|
1068
|
+
# Dry run mode - just prepare images
|
|
1069
|
+
if args.dry_run:
|
|
1070
|
+
print("\n" + "=" * 70)
|
|
1071
|
+
print("DRY RUN - Preparing images only")
|
|
1072
|
+
print("=" * 70)
|
|
1073
|
+
|
|
1074
|
+
image_cache = {}
|
|
1075
|
+
for task in test_data:
|
|
1076
|
+
base_image = get_base_image_name(task)
|
|
1077
|
+
if base_image not in image_cache:
|
|
1078
|
+
claude_image = ensure_claude_code_image(task)
|
|
1079
|
+
image_cache[base_image] = claude_image
|
|
1080
|
+
|
|
1081
|
+
print(f"\nPrepared {len(image_cache)} Claude Code image(s)")
|
|
1082
|
+
print(f"Would evaluate {len(test_data)} tasks")
|
|
1083
|
+
print(f"Test IDs saved to: {output_dir / 'test_ids.txt'}")
|
|
1084
|
+
|
|
1085
|
+
return {"dry_run": True, "num_tasks": len(test_data), "images": list(image_cache.values())}
|
|
1086
|
+
|
|
1087
|
+
# Build run configuration metadata
|
|
1088
|
+
run_config = {
|
|
1089
|
+
"model": args.model,
|
|
1090
|
+
"use_skills": args.use_skills,
|
|
1091
|
+
"skills_path": str(config_dir / "prompts" / "best_skills.txt") if args.use_skills else None,
|
|
1092
|
+
"config_path": str(Path(args.config).resolve()),
|
|
1093
|
+
"source_run_id": run_id,
|
|
1094
|
+
"repo": repo,
|
|
1095
|
+
"workers": args.workers,
|
|
1096
|
+
"timeout": args.timeout,
|
|
1097
|
+
"verify_timeout": args.verify_timeout,
|
|
1098
|
+
"num_tasks": len(test_data),
|
|
1099
|
+
"timestamp": datetime.now().isoformat(),
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
# Run evaluation
|
|
1103
|
+
summary = evaluate_testset(
|
|
1104
|
+
test_data=test_data,
|
|
1105
|
+
output_dir=output_dir,
|
|
1106
|
+
n_workers=args.workers,
|
|
1107
|
+
timeout=args.timeout,
|
|
1108
|
+
verify_timeout=args.verify_timeout,
|
|
1109
|
+
skills=skills,
|
|
1110
|
+
run_config=run_config,
|
|
1111
|
+
)
|
|
1112
|
+
|
|
1113
|
+
return summary
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
if __name__ == "__main__":
|
|
1117
|
+
main()
|