patchground 0.1.0__tar.gz

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 (400) hide show
  1. patchground-0.1.0/.githooks/pre-commit +35 -0
  2. patchground-0.1.0/.githooks/pre-push +22 -0
  3. patchground-0.1.0/.github/CONTRIBUTING.md +32 -0
  4. patchground-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +29 -0
  5. patchground-0.1.0/.github/ISSUE_TEMPLATE/challenge_feedback.yml +18 -0
  6. patchground-0.1.0/.github/ISSUE_TEMPLATE/config.yml +8 -0
  7. patchground-0.1.0/.github/SECURITY.md +19 -0
  8. patchground-0.1.0/.github/pull_request_template.md +10 -0
  9. patchground-0.1.0/.github/workflows/action-smoke.yml +68 -0
  10. patchground-0.1.0/.github/workflows/ci.yml +49 -0
  11. patchground-0.1.0/.github/workflows/oom-stress.yml +46 -0
  12. patchground-0.1.0/.github/workflows/registry.yml +96 -0
  13. patchground-0.1.0/.github/workflows/release.yml +98 -0
  14. patchground-0.1.0/.github/workflows/secret-scan.yml +25 -0
  15. patchground-0.1.0/.github/workflows/web.yml +26 -0
  16. patchground-0.1.0/.gitignore +61 -0
  17. patchground-0.1.0/.gitleaks.toml +31 -0
  18. patchground-0.1.0/.python-version +1 -0
  19. patchground-0.1.0/AGENTS.md +79 -0
  20. patchground-0.1.0/CHANGELOG.md +45 -0
  21. patchground-0.1.0/LICENSE +202 -0
  22. patchground-0.1.0/PKG-INFO +258 -0
  23. patchground-0.1.0/README.md +219 -0
  24. patchground-0.1.0/action.yml +94 -0
  25. patchground-0.1.0/architecture.md +142 -0
  26. patchground-0.1.0/docs/authoring.md +94 -0
  27. patchground-0.1.0/docs/contracts/challenge-format.md +111 -0
  28. patchground-0.1.0/docs/contracts/cli.md +137 -0
  29. patchground-0.1.0/docs/contracts/execution.md +72 -0
  30. patchground-0.1.0/docs/contracts/feedback.md +88 -0
  31. patchground-0.1.0/docs/contracts/security.md +63 -0
  32. patchground-0.1.0/docs/contracts/verification.md +84 -0
  33. patchground-0.1.0/docs/contributing.md +50 -0
  34. patchground-0.1.0/docs/delivery-plan.md +166 -0
  35. patchground-0.1.0/docs/evaluations/ai-debrief-smoke.md +54 -0
  36. patchground-0.1.0/docs/evaluations/authoring-smoke.md +43 -0
  37. patchground-0.1.0/docs/evaluations/automation-smoke.md +39 -0
  38. patchground-0.1.0/docs/evaluations/ci-portability.md +54 -0
  39. patchground-0.1.0/docs/evaluations/cli-artifact-manifest.json +30 -0
  40. patchground-0.1.0/docs/evaluations/coverage-smoke.md +47 -0
  41. patchground-0.1.0/docs/evaluations/fresh-user-wheel-trial.md +78 -0
  42. patchground-0.1.0/docs/evaluations/human-trial-findings.md +32 -0
  43. patchground-0.1.0/docs/evaluations/human-trial-kit.md +63 -0
  44. patchground-0.1.0/docs/evaluations/learner-cohort-registration.json +57 -0
  45. patchground-0.1.0/docs/evaluations/learner-cohort-results.json +924 -0
  46. patchground-0.1.0/docs/evaluations/learner-cohort.md +108 -0
  47. patchground-0.1.0/docs/evaluations/learner-instructions.md +56 -0
  48. patchground-0.1.0/docs/evaluations/release-candidate-check.sh +93 -0
  49. patchground-0.1.0/docs/evaluations/release-candidate.md +76 -0
  50. patchground-0.1.0/docs/first-session.md +123 -0
  51. patchground-0.1.0/docs/launch-brief.md +79 -0
  52. patchground-0.1.0/docs/packs.md +69 -0
  53. patchground-0.1.0/docs/publishing.md +40 -0
  54. patchground-0.1.0/docs/spec.md +683 -0
  55. patchground-0.1.0/docs/synthetic-evaluation.md +88 -0
  56. patchground-0.1.0/docs/validation-action.md +95 -0
  57. patchground-0.1.0/examples/README.md +10 -0
  58. patchground-0.1.0/examples/cache-expiry/TICKET.md +49 -0
  59. patchground-0.1.0/examples/cache-expiry/challenge.yaml +32 -0
  60. patchground-0.1.0/examples/cache-expiry/checks/hidden/tests/test_cache_hidden.py +164 -0
  61. patchground-0.1.0/examples/cache-expiry/checks/visible/tests/test_cache.py +56 -0
  62. patchground-0.1.0/examples/cache-expiry/debrief/questions.md +4 -0
  63. patchground-0.1.0/examples/cache-expiry/environment/Dockerfile +3 -0
  64. patchground-0.1.0/examples/cache-expiry/solution/reference.patch +41 -0
  65. patchground-0.1.0/examples/cache-expiry/workspace/pyproject.toml +8 -0
  66. patchground-0.1.0/examples/cache-expiry/workspace/src/profilecache/__init__.py +6 -0
  67. patchground-0.1.0/examples/cache-expiry/workspace/src/profilecache/cache.py +67 -0
  68. patchground-0.1.0/examples/cache-expiry/workspace/src/profilecache/client.py +38 -0
  69. patchground-0.1.0/examples/csv-import-row-errors/TICKET.md +97 -0
  70. patchground-0.1.0/examples/csv-import-row-errors/challenge.yaml +33 -0
  71. patchground-0.1.0/examples/csv-import-row-errors/checks/hidden/tests/test_import_hidden.py +359 -0
  72. patchground-0.1.0/examples/csv-import-row-errors/checks/visible/tests/test_import.py +34 -0
  73. patchground-0.1.0/examples/csv-import-row-errors/debrief/questions.md +5 -0
  74. patchground-0.1.0/examples/csv-import-row-errors/environment/Dockerfile +3 -0
  75. patchground-0.1.0/examples/csv-import-row-errors/solution/reference.patch +74 -0
  76. patchground-0.1.0/examples/csv-import-row-errors/workspace/pyproject.toml +8 -0
  77. patchground-0.1.0/examples/csv-import-row-errors/workspace/src/stockimport/__init__.py +4 -0
  78. patchground-0.1.0/examples/csv-import-row-errors/workspace/src/stockimport/importer.py +69 -0
  79. patchground-0.1.0/examples/csv-import-row-errors/workspace/src/stockimport/models.py +32 -0
  80. patchground-0.1.0/examples/duplicate-charges-on-retry/TICKET.md +28 -0
  81. patchground-0.1.0/examples/duplicate-charges-on-retry/challenge.yaml +30 -0
  82. patchground-0.1.0/examples/duplicate-charges-on-retry/checks/hidden/tests/test_payment_regressions.py +87 -0
  83. patchground-0.1.0/examples/duplicate-charges-on-retry/checks/visible/tests/test_checkout.py +34 -0
  84. patchground-0.1.0/examples/duplicate-charges-on-retry/debrief/questions.md +6 -0
  85. patchground-0.1.0/examples/duplicate-charges-on-retry/environment/Dockerfile +3 -0
  86. patchground-0.1.0/examples/duplicate-charges-on-retry/solution/reference.patch +11 -0
  87. patchground-0.1.0/examples/duplicate-charges-on-retry/workspace/pyproject.toml +8 -0
  88. patchground-0.1.0/examples/duplicate-charges-on-retry/workspace/src/payments/__init__.py +1 -0
  89. patchground-0.1.0/examples/duplicate-charges-on-retry/workspace/src/payments/gateway.py +38 -0
  90. patchground-0.1.0/examples/duplicate-charges-on-retry/workspace/src/payments/models.py +19 -0
  91. patchground-0.1.0/examples/duplicate-charges-on-retry/workspace/src/payments/service.py +23 -0
  92. patchground-0.1.0/examples/event-bus-subscriptions/TICKET.md +108 -0
  93. patchground-0.1.0/examples/event-bus-subscriptions/challenge.yaml +33 -0
  94. patchground-0.1.0/examples/event-bus-subscriptions/checks/hidden/tests/event-bus-contract.test.ts +566 -0
  95. patchground-0.1.0/examples/event-bus-subscriptions/checks/visible/tests/event-bus.test.ts +70 -0
  96. patchground-0.1.0/examples/event-bus-subscriptions/debrief/questions.md +5 -0
  97. patchground-0.1.0/examples/event-bus-subscriptions/environment/Dockerfile +7 -0
  98. patchground-0.1.0/examples/event-bus-subscriptions/environment/package-lock.json +1054 -0
  99. patchground-0.1.0/examples/event-bus-subscriptions/environment/package.json +13 -0
  100. patchground-0.1.0/examples/event-bus-subscriptions/environment/vitest-runner.mjs +124 -0
  101. patchground-0.1.0/examples/event-bus-subscriptions/solution/reference.patch +82 -0
  102. patchground-0.1.0/examples/event-bus-subscriptions/workspace/package.json +6 -0
  103. patchground-0.1.0/examples/event-bus-subscriptions/workspace/src/bus.ts +32 -0
  104. patchground-0.1.0/examples/order-notifications-boundary/TICKET.md +51 -0
  105. patchground-0.1.0/examples/order-notifications-boundary/challenge.yaml +32 -0
  106. patchground-0.1.0/examples/order-notifications-boundary/checks/hidden/tests/test_boundary_hidden.py +87 -0
  107. patchground-0.1.0/examples/order-notifications-boundary/checks/visible/tests/test_confirmation.py +41 -0
  108. patchground-0.1.0/examples/order-notifications-boundary/debrief/questions.md +5 -0
  109. patchground-0.1.0/examples/order-notifications-boundary/environment/Dockerfile +3 -0
  110. patchground-0.1.0/examples/order-notifications-boundary/solution/reference.patch +77 -0
  111. patchground-0.1.0/examples/order-notifications-boundary/workspace/pyproject.toml +8 -0
  112. patchground-0.1.0/examples/order-notifications-boundary/workspace/src/orders/__init__.py +6 -0
  113. patchground-0.1.0/examples/order-notifications-boundary/workspace/src/orders/app.py +9 -0
  114. patchground-0.1.0/examples/order-notifications-boundary/workspace/src/orders/models.py +25 -0
  115. patchground-0.1.0/examples/order-notifications-boundary/workspace/src/orders/service.py +31 -0
  116. patchground-0.1.0/examples/order-notifications-boundary/workspace/src/orders/smtp_gateway.py +19 -0
  117. patchground-0.1.0/examples/order-notifications-boundary/workspace/src/orders/sqlite_store.py +44 -0
  118. patchground-0.1.0/examples/pagination-last-page/TICKET.md +44 -0
  119. patchground-0.1.0/examples/pagination-last-page/challenge.yaml +31 -0
  120. patchground-0.1.0/examples/pagination-last-page/checks/hidden/tests/test_pagination_hidden.py +32 -0
  121. patchground-0.1.0/examples/pagination-last-page/checks/visible/tests/test_pagination.py +37 -0
  122. patchground-0.1.0/examples/pagination-last-page/debrief/questions.md +9 -0
  123. patchground-0.1.0/examples/pagination-last-page/environment/Dockerfile +3 -0
  124. patchground-0.1.0/examples/pagination-last-page/solution/reference.patch +22 -0
  125. patchground-0.1.0/examples/pagination-last-page/workspace/pyproject.toml +8 -0
  126. patchground-0.1.0/examples/pagination-last-page/workspace/src/catalog/__init__.py +1 -0
  127. patchground-0.1.0/examples/pagination-last-page/workspace/src/catalog/pagination.py +52 -0
  128. patchground-0.1.0/examples/release-version-ordering/TICKET.md +72 -0
  129. patchground-0.1.0/examples/release-version-ordering/challenge.yaml +31 -0
  130. patchground-0.1.0/examples/release-version-ordering/checks/hidden/tests/releasekit_contract.test.ts +92 -0
  131. patchground-0.1.0/examples/release-version-ordering/checks/visible/tests/releasekit.test.ts +31 -0
  132. patchground-0.1.0/examples/release-version-ordering/debrief/questions.md +9 -0
  133. patchground-0.1.0/examples/release-version-ordering/environment/Dockerfile +7 -0
  134. patchground-0.1.0/examples/release-version-ordering/environment/package-lock.json +1054 -0
  135. patchground-0.1.0/examples/release-version-ordering/environment/package.json +13 -0
  136. patchground-0.1.0/examples/release-version-ordering/environment/vitest-runner.mjs +124 -0
  137. patchground-0.1.0/examples/release-version-ordering/solution/reference.patch +36 -0
  138. patchground-0.1.0/examples/release-version-ordering/workspace/README.md +7 -0
  139. patchground-0.1.0/examples/release-version-ordering/workspace/src/releasekit/index.ts +2 -0
  140. patchground-0.1.0/examples/release-version-ordering/workspace/src/releasekit/ordering.ts +41 -0
  141. patchground-0.1.0/examples/release-version-ordering/workspace/src/releasekit/parse.ts +24 -0
  142. patchground-0.1.0/goal-and-requirements.md +187 -0
  143. patchground-0.1.0/pyproject.toml +102 -0
  144. patchground-0.1.0/registry/README.md +43 -0
  145. patchground-0.1.0/registry/SPEC.md +79 -0
  146. patchground-0.1.0/registry/crawl.py +627 -0
  147. patchground-0.1.0/registry/index.json +16 -0
  148. patchground-0.1.0/registry/sources.yaml +6 -0
  149. patchground-0.1.0/registry/status.json +6 -0
  150. patchground-0.1.0/schema/challenge-0.1.schema.json +376 -0
  151. patchground-0.1.0/schema/pack-0.1.schema.json +50 -0
  152. patchground-0.1.0/src/patchground/__init__.py +10 -0
  153. patchground-0.1.0/src/patchground/__main__.py +3 -0
  154. patchground-0.1.0/src/patchground/authoring/SPEC.md +49 -0
  155. patchground-0.1.0/src/patchground/authoring/__init__.py +5 -0
  156. patchground-0.1.0/src/patchground/authoring/create.py +179 -0
  157. patchground-0.1.0/src/patchground/authoring/templates/python-debug/TICKET.md.tmpl +11 -0
  158. patchground-0.1.0/src/patchground/authoring/templates/python-debug/challenge.yaml.tmpl +21 -0
  159. patchground-0.1.0/src/patchground/authoring/templates/python-debug/checks/hidden/tests/test_starter_hidden.py.tmpl +13 -0
  160. patchground-0.1.0/src/patchground/authoring/templates/python-debug/checks/visible/tests/test_starter.py.tmpl +9 -0
  161. patchground-0.1.0/src/patchground/authoring/templates/python-debug/environment/Dockerfile.tmpl +3 -0
  162. patchground-0.1.0/src/patchground/authoring/templates/python-debug/solution/reference.patch.tmpl +8 -0
  163. patchground-0.1.0/src/patchground/authoring/templates/python-debug/workspace/pyproject.toml.tmpl +8 -0
  164. patchground-0.1.0/src/patchground/authoring/templates/python-debug/workspace/src/starter.py.tmpl +3 -0
  165. patchground-0.1.0/src/patchground/authoring/templates/python-feature/TICKET.md.tmpl +13 -0
  166. patchground-0.1.0/src/patchground/authoring/templates/python-feature/challenge.yaml.tmpl +22 -0
  167. patchground-0.1.0/src/patchground/authoring/templates/python-feature/checks/hidden/tests/test_starter_hidden.py.tmpl +13 -0
  168. patchground-0.1.0/src/patchground/authoring/templates/python-feature/checks/visible/tests/test_starter.py.tmpl +9 -0
  169. patchground-0.1.0/src/patchground/authoring/templates/python-feature/environment/Dockerfile.tmpl +3 -0
  170. patchground-0.1.0/src/patchground/authoring/templates/python-feature/solution/reference.patch.tmpl +9 -0
  171. patchground-0.1.0/src/patchground/authoring/templates/python-feature/workspace/pyproject.toml.tmpl +8 -0
  172. patchground-0.1.0/src/patchground/authoring/templates/python-feature/workspace/src/starter.py.tmpl +8 -0
  173. patchground-0.1.0/src/patchground/authoring/templates/typescript-debug/TICKET.md.tmpl +11 -0
  174. patchground-0.1.0/src/patchground/authoring/templates/typescript-debug/challenge.yaml.tmpl +21 -0
  175. patchground-0.1.0/src/patchground/authoring/templates/typescript-debug/checks/hidden/tests/starter-hidden.test.ts.tmpl +14 -0
  176. patchground-0.1.0/src/patchground/authoring/templates/typescript-debug/checks/visible/tests/starter.test.ts.tmpl +10 -0
  177. patchground-0.1.0/src/patchground/authoring/templates/typescript-debug/solution/reference.patch.tmpl +9 -0
  178. patchground-0.1.0/src/patchground/authoring/templates/typescript-debug/workspace/package.json.tmpl +6 -0
  179. patchground-0.1.0/src/patchground/authoring/templates/typescript-debug/workspace/src/starter.ts.tmpl +4 -0
  180. patchground-0.1.0/src/patchground/authoring/templates/typescript-feature/TICKET.md.tmpl +13 -0
  181. patchground-0.1.0/src/patchground/authoring/templates/typescript-feature/challenge.yaml.tmpl +22 -0
  182. patchground-0.1.0/src/patchground/authoring/templates/typescript-feature/checks/hidden/tests/starter-hidden.test.ts.tmpl +14 -0
  183. patchground-0.1.0/src/patchground/authoring/templates/typescript-feature/checks/visible/tests/starter.test.ts.tmpl +10 -0
  184. patchground-0.1.0/src/patchground/authoring/templates/typescript-feature/solution/reference.patch.tmpl +10 -0
  185. patchground-0.1.0/src/patchground/authoring/templates/typescript-feature/workspace/package.json.tmpl +6 -0
  186. patchground-0.1.0/src/patchground/authoring/templates/typescript-feature/workspace/src/starter.ts.tmpl +9 -0
  187. patchground-0.1.0/src/patchground/automation/SPEC.md +84 -0
  188. patchground-0.1.0/src/patchground/automation/__init__.py +5 -0
  189. patchground-0.1.0/src/patchground/automation/__main__.py +131 -0
  190. patchground-0.1.0/src/patchground/automation/workflow.py +214 -0
  191. patchground-0.1.0/src/patchground/cli/SPEC.md +52 -0
  192. patchground-0.1.0/src/patchground/cli/__init__.py +1 -0
  193. patchground-0.1.0/src/patchground/cli/app.py +59 -0
  194. patchground-0.1.0/src/patchground/cli/authoring.py +41 -0
  195. patchground-0.1.0/src/patchground/cli/consent.py +77 -0
  196. patchground-0.1.0/src/patchground/cli/discovery.py +189 -0
  197. patchground-0.1.0/src/patchground/cli/doctor.py +249 -0
  198. patchground-0.1.0/src/patchground/cli/packs.py +142 -0
  199. patchground-0.1.0/src/patchground/cli/practice.py +141 -0
  200. patchground-0.1.0/src/patchground/cli/render.py +58 -0
  201. patchground-0.1.0/src/patchground/cli/reports.py +53 -0
  202. patchground-0.1.0/src/patchground/cli/sessions.py +195 -0
  203. patchground-0.1.0/src/patchground/cli/validate.py +117 -0
  204. patchground-0.1.0/src/patchground/packs/SPEC.md +82 -0
  205. patchground-0.1.0/src/patchground/packs/__init__.py +23 -0
  206. patchground-0.1.0/src/patchground/packs/builtin.py +53 -0
  207. patchground-0.1.0/src/patchground/packs/discovery.py +285 -0
  208. patchground-0.1.0/src/patchground/packs/execution.py +420 -0
  209. patchground-0.1.0/src/patchground/packs/models.py +98 -0
  210. patchground-0.1.0/src/patchground/packs/registry.py +318 -0
  211. patchground-0.1.0/src/patchground/packs/source.py +230 -0
  212. patchground-0.1.0/src/patchground/packs/store.py +420 -0
  213. patchground-0.1.0/src/patchground/py.typed +0 -0
  214. patchground-0.1.0/src/patchground/report/SPEC.md +57 -0
  215. patchground-0.1.0/src/patchground/report/__init__.py +6 -0
  216. patchground-0.1.0/src/patchground/report/analysis.py +188 -0
  217. patchground-0.1.0/src/patchground/report/models.py +59 -0
  218. patchground-0.1.0/src/patchground/report/regression.py +597 -0
  219. patchground-0.1.0/src/patchground/report/render.py +92 -0
  220. patchground-0.1.0/src/patchground/report/workflow.py +84 -0
  221. patchground-0.1.0/src/patchground/runner_assets/Dockerfile +7 -0
  222. patchground-0.1.0/src/patchground/runner_assets/SPEC.md +46 -0
  223. patchground-0.1.0/src/patchground/runner_assets/__init__.py +16 -0
  224. patchground-0.1.0/src/patchground/runner_assets/package-lock.json +1054 -0
  225. patchground-0.1.0/src/patchground/runner_assets/package.json +13 -0
  226. patchground-0.1.0/src/patchground/runner_assets/vitest-runner.mjs +124 -0
  227. patchground-0.1.0/src/patchground/safeio/SPEC.md +26 -0
  228. patchground-0.1.0/src/patchground/safeio/__init__.py +115 -0
  229. patchground-0.1.0/src/patchground/sandbox/SPEC.md +80 -0
  230. patchground-0.1.0/src/patchground/sandbox/__init__.py +39 -0
  231. patchground-0.1.0/src/patchground/sandbox/_engine.py +23 -0
  232. patchground-0.1.0/src/patchground/sandbox/build.py +202 -0
  233. patchground-0.1.0/src/patchground/sandbox/errors.py +23 -0
  234. patchground-0.1.0/src/patchground/sandbox/junit.py +76 -0
  235. patchground-0.1.0/src/patchground/sandbox/probe.py +118 -0
  236. patchground-0.1.0/src/patchground/sandbox/run.py +535 -0
  237. patchground-0.1.0/src/patchground/sessions/SPEC.md +219 -0
  238. patchground-0.1.0/src/patchground/sessions/__init__.py +44 -0
  239. patchground-0.1.0/src/patchground/sessions/diagnostics.py +145 -0
  240. patchground-0.1.0/src/patchground/sessions/files.py +113 -0
  241. patchground-0.1.0/src/patchground/sessions/git.py +70 -0
  242. patchground-0.1.0/src/patchground/sessions/history.py +386 -0
  243. patchground-0.1.0/src/patchground/sessions/history_ignore.py +279 -0
  244. patchground-0.1.0/src/patchground/sessions/models.py +126 -0
  245. patchground-0.1.0/src/patchground/sessions/practice.py +202 -0
  246. patchground-0.1.0/src/patchground/sessions/reset.py +121 -0
  247. patchground-0.1.0/src/patchground/sessions/store.py +203 -0
  248. patchground-0.1.0/src/patchground/sessions/submission.py +344 -0
  249. patchground-0.1.0/src/patchground/sessions/workflow.py +297 -0
  250. patchground-0.1.0/src/patchground/spec/SPEC.md +37 -0
  251. patchground-0.1.0/src/patchground/spec/__init__.py +35 -0
  252. patchground-0.1.0/src/patchground/spec/_schema.py +53 -0
  253. patchground-0.1.0/src/patchground/spec/loader.py +713 -0
  254. patchground-0.1.0/src/patchground/spec/models.py +394 -0
  255. patchground-0.1.0/src/patchground/spec/schema.py +50 -0
  256. patchground-0.1.0/src/patchground/verify/SPEC.md +84 -0
  257. patchground-0.1.0/src/patchground/verify/__init__.py +29 -0
  258. patchground-0.1.0/src/patchground/verify/errors.py +10 -0
  259. patchground-0.1.0/src/patchground/verify/pytest_adapter.py +95 -0
  260. patchground-0.1.0/src/patchground/verify/record.py +94 -0
  261. patchground-0.1.0/src/patchground/verify/reference.py +158 -0
  262. patchground-0.1.0/src/patchground/verify/result.py +199 -0
  263. patchground-0.1.0/src/patchground/verify/runners.py +47 -0
  264. patchground-0.1.0/src/patchground/verify/tampering.py +106 -0
  265. patchground-0.1.0/src/patchground/verify/validate.py +686 -0
  266. patchground-0.1.0/src/patchground/verify/vitest_adapter.py +252 -0
  267. patchground-0.1.0/tests/conftest.py +99 -0
  268. patchground-0.1.0/tests/fixtures/README.md +31 -0
  269. patchground-0.1.0/tests/fixtures/broken-state-passes/TICKET.md +41 -0
  270. patchground-0.1.0/tests/fixtures/broken-state-passes/challenge.yaml +31 -0
  271. patchground-0.1.0/tests/fixtures/broken-state-passes/checks/hidden/tests/test_pagination_hidden.py +16 -0
  272. patchground-0.1.0/tests/fixtures/broken-state-passes/checks/visible/tests/test_pagination.py +37 -0
  273. patchground-0.1.0/tests/fixtures/broken-state-passes/debrief/questions.md +9 -0
  274. patchground-0.1.0/tests/fixtures/broken-state-passes/environment/Dockerfile +3 -0
  275. patchground-0.1.0/tests/fixtures/broken-state-passes/solution/reference.patch +22 -0
  276. patchground-0.1.0/tests/fixtures/broken-state-passes/workspace/pyproject.toml +8 -0
  277. patchground-0.1.0/tests/fixtures/broken-state-passes/workspace/src/catalog/__init__.py +1 -0
  278. patchground-0.1.0/tests/fixtures/broken-state-passes/workspace/src/catalog/pagination.py +52 -0
  279. patchground-0.1.0/tests/fixtures/build-fails/TICKET.md +41 -0
  280. patchground-0.1.0/tests/fixtures/build-fails/challenge.yaml +31 -0
  281. patchground-0.1.0/tests/fixtures/build-fails/checks/hidden/tests/test_pagination_hidden.py +32 -0
  282. patchground-0.1.0/tests/fixtures/build-fails/checks/visible/tests/test_pagination.py +37 -0
  283. patchground-0.1.0/tests/fixtures/build-fails/debrief/questions.md +9 -0
  284. patchground-0.1.0/tests/fixtures/build-fails/environment/Dockerfile +2 -0
  285. patchground-0.1.0/tests/fixtures/build-fails/solution/reference.patch +22 -0
  286. patchground-0.1.0/tests/fixtures/build-fails/workspace/pyproject.toml +8 -0
  287. patchground-0.1.0/tests/fixtures/build-fails/workspace/src/catalog/__init__.py +1 -0
  288. patchground-0.1.0/tests/fixtures/build-fails/workspace/src/catalog/pagination.py +52 -0
  289. patchground-0.1.0/tests/fixtures/conftest.py +3 -0
  290. patchground-0.1.0/tests/fixtures/invalid-manifest/TICKET.md +41 -0
  291. patchground-0.1.0/tests/fixtures/invalid-manifest/challenge.yaml +24 -0
  292. patchground-0.1.0/tests/fixtures/invalid-manifest/checks/hidden/tests/test_pagination_hidden.py +32 -0
  293. patchground-0.1.0/tests/fixtures/invalid-manifest/checks/visible/tests/test_pagination.py +37 -0
  294. patchground-0.1.0/tests/fixtures/invalid-manifest/debrief/questions.md +9 -0
  295. patchground-0.1.0/tests/fixtures/invalid-manifest/environment/Dockerfile +3 -0
  296. patchground-0.1.0/tests/fixtures/invalid-manifest/solution/reference.patch +22 -0
  297. patchground-0.1.0/tests/fixtures/invalid-manifest/workspace/pyproject.toml +8 -0
  298. patchground-0.1.0/tests/fixtures/invalid-manifest/workspace/src/catalog/__init__.py +1 -0
  299. patchground-0.1.0/tests/fixtures/invalid-manifest/workspace/src/catalog/pagination.py +52 -0
  300. patchground-0.1.0/tests/fixtures/reference-fails/TICKET.md +41 -0
  301. patchground-0.1.0/tests/fixtures/reference-fails/challenge.yaml +31 -0
  302. patchground-0.1.0/tests/fixtures/reference-fails/checks/hidden/tests/test_pagination_hidden.py +32 -0
  303. patchground-0.1.0/tests/fixtures/reference-fails/checks/visible/tests/test_pagination.py +37 -0
  304. patchground-0.1.0/tests/fixtures/reference-fails/debrief/questions.md +9 -0
  305. patchground-0.1.0/tests/fixtures/reference-fails/environment/Dockerfile +3 -0
  306. patchground-0.1.0/tests/fixtures/reference-fails/solution/reference.patch +13 -0
  307. patchground-0.1.0/tests/fixtures/reference-fails/workspace/pyproject.toml +8 -0
  308. patchground-0.1.0/tests/fixtures/reference-fails/workspace/src/catalog/__init__.py +1 -0
  309. patchground-0.1.0/tests/fixtures/reference-fails/workspace/src/catalog/pagination.py +52 -0
  310. patchground-0.1.0/tests/fixtures/services-unsupported/TICKET.md +41 -0
  311. patchground-0.1.0/tests/fixtures/services-unsupported/challenge.yaml +31 -0
  312. patchground-0.1.0/tests/fixtures/services-unsupported/checks/hidden/tests/test_pagination_hidden.py +32 -0
  313. patchground-0.1.0/tests/fixtures/services-unsupported/checks/visible/tests/test_pagination.py +37 -0
  314. patchground-0.1.0/tests/fixtures/services-unsupported/debrief/questions.md +9 -0
  315. patchground-0.1.0/tests/fixtures/services-unsupported/environment/Dockerfile +3 -0
  316. patchground-0.1.0/tests/fixtures/services-unsupported/solution/reference.patch +22 -0
  317. patchground-0.1.0/tests/fixtures/services-unsupported/workspace/pyproject.toml +8 -0
  318. patchground-0.1.0/tests/fixtures/services-unsupported/workspace/src/catalog/__init__.py +1 -0
  319. patchground-0.1.0/tests/fixtures/services-unsupported/workspace/src/catalog/pagination.py +52 -0
  320. patchground-0.1.0/tests/integration/test_authoring.py +39 -0
  321. patchground-0.1.0/tests/integration/test_automation.py +62 -0
  322. patchground-0.1.0/tests/integration/test_history.py +68 -0
  323. patchground-0.1.0/tests/integration/test_packs.py +51 -0
  324. patchground-0.1.0/tests/integration/test_practice.py +60 -0
  325. patchground-0.1.0/tests/integration/test_regression_evidence.py +158 -0
  326. patchground-0.1.0/tests/integration/test_reports.py +73 -0
  327. patchground-0.1.0/tests/integration/test_sandbox_docker.py +644 -0
  328. patchground-0.1.0/tests/integration/test_sessions.py +93 -0
  329. patchground-0.1.0/tests/integration/test_validate_examples.py +152 -0
  330. patchground-0.1.0/tests/integration/test_vitest_runner.py +259 -0
  331. patchground-0.1.0/tests/unit/authoring/test_create.py +277 -0
  332. patchground-0.1.0/tests/unit/authoring/test_wheel.py +30 -0
  333. patchground-0.1.0/tests/unit/automation/__init__.py +1 -0
  334. patchground-0.1.0/tests/unit/automation/test_action.py +125 -0
  335. patchground-0.1.0/tests/unit/automation/test_entrypoint.py +188 -0
  336. patchground-0.1.0/tests/unit/automation/test_workflow.py +305 -0
  337. patchground-0.1.0/tests/unit/cli/test_cli_authoring.py +91 -0
  338. patchground-0.1.0/tests/unit/cli/test_cli_discovery.py +154 -0
  339. patchground-0.1.0/tests/unit/cli/test_cli_doctor.py +91 -0
  340. patchground-0.1.0/tests/unit/cli/test_cli_m1_app.py +35 -0
  341. patchground-0.1.0/tests/unit/cli/test_cli_packs.py +107 -0
  342. patchground-0.1.0/tests/unit/cli/test_cli_practice.py +87 -0
  343. patchground-0.1.0/tests/unit/cli/test_cli_reset.py +38 -0
  344. patchground-0.1.0/tests/unit/cli/test_cli_sessions.py +100 -0
  345. patchground-0.1.0/tests/unit/cli/test_cli_validate.py +207 -0
  346. patchground-0.1.0/tests/unit/packs/test_builtin.py +83 -0
  347. patchground-0.1.0/tests/unit/packs/test_packs_discovery.py +214 -0
  348. patchground-0.1.0/tests/unit/packs/test_registry.py +878 -0
  349. patchground-0.1.0/tests/unit/packs/test_source.py +252 -0
  350. patchground-0.1.0/tests/unit/packs/test_state_safety.py +75 -0
  351. patchground-0.1.0/tests/unit/registry/__init__.py +0 -0
  352. patchground-0.1.0/tests/unit/registry/test_crawl.py +100 -0
  353. patchground-0.1.0/tests/unit/report/conftest.py +43 -0
  354. patchground-0.1.0/tests/unit/report/test_analysis.py +100 -0
  355. patchground-0.1.0/tests/unit/report/test_cli_reports.py +79 -0
  356. patchground-0.1.0/tests/unit/report/test_regression.py +422 -0
  357. patchground-0.1.0/tests/unit/report/test_vitest_regression.py +275 -0
  358. patchground-0.1.0/tests/unit/report/test_workflow.py +88 -0
  359. patchground-0.1.0/tests/unit/runner_assets/test_assets.py +63 -0
  360. patchground-0.1.0/tests/unit/sandbox/test_sandbox_engine.py +44 -0
  361. patchground-0.1.0/tests/unit/sandbox/test_sandbox_errors.py +244 -0
  362. patchground-0.1.0/tests/unit/sandbox/test_sandbox_junit.py +126 -0
  363. patchground-0.1.0/tests/unit/sandbox/test_sandbox_limits.py +63 -0
  364. patchground-0.1.0/tests/unit/sandbox/test_sandbox_output.py +62 -0
  365. patchground-0.1.0/tests/unit/sandbox/test_sandbox_probe.py +111 -0
  366. patchground-0.1.0/tests/unit/sandbox/test_sandbox_tar.py +185 -0
  367. patchground-0.1.0/tests/unit/sessions/__init__.py +0 -0
  368. patchground-0.1.0/tests/unit/sessions/conftest.py +82 -0
  369. patchground-0.1.0/tests/unit/sessions/test_diagnostics.py +92 -0
  370. patchground-0.1.0/tests/unit/sessions/test_git_safety.py +186 -0
  371. patchground-0.1.0/tests/unit/sessions/test_history.py +542 -0
  372. patchground-0.1.0/tests/unit/sessions/test_practice.py +124 -0
  373. patchground-0.1.0/tests/unit/sessions/test_reset.py +90 -0
  374. patchground-0.1.0/tests/unit/sessions/test_security.py +275 -0
  375. patchground-0.1.0/tests/unit/sessions/test_sessions_root.py +55 -0
  376. patchground-0.1.0/tests/unit/sessions/test_submission.py +387 -0
  377. patchground-0.1.0/tests/unit/sessions/test_workflow.py +376 -0
  378. patchground-0.1.0/tests/unit/spec/test_spec_loader.py +767 -0
  379. patchground-0.1.0/tests/unit/spec/test_spec_schema.py +71 -0
  380. patchground-0.1.0/tests/unit/test_docker_gate.py +41 -0
  381. patchground-0.1.0/tests/unit/test_package.py +10 -0
  382. patchground-0.1.0/tests/unit/verify/test_tampering.py +97 -0
  383. patchground-0.1.0/tests/unit/verify/test_verify_reference.py +152 -0
  384. patchground-0.1.0/tests/unit/verify/test_verify_validate.py +994 -0
  385. patchground-0.1.0/tests/unit/verify/test_vitest_adapter.py +201 -0
  386. patchground-0.1.0/tests/unit/verify/test_vitest_tampering.py +69 -0
  387. patchground-0.1.0/uv.lock +914 -0
  388. patchground-0.1.0/web/README.md +73 -0
  389. patchground-0.1.0/web/SPEC.md +74 -0
  390. patchground-0.1.0/web/package.json +14 -0
  391. patchground-0.1.0/web/public/catalog.json +168 -0
  392. patchground-0.1.0/web/public/favicon.svg +1 -0
  393. patchground-0.1.0/web/public/index.html +325 -0
  394. patchground-0.1.0/web/public/main.js +102 -0
  395. patchground-0.1.0/web/public/status.html +29 -0
  396. patchground-0.1.0/web/public/styles.css +465 -0
  397. patchground-0.1.0/web/scripts/build.mjs +12 -0
  398. patchground-0.1.0/web/scripts/catalog.mjs +279 -0
  399. patchground-0.1.0/web/scripts/site.test.mjs +299 -0
  400. patchground-0.1.0/wrangler.jsonc +10 -0
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env bash
2
+ # Blocks commits that contain secrets or personal data. Fails closed.
3
+ # Enable once per clone: git config core.hooksPath .githooks
4
+ set -euo pipefail
5
+
6
+ fail() { echo "pre-commit: BLOCKED — $*" >&2; exit 1; }
7
+
8
+ command -v gitleaks >/dev/null 2>&1 \
9
+ || fail "gitleaks is not installed (brew install gitleaks). Refusing to commit unscanned."
10
+
11
+ # 1. Commit identity must not expose a personal email.
12
+ email="$(git config user.email || true)"
13
+ [[ "$email" == *@users.noreply.github.com ]] \
14
+ || fail "user.email is '$email'. Use your GitHub noreply address for this repo."
15
+
16
+ # 2. Secret scan of the staged changes.
17
+ gitleaks git --config .gitleaks.toml --pre-commit --staged --redact --no-banner -v \
18
+ || fail "gitleaks found a possible secret in staged changes."
19
+
20
+ # 3. Forbidden file types, even if .gitignore was bypassed with -f.
21
+ forbidden='(^|/)(\.env(\..*)?|.*\.(pem|key|p12|pfx|keystore|jks|gpg)|id_(rsa|ed25519|ecdsa).*|\.netrc|\.pypirc|\.npmrc|\.mcp\.json|credentials.*\.json|service-account.*\.json)$'
22
+ bad="$(git diff --cached --name-only --diff-filter=ACMR | grep -E "$forbidden" | grep -v '\.env\.example$' || true)"
23
+ [[ -z "$bad" ]] || fail "sensitive file(s) staged: $bad"
24
+
25
+ # 4. Personal strings kept OUTSIDE the repo (never committed), one per line.
26
+ # File: .git/info/forbidden-strings
27
+ list="$(git rev-parse --git-dir)/info/forbidden-strings"
28
+ if [[ -s "$list" ]]; then
29
+ while IFS= read -r s; do
30
+ [[ -z "$s" || "$s" == \#* ]] && continue
31
+ if git diff --cached -U0 | grep -E '^\+' | grep -qiF -- "$s"; then
32
+ fail "staged changes contain a string from .git/info/forbidden-strings."
33
+ fi
34
+ done < "$list"
35
+ fi
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ # Last local gate before anything leaves the machine. Fails closed.
3
+ set -euo pipefail
4
+
5
+ fail() { echo "pre-push: BLOCKED — $*" >&2; exit 1; }
6
+
7
+ command -v gitleaks >/dev/null 2>&1 \
8
+ || fail "gitleaks is not installed (brew install gitleaks). Refusing to push unscanned."
9
+
10
+ zero=0000000000000000000000000000000000000000
11
+ while read -r _local_ref local_sha _remote_ref remote_sha; do
12
+ [[ "$local_sha" == "$zero" ]] && continue
13
+ if [[ "$remote_sha" == "$zero" ]]; then range="$local_sha"; else range="$remote_sha..$local_sha"; fi
14
+
15
+ # Every pushed commit must use a noreply identity.
16
+ leaked="$(git log --format='%ae%n%ce' "$range" | sort -u | grep -v '@users.noreply.github.com$' || true)"
17
+ [[ -z "$leaked" ]] || fail "commits in $range use a non-noreply email. Rewrite them before pushing."
18
+
19
+ # Secret scan of every pushed commit.
20
+ gitleaks git --config .gitleaks.toml --log-opts="$range" --redact --no-banner -v \
21
+ || fail "gitleaks found a possible secret in $range."
22
+ done
@@ -0,0 +1,32 @@
1
+ # Contributing to Patchground
2
+
3
+ Thanks for helping. There are two ways to contribute.
4
+
5
+ ## Improve the CLI
6
+
7
+ ```sh
8
+ git clone https://github.com/franndeli/patchground && cd patchground
9
+ uv sync
10
+ git config core.hooksPath .githooks # needs gitleaks: brew install gitleaks
11
+ uv run ruff check . && uv run ruff format --check . && uv run mypy
12
+ uv run pytest # Docker tests are skipped without an engine
13
+ ```
14
+
15
+ - Read the `SPEC.md` of the module you change and the contracts in `docs/contracts/` it implements.
16
+ A change that alters a boundary or contract updates the owning spec in the same pull request.
17
+ - Challenge commands only ever run in containers. Never print hidden checks or copy `solution/` into
18
+ a workspace.
19
+ - Use obviously fake values in tests (`sk-test-not-a-real-key`). Never commit secrets or personal
20
+ data.
21
+ - Keep pull requests small, with tests for new behaviour.
22
+
23
+ ## Write a challenge
24
+
25
+ See [docs/contributing.md](../docs/contributing.md) and the
26
+ [authoring guide](../docs/authoring.md). `pgnd new` creates a runnable starter, and `pgnd validate`
27
+ proves it in Docker.
28
+
29
+ ## Report a problem
30
+
31
+ Open an issue using one of the templates. For security problems, follow [SECURITY.md](SECURITY.md)
32
+ instead of opening a public issue.
@@ -0,0 +1,29 @@
1
+ name: Bug report
2
+ description: Something in the CLI did not behave as documented.
3
+ labels: [bug]
4
+ body:
5
+ - type: textarea
6
+ id: what
7
+ attributes:
8
+ label: What happened?
9
+ description: Include the command you ran and its output. Remove any secrets or personal paths.
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: expected
14
+ attributes:
15
+ label: What did you expect?
16
+ validations:
17
+ required: true
18
+ - type: input
19
+ id: version
20
+ attributes:
21
+ label: Version
22
+ description: Output of `pgnd --version`
23
+ validations:
24
+ required: true
25
+ - type: textarea
26
+ id: doctor
27
+ attributes:
28
+ label: Environment
29
+ description: Output of `pgnd doctor`, plus your OS.
@@ -0,0 +1,18 @@
1
+ name: Challenge feedback
2
+ description: A ticket was unclear, a check seemed wrong or the difficulty felt off.
3
+ labels: [challenge]
4
+ body:
5
+ - type: input
6
+ id: challenge
7
+ attributes:
8
+ label: Challenge ID
9
+ placeholder: pagination-last-page
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: feedback
14
+ attributes:
15
+ label: What happened?
16
+ description: Please don't paste hidden test sources or the reference solution.
17
+ validations:
18
+ required: true
@@ -0,0 +1,8 @@
1
+ blank_issues_enabled: true
2
+ contact_links:
3
+ - name: Questions and ideas
4
+ url: https://github.com/franndeli/patchground/discussions
5
+ about: Ask a question or share an idea for a challenge.
6
+ - name: Report a security vulnerability
7
+ url: https://github.com/franndeli/patchground/security/advisories/new
8
+ about: Please report security issues privately.
@@ -0,0 +1,19 @@
1
+ # Security policy
2
+
3
+ Patchground builds and runs challenge code in Docker. Weaknesses in that boundary matter to us.
4
+ Examples include escaping the sandbox, reaching the network during checks, running pack content
5
+ without consent, leaking hidden checks or reference solutions, and modifying a learner's Git state.
6
+
7
+ ## Reporting a vulnerability
8
+
9
+ Please report privately through
10
+ [GitHub private vulnerability reporting](https://github.com/franndeli/patchground/security/advisories/new).
11
+ Do not open a public issue. Include the Patchground version (`pgnd --version`), your platform and
12
+ container engine, and the smallest reproduction you can share.
13
+
14
+ We aim to acknowledge reports within a week. Please allow us to release a fix before you disclose
15
+ the issue publicly.
16
+
17
+ ## Supported versions
18
+
19
+ Patchground is pre-1.0. Only the latest release receives security fixes.
@@ -0,0 +1,10 @@
1
+ ## Summary
2
+
3
+ <!-- What changes and why. Link the issue: Closes #NNN -->
4
+
5
+ ## Checklist
6
+
7
+ - [ ] `uv run ruff check . && uv run ruff format --check . && uv run mypy`
8
+ - [ ] `uv run pytest` (and `-m docker` if the sandbox, sessions or verify changed)
9
+ - [ ] The owning `SPEC.md` or contract is updated if a boundary or contract changed
10
+ - [ ] No secrets, personal data or hidden-check content
@@ -0,0 +1,68 @@
1
+ name: validation-action-smoke
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ action:
14
+ runs-on: ubuntu-latest
15
+ timeout-minutes: 20
16
+ steps:
17
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
18
+ with:
19
+ persist-credentials: false
20
+ - name: Record checkout Git state
21
+ env:
22
+ SNAPSHOT: ${{ runner.temp }}/patchground-git-before.json
23
+ shell: bash
24
+ run: |
25
+ python3 -I - <<'PY'
26
+ import hashlib
27
+ import json
28
+ import os
29
+ from pathlib import Path
30
+ state = {
31
+ p.as_posix(): hashlib.sha256(p.read_bytes()).hexdigest()
32
+ for p in Path('.git').rglob('*') if p.is_file()
33
+ }
34
+ Path(os.environ['SNAPSHOT']).write_text(json.dumps(state))
35
+ PY
36
+ - name: Exercise the reusable Action
37
+ id: validate
38
+ uses: ./
39
+ with:
40
+ challenge-directory: examples
41
+ # An unavailable base deliberately exercises the conservative validate-all fallback.
42
+ base-commit: "0000000000000000000000000000000000000000"
43
+ allow-execution: "true"
44
+ - name: Check the report and unchanged Git metadata
45
+ if: always()
46
+ env:
47
+ REPORT: ${{ steps.validate.outputs.report-file }}
48
+ SNAPSHOT: ${{ runner.temp }}/patchground-git-before.json
49
+ shell: bash
50
+ run: |
51
+ python3 -I - <<'PY'
52
+ import hashlib
53
+ import json
54
+ import os
55
+ from pathlib import Path
56
+ report = json.loads(Path(os.environ['REPORT']).read_text())
57
+ print(json.dumps(report, ensure_ascii=True))
58
+ assert report['exit_code'] == 0
59
+ assert report['commit'] == os.environ['GITHUB_SHA']
60
+ expected = {p.name for p in Path('examples').iterdir() if (p / 'challenge.yaml').is_file()}
61
+ assert {Path(name).name for name in report['selected']} == expected, expected
62
+ assert report['selection_mode'] == 'fallback_all'
63
+ after = {
64
+ p.as_posix(): hashlib.sha256(p.read_bytes()).hexdigest()
65
+ for p in Path('.git').rglob('*') if p.is_file()
66
+ }
67
+ assert after == json.loads(Path(os.environ['SNAPSHOT']).read_text()), 'Git metadata changed'
68
+ PY
@@ -0,0 +1,49 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ checks:
13
+ name: lint, types, unit tests, schema
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: astral-sh/setup-uv@v6
18
+ with:
19
+ python-version: "3.12"
20
+ - run: uv sync --locked
21
+ - run: uv run ruff check .
22
+ - run: uv run ruff format --check .
23
+ - run: uv run mypy
24
+ - run: uv run pytest -m "not docker"
25
+ - name: exported schema is up to date
26
+ run: |
27
+ uv run python -m patchground.spec.schema schema
28
+ test -z "$(git status --porcelain -- schema/)" || { git status --porcelain -- schema/; echo "schema/ is stale: run 'uv run python -m patchground.spec.schema schema'"; exit 1; }
29
+
30
+ docker:
31
+ name: docker integration, validate examples
32
+ runs-on: ubuntu-latest
33
+ env:
34
+ PATCHGROUND_REQUIRE_DOCKER: "1"
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+ - uses: astral-sh/setup-uv@v6
38
+ with:
39
+ python-version: "3.12"
40
+ - run: uv sync --locked
41
+ - run: uv run pytest -m docker
42
+ - name: every example passes pgnd validate
43
+ run: |
44
+ set -e
45
+ for dir in examples/*/; do
46
+ echo "::group::validate $dir"
47
+ uv run pgnd validate "$dir"
48
+ echo "::endgroup::"
49
+ done
@@ -0,0 +1,46 @@
1
+ name: oom-stress
2
+
3
+ # Manually triggered, bounded repeat of the Docker memory-limit check on fresh GitHub runners.
4
+ # Diagnostic only: it never changes classification semantics or publishes anything.
5
+ on:
6
+ workflow_dispatch:
7
+ inputs:
8
+ repeats:
9
+ description: "Repeats per runner (1-20)"
10
+ default: "10"
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ memory-limit:
17
+ name: memory limit repeat ${{ matrix.shard }}
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 30
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
24
+ env:
25
+ PATCHGROUND_REQUIRE_DOCKER: "1"
26
+ REPEATS: ${{ inputs.repeats }}
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+ - uses: astral-sh/setup-uv@v6
30
+ with:
31
+ python-version: "3.12"
32
+ - run: uv sync --locked
33
+ - name: repeat the strict memory-limit test
34
+ run: |
35
+ set -eu
36
+ case "$REPEATS" in ''|*[!0-9]*) echo "repeats must be a number"; exit 2;; esac
37
+ [ "$REPEATS" -ge 1 ] && [ "$REPEATS" -le 20 ] || { echo "repeats must be 1-20"; exit 2; }
38
+ failures=0
39
+ for i in $(seq "$REPEATS"); do
40
+ if ! uv run pytest -q --tb=short -p no:cacheprovider \
41
+ "tests/integration/test_sandbox_docker.py::test_command_timeout_and_memory_limits"; then
42
+ failures=$((failures + 1))
43
+ fi
44
+ done
45
+ echo "shard ${{ matrix.shard }}: $failures of $REPEATS repeats failed"
46
+ [ "$failures" -eq 0 ]
@@ -0,0 +1,96 @@
1
+ name: registry
2
+
3
+ # Discovers community packs (GitHub topic `patchground-pack` + registry/sources.yaml), re-validates
4
+ # every challenge with Patchground's own validator in Docker, and publishes registry/index.json and
5
+ # registry/status.json. See registry/SPEC.md.
6
+ #
7
+ # Two jobs on purpose: `validate` builds and runs untrusted Dockerfiles, so it gets a read-only
8
+ # token and no secrets; `publish` has write access but only commits files produced by `validate`.
9
+
10
+ on:
11
+ schedule:
12
+ - cron: "17 */6 * * *"
13
+ workflow_dispatch:
14
+ inputs:
15
+ source:
16
+ description: "Process exactly one public https git URL (skips discovery)"
17
+ required: false
18
+ default: ""
19
+
20
+ concurrency:
21
+ group: registry
22
+ cancel-in-progress: false
23
+
24
+ permissions:
25
+ contents: read
26
+
27
+ jobs:
28
+ validate:
29
+ name: discover and validate packs (untrusted content, read-only)
30
+ runs-on: ubuntu-latest
31
+ timeout-minutes: 300
32
+ env:
33
+ PATCHGROUND_REQUIRE_DOCKER: "1"
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+ with:
37
+ persist-credentials: false # untrusted packs build here; leave no token in .git/config
38
+ - uses: astral-sh/setup-uv@v6
39
+ with:
40
+ python-version: "3.12"
41
+ - run: uv sync --locked
42
+ - name: install gitleaks for the pack secret scan
43
+ run: |
44
+ curl -sSfL -o gitleaks.tar.gz https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz
45
+ echo "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb gitleaks.tar.gz" | sha256sum -c -
46
+ tar xzf gitleaks.tar.gz gitleaks
47
+ sudo mv gitleaks /usr/local/bin/gitleaks
48
+ rm gitleaks.tar.gz
49
+ - name: crawl
50
+ env:
51
+ GITHUB_TOKEN: ${{ github.token }} # read-only; search rate limit only; never passed to pack tools
52
+ SOURCE: ${{ inputs.source }} # via env, never interpolated into the script
53
+ run: |
54
+ if [ -n "$SOURCE" ]; then
55
+ case "$SOURCE" in https://*) ;; *) echo "source must be an https URL"; exit 2;; esac
56
+ uv run python registry/crawl.py --source "$SOURCE"
57
+ else
58
+ uv run python registry/crawl.py
59
+ fi
60
+ - uses: actions/upload-artifact@v4
61
+ with:
62
+ name: registry
63
+ path: |
64
+ registry/index.json
65
+ registry/status.json
66
+ retention-days: 7
67
+
68
+ publish:
69
+ name: commit index and status if changed
70
+ needs: validate
71
+ runs-on: ubuntu-latest
72
+ permissions:
73
+ contents: write
74
+ steps:
75
+ - uses: actions/checkout@v4
76
+ - uses: actions/download-artifact@v4
77
+ with:
78
+ name: registry
79
+ path: registry
80
+ - uses: actions/setup-node@v4
81
+ with:
82
+ node-version: "24"
83
+ - name: regenerate the site catalog from the new index
84
+ run: npm --prefix web run catalog && npm --prefix web test
85
+ - name: commit
86
+ run: |
87
+ git config user.name "github-actions[bot]"
88
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
89
+ git add registry/index.json registry/status.json web/public
90
+ if git diff --cached --quiet; then
91
+ echo "registry unchanged"
92
+ exit 0
93
+ fi
94
+ packs="$(python3 -c "import json;print(len(json.load(open('registry/index.json'))['packs']))")"
95
+ git commit -m "Update community pack registry (${packs} verified packs)"
96
+ git push
@@ -0,0 +1,98 @@
1
+ name: release
2
+
3
+ # Publishes a version tag (vX.Y.Z) as a GitHub release and a PyPI package.
4
+ # Nothing here runs on ordinary pushes. Both publishing jobs use protected environments
5
+ # (`release`, `pypi`) that must require a maintainer's approval; see docs/publishing.md.
6
+ # PyPI uses trusted publishing (OIDC): no API token is stored anywhere.
7
+
8
+ on:
9
+ push:
10
+ tags: ["v*"]
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ concurrency:
16
+ group: release-${{ github.ref }}
17
+ cancel-in-progress: false
18
+
19
+ jobs:
20
+ build:
21
+ name: build and verify distributions
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
25
+ with:
26
+ persist-credentials: false
27
+ - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
28
+ with:
29
+ python-version: "3.12"
30
+ - name: tag matches the package version
31
+ env:
32
+ TAG: ${{ github.ref_name }}
33
+ run: |
34
+ version="$(uv run --no-project python -c 'import tomllib;print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')"
35
+ [ "$TAG" = "v$version" ] || { echo "tag $TAG does not match pyproject version $version"; exit 1; }
36
+ - run: uv sync --locked
37
+ - run: uv run pytest -q -m "not docker"
38
+ - run: uv build --out-dir dist
39
+ - name: installed wheel runs, without AI SDKs
40
+ env:
41
+ TAG: ${{ github.ref_name }}
42
+ run: |
43
+ uv venv --python 3.12 /tmp/verify
44
+ uv pip install --python /tmp/verify/bin/python dist/*.whl
45
+ /tmp/verify/bin/pgnd --version | grep -F "${TAG#v}"
46
+ (cd /tmp && /tmp/verify/bin/pgnd list --builtin --json) | grep -F '"pagination-last-page"'
47
+ ! /tmp/verify/bin/python -c "import anthropic" 2>/dev/null
48
+ ! /tmp/verify/bin/python -c "import litellm" 2>/dev/null
49
+ - name: checksums
50
+ run: (cd dist && sha256sum -- * > ../SHA256SUMS) && cat SHA256SUMS
51
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
52
+ with:
53
+ name: dist
54
+ path: |
55
+ dist/
56
+ SHA256SUMS
57
+ retention-days: 30
58
+
59
+ github-release:
60
+ name: GitHub release (approval required)
61
+ needs: build
62
+ runs-on: ubuntu-latest
63
+ environment: release
64
+ permissions:
65
+ contents: write
66
+ steps:
67
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
68
+ with:
69
+ persist-credentials: false
70
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
71
+ with:
72
+ name: dist
73
+ - name: create the release from the CHANGELOG section
74
+ env:
75
+ GH_TOKEN: ${{ github.token }}
76
+ TAG: ${{ github.ref_name }}
77
+ run: |
78
+ awk -v v="${TAG#v}" '$0 ~ "^## \\[?"v"\\]?" {on=1; next} /^## / && on {exit} on' CHANGELOG.md > notes.md
79
+ [ -s notes.md ] || { echo "CHANGELOG.md has no section for ${TAG#v}"; exit 1; }
80
+ gh release create "$TAG" dist/* SHA256SUMS --repo "$GITHUB_REPOSITORY" \
81
+ --title "Patchground ${TAG#v}" --notes-file notes.md --verify-tag
82
+
83
+ pypi:
84
+ name: PyPI (approval required)
85
+ needs: github-release
86
+ runs-on: ubuntu-latest
87
+ environment:
88
+ name: pypi
89
+ url: https://pypi.org/project/patchground/
90
+ permissions:
91
+ id-token: write
92
+ steps:
93
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
94
+ with:
95
+ name: dist
96
+ - uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
97
+ with:
98
+ packages-dir: dist/
@@ -0,0 +1,25 @@
1
+ name: secret-scan
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ gitleaks:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ with:
16
+ fetch-depth: 0
17
+ - name: Scan full history with gitleaks
18
+ run: |
19
+ curl -sSfL https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz \
20
+ | tar xz gitleaks
21
+ ./gitleaks git . --config .gitleaks.toml --redact --no-banner -v
22
+ - name: Reject non-noreply commit emails
23
+ run: |
24
+ bad="$(git log --format='%ae%n%ce' | sort -u | grep -v '@users.noreply.github.com$' | grep -v '^noreply@github.com$' || true)"
25
+ if [ -n "$bad" ]; then echo "Commits with non-noreply emails found"; exit 1; fi
@@ -0,0 +1,26 @@
1
+ name: web
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths: ["web/**", "examples/*/challenge.yaml", "registry/*.json", ".github/workflows/web.yml"]
7
+ pull_request:
8
+ paths: ["web/**", "examples/*/challenge.yaml", "registry/*.json", ".github/workflows/web.yml"]
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ static-site:
15
+ name: public site checks and build
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: actions/setup-node@v4
20
+ with:
21
+ node-version: "24"
22
+ - run: npm --prefix web run check
23
+ - name: catalog is generated from examples/*/challenge.yaml and up to date
24
+ run: node web/scripts/catalog.mjs --check
25
+ - run: npm --prefix web test
26
+ - run: npm --prefix web run build
@@ -0,0 +1,61 @@
1
+ # --- Secrets and credentials: never commit ---
2
+ .pi
3
+ .env
4
+ .env.*
5
+ !.env.example
6
+ *.pem
7
+ *.key
8
+ *.p12
9
+ *.pfx
10
+ *.keystore
11
+ *.jks
12
+ id_rsa*
13
+ id_ed25519*
14
+ id_ecdsa*
15
+ *.gpg
16
+ .netrc
17
+ .npmrc
18
+ .pypirc
19
+ credentials*.json
20
+ service-account*.json
21
+ secrets.*
22
+ *.secret
23
+ .mcp.json
24
+ .claude/settings.local.json
25
+
26
+ # --- Local Patchground state (may hold AI payloads, transcripts, config) ---
27
+ .patchground/
28
+ /sessions/
29
+ *.transcript.*
30
+ .session/
31
+
32
+ # --- Agent / tool working state ---
33
+ .thinkrail/
34
+ .cursor/
35
+ .aider*
36
+
37
+ # --- Python ---
38
+ __pycache__/
39
+ *.py[cod]
40
+ .venv/
41
+ venv/
42
+ build/
43
+ dist/
44
+ *.egg-info/
45
+ .pytest_cache/
46
+ .mypy_cache/
47
+ .ruff_cache/
48
+ .coverage
49
+ htmlcov/
50
+ site/
51
+
52
+ # --- OS / editors ---
53
+ .DS_Store
54
+ .idea/
55
+ .vscode/
56
+ *.swp
57
+
58
+ # --- pgnd validate output: CI re-validates every push; local image ids don't travel ---
59
+ examples/**/verification.json
60
+ tests/fixtures/**/verification.json
61
+ .wrangler/