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.
Files changed (378) hide show
  1. package/README.md +23 -0
  2. package/dist/archive-artifacts-cli.d.ts +50 -0
  3. package/dist/archive-artifacts-cli.d.ts.map +1 -0
  4. package/dist/artifact-archive.d.ts +48 -0
  5. package/dist/artifact-archive.d.ts.map +1 -0
  6. package/dist/artifact-pull.d.ts +22 -0
  7. package/dist/artifact-pull.d.ts.map +1 -0
  8. package/dist/banner.d.ts +3 -0
  9. package/dist/banner.d.ts.map +1 -0
  10. package/dist/connectors-cli.d.ts +31 -0
  11. package/dist/connectors-cli.d.ts.map +1 -0
  12. package/dist/credentials.d.ts +46 -0
  13. package/dist/credentials.d.ts.map +1 -0
  14. package/dist/csv.d.ts +49 -0
  15. package/dist/csv.d.ts.map +1 -0
  16. package/dist/dataset-download.d.ts +6 -0
  17. package/dist/dataset-download.d.ts.map +1 -0
  18. package/dist/dataset-edit-rows.d.ts +10 -0
  19. package/dist/dataset-edit-rows.d.ts.map +1 -0
  20. package/dist/dataset-upload-snapshot.d.ts +13 -0
  21. package/dist/dataset-upload-snapshot.d.ts.map +1 -0
  22. package/dist/daytona-slice-rehearsal.d.ts +89 -0
  23. package/dist/daytona-slice-rehearsal.d.ts.map +1 -0
  24. package/dist/daytona-slice.d.ts +106 -0
  25. package/dist/daytona-slice.d.ts.map +1 -0
  26. package/dist/deploy-key-merge-core.d.ts +121 -0
  27. package/dist/deploy-key-merge-core.d.ts.map +1 -0
  28. package/dist/diff-comments-cli.d.ts +12 -0
  29. package/dist/diff-comments-cli.d.ts.map +1 -0
  30. package/dist/diff-comments-cli.js +21 -2
  31. package/dist/egress-allowlist-cli.d.ts +23 -0
  32. package/dist/egress-allowlist-cli.d.ts.map +1 -0
  33. package/dist/egress-policy.d.ts +108 -0
  34. package/dist/egress-policy.d.ts.map +1 -0
  35. package/dist/error-response.d.ts +9 -0
  36. package/dist/error-response.d.ts.map +1 -0
  37. package/dist/file-parser.d.ts +5 -0
  38. package/dist/file-parser.d.ts.map +1 -0
  39. package/dist/gepa-engine-dispatch.d.ts +9 -0
  40. package/dist/gepa-engine-dispatch.d.ts.map +1 -0
  41. package/dist/gepa-engine-dispatch.js +307 -0
  42. package/dist/gepa-python-command.d.ts +6 -0
  43. package/dist/gepa-python-command.d.ts.map +1 -0
  44. package/dist/gepa-python-command.js +26 -0
  45. package/dist/gepa-python-paths.d.ts +6 -0
  46. package/dist/gepa-python-paths.d.ts.map +1 -0
  47. package/dist/gepa-python-paths.js +42 -0
  48. package/dist/git-credential.d.ts +69 -0
  49. package/dist/git-credential.d.ts.map +1 -0
  50. package/dist/git-recovery-environment.d.ts +31 -0
  51. package/dist/git-recovery-environment.d.ts.map +1 -0
  52. package/dist/github-setup.d.ts +122 -0
  53. package/dist/github-setup.d.ts.map +1 -0
  54. package/dist/global-flags.d.ts +12 -0
  55. package/dist/global-flags.d.ts.map +1 -0
  56. package/dist/help.d.ts +46 -0
  57. package/dist/help.d.ts.map +1 -0
  58. package/dist/help.js +93 -25
  59. package/dist/hosted-boot.d.ts +273 -0
  60. package/dist/hosted-boot.d.ts.map +1 -0
  61. package/dist/hosted-bootstrap-rehearsal.d.ts +86 -0
  62. package/dist/hosted-bootstrap-rehearsal.d.ts.map +1 -0
  63. package/dist/hosted-bootstrap.d.ts +188 -0
  64. package/dist/hosted-bootstrap.d.ts.map +1 -0
  65. package/dist/hosted-broker-server.d.ts +50 -0
  66. package/dist/hosted-broker-server.d.ts.map +1 -0
  67. package/dist/hosted-git-http-server.d.ts +36 -0
  68. package/dist/hosted-git-http-server.d.ts.map +1 -0
  69. package/dist/hosted-harness-claude.d.ts +195 -0
  70. package/dist/hosted-harness-claude.d.ts.map +1 -0
  71. package/dist/hosted-harness-opencode.d.ts +227 -0
  72. package/dist/hosted-harness-opencode.d.ts.map +1 -0
  73. package/dist/hosted-harness.d.ts +118 -0
  74. package/dist/hosted-harness.d.ts.map +1 -0
  75. package/dist/hosted-harvest.d.ts +68 -0
  76. package/dist/hosted-harvest.d.ts.map +1 -0
  77. package/dist/hosted-headless.d.ts +72 -0
  78. package/dist/hosted-headless.d.ts.map +1 -0
  79. package/dist/hosted-loop-lifecycle.d.ts +206 -0
  80. package/dist/hosted-loop-lifecycle.d.ts.map +1 -0
  81. package/dist/hosted-loop.d.ts +170 -0
  82. package/dist/hosted-loop.d.ts.map +1 -0
  83. package/dist/hosted-run-event-sink.d.ts +222 -0
  84. package/dist/hosted-run-event-sink.d.ts.map +1 -0
  85. package/dist/hosted-runtime-assets.d.ts +186 -0
  86. package/dist/hosted-runtime-assets.d.ts.map +1 -0
  87. package/dist/hosted-session-cli.d.ts +258 -0
  88. package/dist/hosted-session-cli.d.ts.map +1 -0
  89. package/dist/hosted-skill-staging.d.ts +73 -0
  90. package/dist/hosted-skill-staging.d.ts.map +1 -0
  91. package/dist/http.d.ts +8 -0
  92. package/dist/http.d.ts.map +1 -0
  93. package/dist/index.d.ts +22 -0
  94. package/dist/index.d.ts.map +1 -0
  95. package/dist/index.js +119 -129
  96. package/dist/instruction-set-gepa-launch.d.ts +3 -0
  97. package/dist/instruction-set-gepa-launch.d.ts.map +1 -0
  98. package/dist/instruction-set-gepa-launch.js +15 -0
  99. package/dist/instruction-set-loader.d.ts +6 -0
  100. package/dist/instruction-set-loader.d.ts.map +1 -0
  101. package/dist/instruction-set-loader.js +59 -0
  102. package/dist/instruction-set-manifest.d.ts +14 -0
  103. package/dist/instruction-set-manifest.d.ts.map +1 -0
  104. package/dist/instruction-set-manifest.js +162 -0
  105. package/dist/instruction-sets-cli.d.ts +40 -0
  106. package/dist/instruction-sets-cli.d.ts.map +1 -0
  107. package/dist/instruction-sets-cli.js +361 -0
  108. package/dist/json-response.d.ts +4 -0
  109. package/dist/json-response.d.ts.map +1 -0
  110. package/dist/jsonl-stream.d.ts +8 -0
  111. package/dist/jsonl-stream.d.ts.map +1 -0
  112. package/dist/kill-switch-cli.d.ts +30 -0
  113. package/dist/kill-switch-cli.d.ts.map +1 -0
  114. package/dist/loopback-serve.d.ts +25 -0
  115. package/dist/loopback-serve.d.ts.map +1 -0
  116. package/dist/manifests-cli.d.ts +22 -0
  117. package/dist/manifests-cli.d.ts.map +1 -0
  118. package/dist/markdown-report-input.d.ts +7 -0
  119. package/dist/markdown-report-input.d.ts.map +1 -0
  120. package/dist/markdown-report-input.js +15 -1
  121. package/dist/merge-job.d.ts +155 -0
  122. package/dist/merge-job.d.ts.map +1 -0
  123. package/dist/model-configs-cli.d.ts +19 -0
  124. package/dist/model-configs-cli.d.ts.map +1 -0
  125. package/dist/model-configs-cli.js +104 -0
  126. package/dist/optimization-export-cli.d.ts +19 -0
  127. package/dist/optimization-export-cli.d.ts.map +1 -0
  128. package/dist/optimizations-list-cli.d.ts +20 -0
  129. package/dist/optimizations-list-cli.d.ts.map +1 -0
  130. package/dist/optimizations-promote-cli.d.ts +8 -0
  131. package/dist/optimizations-promote-cli.d.ts.map +1 -0
  132. package/dist/optimizations-promote-cli.js +34 -0
  133. package/dist/option-validation.d.ts +15 -0
  134. package/dist/option-validation.d.ts.map +1 -0
  135. package/dist/preview-runtime.d.ts +63 -0
  136. package/dist/preview-runtime.d.ts.map +1 -0
  137. package/dist/prompt-draft-push.d.ts +60 -0
  138. package/dist/prompt-draft-push.d.ts.map +1 -0
  139. package/dist/prompt-label-cli.d.ts +11 -0
  140. package/dist/prompt-label-cli.d.ts.map +1 -0
  141. package/dist/prompt-label-cli.js +29 -0
  142. package/dist/prompt-length-wire.d.ts +28 -0
  143. package/dist/prompt-length-wire.d.ts.map +1 -0
  144. package/dist/prompt-length-wire.js +2 -2
  145. package/dist/prompt-report-cli.d.ts +10 -0
  146. package/dist/prompt-report-cli.d.ts.map +1 -0
  147. package/dist/prompt-report-cli.js +2 -2
  148. package/dist/prompt-summary-table.d.ts +15 -0
  149. package/dist/prompt-summary-table.d.ts.map +1 -0
  150. package/dist/prompt-summary-table.js +6 -0
  151. package/dist/registration-tag-warning.d.ts +32 -0
  152. package/dist/registration-tag-warning.d.ts.map +1 -0
  153. package/dist/report-comments-cli.d.ts +16 -0
  154. package/dist/report-comments-cli.d.ts.map +1 -0
  155. package/dist/report-markdown-blankness.d.ts +2 -0
  156. package/dist/report-markdown-blankness.d.ts.map +1 -0
  157. package/dist/report-markdown-blankness.js +6 -0
  158. package/dist/report-replacement-warning.d.ts +17 -0
  159. package/dist/report-replacement-warning.d.ts.map +1 -0
  160. package/dist/report-replacement-warning.js +31 -0
  161. package/dist/repository-backup-core.d.ts +225 -0
  162. package/dist/repository-backup-core.d.ts.map +1 -0
  163. package/dist/repository-recovery-manifest.d.ts +83 -0
  164. package/dist/repository-recovery-manifest.d.ts.map +1 -0
  165. package/dist/run-event-digest.d.ts +74 -0
  166. package/dist/run-event-digest.d.ts.map +1 -0
  167. package/dist/runner-dir-verify.d.ts +66 -0
  168. package/dist/runner-dir-verify.d.ts.map +1 -0
  169. package/dist/runner-instruction-set-contract.d.ts +24 -0
  170. package/dist/runner-instruction-set-contract.d.ts.map +1 -0
  171. package/dist/runner-instruction-set-contract.js +50 -0
  172. package/dist/sandbox-hygiene.d.ts +45 -0
  173. package/dist/sandbox-hygiene.d.ts.map +1 -0
  174. package/dist/sandbox-provider.d.ts +193 -0
  175. package/dist/sandbox-provider.d.ts.map +1 -0
  176. package/dist/scorer-draft-push.d.ts +50 -0
  177. package/dist/scorer-draft-push.d.ts.map +1 -0
  178. package/dist/scorers-cli.d.ts +20 -0
  179. package/dist/scorers-cli.d.ts.map +1 -0
  180. package/dist/scorers-cli.js +22 -0
  181. package/dist/scorers-verify-parity-bridge.d.ts +16 -0
  182. package/dist/scorers-verify-parity-bridge.d.ts.map +1 -0
  183. package/dist/scorers-verify-parity-bridge.js +90 -0
  184. package/dist/scorers-verify-parity.d.ts +81 -0
  185. package/dist/scorers-verify-parity.d.ts.map +1 -0
  186. package/dist/scorers-verify-parity.js +502 -0
  187. package/dist/scores-accept-cli.d.ts +21 -0
  188. package/dist/scores-accept-cli.d.ts.map +1 -0
  189. package/dist/secret-redaction.d.ts +47 -0
  190. package/dist/secret-redaction.d.ts.map +1 -0
  191. package/dist/session-timeline.d.ts +54 -0
  192. package/dist/session-timeline.d.ts.map +1 -0
  193. package/dist/setup-prompt.d.ts +5 -0
  194. package/dist/setup-prompt.d.ts.map +1 -0
  195. package/dist/skill-installer.d.ts +68 -0
  196. package/dist/skill-installer.d.ts.map +1 -0
  197. package/dist/skilled-proposer-launch.d.ts +7 -0
  198. package/dist/skilled-proposer-launch.d.ts.map +1 -0
  199. package/dist/skilled-proposer-launch.js +60 -0
  200. package/dist/smoke.d.ts +102 -0
  201. package/dist/smoke.d.ts.map +1 -0
  202. package/dist/start-intent.d.ts +12 -0
  203. package/dist/start-intent.d.ts.map +1 -0
  204. package/dist/task-assignment-manifest.d.ts +15 -0
  205. package/dist/task-assignment-manifest.d.ts.map +1 -0
  206. package/dist/task-create-error.d.ts +18 -0
  207. package/dist/task-create-error.d.ts.map +1 -0
  208. package/dist/team-connectors-cli.d.ts +29 -0
  209. package/dist/team-connectors-cli.d.ts.map +1 -0
  210. package/dist/types.d.ts +39 -0
  211. package/dist/types.d.ts.map +1 -0
  212. package/dist/vercel-sandbox-provider.d.ts +156 -0
  213. package/dist/vercel-sandbox-provider.d.ts.map +1 -0
  214. package/dist/workbench-cli.d.ts +233 -0
  215. package/dist/workbench-cli.d.ts.map +1 -0
  216. package/dist/workspace-sync.d.ts +207 -0
  217. package/dist/workspace-sync.d.ts.map +1 -0
  218. package/dist/workspace.d.ts +92 -0
  219. package/dist/workspace.d.ts.map +1 -0
  220. package/dist/zip-draft-push.d.ts +55 -0
  221. package/dist/zip-draft-push.d.ts.map +1 -0
  222. package/package.json +15 -2
  223. package/requirements/README.md +138 -0
  224. package/requirements/skilled-proposer.in +66 -0
  225. package/requirements/skilled-proposer.lock +265 -0
  226. package/scripts/build-cli-dist.mjs +30 -0
  227. package/scripts/ensure-skilled-proposer-venv.mjs +5 -0
  228. package/scripts/orizu-source-checkout.d.mts +1 -0
  229. package/scripts/orizu-source-checkout.mjs +10 -0
  230. package/scripts/refresh-skilled-proposer-lock.mjs +256 -0
  231. package/scripts/vendor-gepa-python.mjs +187 -4
  232. package/src/diff-comments-cli.ts +41 -3
  233. package/src/gepa-engine-dispatch.ts +340 -0
  234. package/src/gepa-python-command.ts +31 -0
  235. package/src/gepa-python-paths.ts +56 -0
  236. package/src/help.ts +95 -25
  237. package/src/index.ts +131 -166
  238. package/src/instruction-set-gepa-launch.ts +28 -0
  239. package/src/instruction-set-loader.ts +37 -0
  240. package/src/instruction-set-manifest.ts +120 -0
  241. package/src/instruction-sets-cli.ts +306 -0
  242. package/src/markdown-report-input.ts +19 -1
  243. package/src/model-configs-cli.ts +119 -0
  244. package/src/optimizations-promote-cli.ts +41 -0
  245. package/src/prompt-label-cli.ts +55 -0
  246. package/src/prompt-length-wire.ts +1 -1
  247. package/src/prompt-report-cli.ts +2 -2
  248. package/src/prompt-summary-table.ts +12 -0
  249. package/src/report-markdown-blankness.ts +6 -0
  250. package/src/report-replacement-warning.ts +35 -0
  251. package/src/runner-instruction-set-contract.ts +64 -0
  252. package/src/scorers-cli.ts +40 -0
  253. package/src/scorers-verify-parity-bridge.ts +90 -0
  254. package/src/scorers-verify-parity.ts +564 -0
  255. package/src/skilled-proposer-launch.ts +89 -0
  256. package/src/skilled-proposer-venv-manager.mjs +757 -0
  257. package/tsconfig.json +2 -0
  258. package/vendor/gepa-python/LICENSE +7 -0
  259. package/vendor/gepa-python/manifest.json +107 -0
  260. package/vendor/gepa-python/src/gepa/__init__.py +22 -0
  261. package/vendor/gepa-python/src/gepa/adapters/__init__.py +0 -0
  262. package/vendor/gepa-python/src/gepa/adapters/anymaths_adapter/__init__.py +1 -0
  263. package/vendor/gepa-python/src/gepa/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
  264. package/vendor/gepa-python/src/gepa/adapters/confidence_adapter/__init__.py +26 -0
  265. package/vendor/gepa-python/src/gepa/adapters/confidence_adapter/confidence_adapter.py +618 -0
  266. package/vendor/gepa-python/src/gepa/adapters/confidence_adapter/scoring.py +206 -0
  267. package/vendor/gepa-python/src/gepa/adapters/default_adapter/__init__.py +0 -0
  268. package/vendor/gepa-python/src/gepa/adapters/default_adapter/default_adapter.py +206 -0
  269. package/vendor/gepa-python/src/gepa/adapters/dspy_adapter/__init__.py +0 -0
  270. package/vendor/gepa-python/src/gepa/adapters/dspy_adapter/dspy_adapter.py +525 -0
  271. package/vendor/gepa-python/src/gepa/adapters/dspy_adapter/instruction_proposal.py +500 -0
  272. package/vendor/gepa-python/src/gepa/adapters/dspy_full_program_adapter/__init__.py +6 -0
  273. package/vendor/gepa-python/src/gepa/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
  274. package/vendor/gepa-python/src/gepa/adapters/dspy_full_program_adapter/full_program_adapter.py +269 -0
  275. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/__init__.py +56 -0
  276. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
  277. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
  278. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
  279. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
  280. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
  281. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
  282. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
  283. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
  284. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
  285. package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
  286. package/vendor/gepa-python/src/gepa/adapters/langchain_adapter/__init__.py +11 -0
  287. package/vendor/gepa-python/src/gepa/adapters/langchain_adapter/langchain_adapter.py +261 -0
  288. package/vendor/gepa-python/src/gepa/adapters/mcp_adapter/__init__.py +37 -0
  289. package/vendor/gepa-python/src/gepa/adapters/mcp_adapter/mcp_adapter.py +693 -0
  290. package/vendor/gepa-python/src/gepa/adapters/mcp_adapter/mcp_client.py +364 -0
  291. package/vendor/gepa-python/src/gepa/adapters/optimize_anything_adapter/optimize_anything_adapter.py +879 -0
  292. package/vendor/gepa-python/src/gepa/adapters/terminal_bench_adapter/__init__.py +0 -0
  293. package/vendor/gepa-python/src/gepa/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
  294. package/vendor/gepa-python/src/gepa/api.py +468 -0
  295. package/vendor/gepa-python/src/gepa/core/__init__.py +0 -0
  296. package/vendor/gepa-python/src/gepa/core/adapter.py +227 -0
  297. package/vendor/gepa-python/src/gepa/core/callbacks.py +551 -0
  298. package/vendor/gepa-python/src/gepa/core/data_loader.py +74 -0
  299. package/vendor/gepa-python/src/gepa/core/engine.py +1025 -0
  300. package/vendor/gepa-python/src/gepa/core/result.py +271 -0
  301. package/vendor/gepa-python/src/gepa/core/state.py +706 -0
  302. package/vendor/gepa-python/src/gepa/examples/__init__.py +0 -0
  303. package/vendor/gepa-python/src/gepa/examples/aime.py +24 -0
  304. package/vendor/gepa-python/src/gepa/examples/anymaths-bench/eval_default.py +111 -0
  305. package/vendor/gepa-python/src/gepa/examples/anymaths-bench/train_anymaths.py +177 -0
  306. package/vendor/gepa-python/src/gepa/examples/mcp_adapter/__init__.py +4 -0
  307. package/vendor/gepa-python/src/gepa/examples/mcp_adapter/mcp_optimization_example.py +456 -0
  308. package/vendor/gepa-python/src/gepa/examples/rag_adapter/__init__.py +9 -0
  309. package/vendor/gepa-python/src/gepa/examples/rag_adapter/rag_optimization.py +824 -0
  310. package/vendor/gepa-python/src/gepa/examples/terminal-bench/train_terminus.py +161 -0
  311. package/vendor/gepa-python/src/gepa/gepa_utils.py +117 -0
  312. package/vendor/gepa-python/src/gepa/gskill/__init__.py +1 -0
  313. package/vendor/gepa-python/src/gepa/gskill/gskill/__init__.py +0 -0
  314. package/vendor/gepa-python/src/gepa/gskill/gskill/cost_tracker.py +194 -0
  315. package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/__init__.py +0 -0
  316. package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/claude_code.py +1117 -0
  317. package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/claude_code_skills.py +613 -0
  318. package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/mini_swe_agent.py +440 -0
  319. package/vendor/gepa-python/src/gepa/gskill/gskill/experiment_logger.py +367 -0
  320. package/vendor/gepa-python/src/gepa/gskill/gskill/swe_fitness_fn.py +209 -0
  321. package/vendor/gepa-python/src/gepa/gskill/gskill/swe_harness.py +352 -0
  322. package/vendor/gepa-python/src/gepa/gskill/gskill/train_optimize_anything.py +830 -0
  323. package/vendor/gepa-python/src/gepa/image.py +104 -0
  324. package/vendor/gepa-python/src/gepa/lm.py +261 -0
  325. package/vendor/gepa-python/src/gepa/logging/__init__.py +0 -0
  326. package/vendor/gepa-python/src/gepa/logging/experiment_tracker.py +457 -0
  327. package/vendor/gepa-python/src/gepa/logging/logger.py +75 -0
  328. package/vendor/gepa-python/src/gepa/logging/utils.py +130 -0
  329. package/vendor/gepa-python/src/gepa/optimize_anything.py +1701 -0
  330. package/vendor/gepa-python/src/gepa/proposer/__init__.py +0 -0
  331. package/vendor/gepa-python/src/gepa/proposer/base.py +53 -0
  332. package/vendor/gepa-python/src/gepa/proposer/merge.py +403 -0
  333. package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/__init__.py +0 -0
  334. package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/base.py +64 -0
  335. package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/reflection_lm.py +178 -0
  336. package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/reflective_mutation.py +641 -0
  337. package/vendor/gepa-python/src/gepa/py.typed +0 -0
  338. package/vendor/gepa-python/src/gepa/strategies/__init__.py +0 -0
  339. package/vendor/gepa-python/src/gepa/strategies/acceptance.py +66 -0
  340. package/vendor/gepa-python/src/gepa/strategies/batch_sampler.py +103 -0
  341. package/vendor/gepa-python/src/gepa/strategies/candidate_selector.py +82 -0
  342. package/vendor/gepa-python/src/gepa/strategies/component_selector.py +36 -0
  343. package/vendor/gepa-python/src/gepa/strategies/eval_policy.py +64 -0
  344. package/vendor/gepa-python/src/gepa/strategies/instruction_proposal.py +153 -0
  345. package/vendor/gepa-python/src/gepa/strategies/proposal_sampling.py +128 -0
  346. package/vendor/gepa-python/src/gepa/strategies/proposal_selection.py +84 -0
  347. package/vendor/gepa-python/src/gepa/utils/__init__.py +63 -0
  348. package/vendor/gepa-python/src/gepa/utils/code_execution.py +626 -0
  349. package/vendor/gepa-python/src/gepa/utils/stdio_capture.py +145 -0
  350. package/vendor/gepa-python/src/gepa/utils/stop_condition.py +230 -0
  351. package/vendor/gepa-python/src/gepa/visualization.py +359 -0
  352. package/vendor/orizu-gepa/pyproject.toml +18 -0
  353. package/vendor/orizu-gepa/src/orizu_gepa_connector/__init__.py +9 -0
  354. package/vendor/orizu-gepa/src/orizu_gepa_connector/__main__.py +5 -0
  355. package/vendor/orizu-gepa/src/orizu_gepa_connector/adapter.py +138 -0
  356. package/vendor/orizu-gepa/src/orizu_gepa_connector/callbacks.py +666 -0
  357. package/vendor/orizu-gepa/src/orizu_gepa_connector/cli.py +7 -0
  358. package/vendor/orizu-gepa/src/orizu_gepa_connector/engine.py +269 -0
  359. package/vendor/orizu-gepa/src/orizu_gepa_connector/instruction_set_loader.py +52 -0
  360. package/vendor/orizu-gepa/src/orizu_gepa_connector/preflight.py +64 -0
  361. package/vendor/orizu-gepa/src/orizu_gepa_connector/reflection.py +186 -0
  362. package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +721 -0
  363. package/vendor/orizu-gepa/src/orizu_gepa_connector/skilled_proposer_bridge.py +381 -0
  364. package/vendor/orizu-gepa/src/orizu_gepa_connector/stop_conditions.py +55 -0
  365. package/vendor/orizu-gepa/src/orizu_gepa_connector/translator.py +203 -0
  366. package/vendor/orizu-gepa-python/pyproject.toml +1 -1
  367. package/vendor/orizu-gepa-python/src/orizu_gepa/cli.py +1 -0
  368. package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +52 -7
  369. package/vendor/orizu-gepa-python/src/orizu_gepa/local_log.py +52 -0
  370. package/vendor/orizu-gepa-python/src/orizu_gepa/optimizer.py +10 -5
  371. package/vendor/orizu-gepa-python/src/orizu_gepa/reflection.py +323 -68
  372. package/vendor/orizu-gepa-python/src/orizu_gepa/runner.py +207 -13
  373. package/vendor/skills/orizu-cli/SKILL.md +3 -1
  374. package/vendor/skills/orizu-cli/references/cli-reference.md +85 -1
  375. package/vendor/skills/orizu-cli/references/migrate-existing-gepa-setup.md +303 -0
  376. package/vendor/skills/orizu-cli/references/optimization-reports.md +1 -1
  377. package/vendor/skills/orizu-cli/references/optimization-with-gepa.md +26 -1
  378. 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()