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.
- patchground-0.1.0/.githooks/pre-commit +35 -0
- patchground-0.1.0/.githooks/pre-push +22 -0
- patchground-0.1.0/.github/CONTRIBUTING.md +32 -0
- patchground-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +29 -0
- patchground-0.1.0/.github/ISSUE_TEMPLATE/challenge_feedback.yml +18 -0
- patchground-0.1.0/.github/ISSUE_TEMPLATE/config.yml +8 -0
- patchground-0.1.0/.github/SECURITY.md +19 -0
- patchground-0.1.0/.github/pull_request_template.md +10 -0
- patchground-0.1.0/.github/workflows/action-smoke.yml +68 -0
- patchground-0.1.0/.github/workflows/ci.yml +49 -0
- patchground-0.1.0/.github/workflows/oom-stress.yml +46 -0
- patchground-0.1.0/.github/workflows/registry.yml +96 -0
- patchground-0.1.0/.github/workflows/release.yml +98 -0
- patchground-0.1.0/.github/workflows/secret-scan.yml +25 -0
- patchground-0.1.0/.github/workflows/web.yml +26 -0
- patchground-0.1.0/.gitignore +61 -0
- patchground-0.1.0/.gitleaks.toml +31 -0
- patchground-0.1.0/.python-version +1 -0
- patchground-0.1.0/AGENTS.md +79 -0
- patchground-0.1.0/CHANGELOG.md +45 -0
- patchground-0.1.0/LICENSE +202 -0
- patchground-0.1.0/PKG-INFO +258 -0
- patchground-0.1.0/README.md +219 -0
- patchground-0.1.0/action.yml +94 -0
- patchground-0.1.0/architecture.md +142 -0
- patchground-0.1.0/docs/authoring.md +94 -0
- patchground-0.1.0/docs/contracts/challenge-format.md +111 -0
- patchground-0.1.0/docs/contracts/cli.md +137 -0
- patchground-0.1.0/docs/contracts/execution.md +72 -0
- patchground-0.1.0/docs/contracts/feedback.md +88 -0
- patchground-0.1.0/docs/contracts/security.md +63 -0
- patchground-0.1.0/docs/contracts/verification.md +84 -0
- patchground-0.1.0/docs/contributing.md +50 -0
- patchground-0.1.0/docs/delivery-plan.md +166 -0
- patchground-0.1.0/docs/evaluations/ai-debrief-smoke.md +54 -0
- patchground-0.1.0/docs/evaluations/authoring-smoke.md +43 -0
- patchground-0.1.0/docs/evaluations/automation-smoke.md +39 -0
- patchground-0.1.0/docs/evaluations/ci-portability.md +54 -0
- patchground-0.1.0/docs/evaluations/cli-artifact-manifest.json +30 -0
- patchground-0.1.0/docs/evaluations/coverage-smoke.md +47 -0
- patchground-0.1.0/docs/evaluations/fresh-user-wheel-trial.md +78 -0
- patchground-0.1.0/docs/evaluations/human-trial-findings.md +32 -0
- patchground-0.1.0/docs/evaluations/human-trial-kit.md +63 -0
- patchground-0.1.0/docs/evaluations/learner-cohort-registration.json +57 -0
- patchground-0.1.0/docs/evaluations/learner-cohort-results.json +924 -0
- patchground-0.1.0/docs/evaluations/learner-cohort.md +108 -0
- patchground-0.1.0/docs/evaluations/learner-instructions.md +56 -0
- patchground-0.1.0/docs/evaluations/release-candidate-check.sh +93 -0
- patchground-0.1.0/docs/evaluations/release-candidate.md +76 -0
- patchground-0.1.0/docs/first-session.md +123 -0
- patchground-0.1.0/docs/launch-brief.md +79 -0
- patchground-0.1.0/docs/packs.md +69 -0
- patchground-0.1.0/docs/publishing.md +40 -0
- patchground-0.1.0/docs/spec.md +683 -0
- patchground-0.1.0/docs/synthetic-evaluation.md +88 -0
- patchground-0.1.0/docs/validation-action.md +95 -0
- patchground-0.1.0/examples/README.md +10 -0
- patchground-0.1.0/examples/cache-expiry/TICKET.md +49 -0
- patchground-0.1.0/examples/cache-expiry/challenge.yaml +32 -0
- patchground-0.1.0/examples/cache-expiry/checks/hidden/tests/test_cache_hidden.py +164 -0
- patchground-0.1.0/examples/cache-expiry/checks/visible/tests/test_cache.py +56 -0
- patchground-0.1.0/examples/cache-expiry/debrief/questions.md +4 -0
- patchground-0.1.0/examples/cache-expiry/environment/Dockerfile +3 -0
- patchground-0.1.0/examples/cache-expiry/solution/reference.patch +41 -0
- patchground-0.1.0/examples/cache-expiry/workspace/pyproject.toml +8 -0
- patchground-0.1.0/examples/cache-expiry/workspace/src/profilecache/__init__.py +6 -0
- patchground-0.1.0/examples/cache-expiry/workspace/src/profilecache/cache.py +67 -0
- patchground-0.1.0/examples/cache-expiry/workspace/src/profilecache/client.py +38 -0
- patchground-0.1.0/examples/csv-import-row-errors/TICKET.md +97 -0
- patchground-0.1.0/examples/csv-import-row-errors/challenge.yaml +33 -0
- patchground-0.1.0/examples/csv-import-row-errors/checks/hidden/tests/test_import_hidden.py +359 -0
- patchground-0.1.0/examples/csv-import-row-errors/checks/visible/tests/test_import.py +34 -0
- patchground-0.1.0/examples/csv-import-row-errors/debrief/questions.md +5 -0
- patchground-0.1.0/examples/csv-import-row-errors/environment/Dockerfile +3 -0
- patchground-0.1.0/examples/csv-import-row-errors/solution/reference.patch +74 -0
- patchground-0.1.0/examples/csv-import-row-errors/workspace/pyproject.toml +8 -0
- patchground-0.1.0/examples/csv-import-row-errors/workspace/src/stockimport/__init__.py +4 -0
- patchground-0.1.0/examples/csv-import-row-errors/workspace/src/stockimport/importer.py +69 -0
- patchground-0.1.0/examples/csv-import-row-errors/workspace/src/stockimport/models.py +32 -0
- patchground-0.1.0/examples/duplicate-charges-on-retry/TICKET.md +28 -0
- patchground-0.1.0/examples/duplicate-charges-on-retry/challenge.yaml +30 -0
- patchground-0.1.0/examples/duplicate-charges-on-retry/checks/hidden/tests/test_payment_regressions.py +87 -0
- patchground-0.1.0/examples/duplicate-charges-on-retry/checks/visible/tests/test_checkout.py +34 -0
- patchground-0.1.0/examples/duplicate-charges-on-retry/debrief/questions.md +6 -0
- patchground-0.1.0/examples/duplicate-charges-on-retry/environment/Dockerfile +3 -0
- patchground-0.1.0/examples/duplicate-charges-on-retry/solution/reference.patch +11 -0
- patchground-0.1.0/examples/duplicate-charges-on-retry/workspace/pyproject.toml +8 -0
- patchground-0.1.0/examples/duplicate-charges-on-retry/workspace/src/payments/__init__.py +1 -0
- patchground-0.1.0/examples/duplicate-charges-on-retry/workspace/src/payments/gateway.py +38 -0
- patchground-0.1.0/examples/duplicate-charges-on-retry/workspace/src/payments/models.py +19 -0
- patchground-0.1.0/examples/duplicate-charges-on-retry/workspace/src/payments/service.py +23 -0
- patchground-0.1.0/examples/event-bus-subscriptions/TICKET.md +108 -0
- patchground-0.1.0/examples/event-bus-subscriptions/challenge.yaml +33 -0
- patchground-0.1.0/examples/event-bus-subscriptions/checks/hidden/tests/event-bus-contract.test.ts +566 -0
- patchground-0.1.0/examples/event-bus-subscriptions/checks/visible/tests/event-bus.test.ts +70 -0
- patchground-0.1.0/examples/event-bus-subscriptions/debrief/questions.md +5 -0
- patchground-0.1.0/examples/event-bus-subscriptions/environment/Dockerfile +7 -0
- patchground-0.1.0/examples/event-bus-subscriptions/environment/package-lock.json +1054 -0
- patchground-0.1.0/examples/event-bus-subscriptions/environment/package.json +13 -0
- patchground-0.1.0/examples/event-bus-subscriptions/environment/vitest-runner.mjs +124 -0
- patchground-0.1.0/examples/event-bus-subscriptions/solution/reference.patch +82 -0
- patchground-0.1.0/examples/event-bus-subscriptions/workspace/package.json +6 -0
- patchground-0.1.0/examples/event-bus-subscriptions/workspace/src/bus.ts +32 -0
- patchground-0.1.0/examples/order-notifications-boundary/TICKET.md +51 -0
- patchground-0.1.0/examples/order-notifications-boundary/challenge.yaml +32 -0
- patchground-0.1.0/examples/order-notifications-boundary/checks/hidden/tests/test_boundary_hidden.py +87 -0
- patchground-0.1.0/examples/order-notifications-boundary/checks/visible/tests/test_confirmation.py +41 -0
- patchground-0.1.0/examples/order-notifications-boundary/debrief/questions.md +5 -0
- patchground-0.1.0/examples/order-notifications-boundary/environment/Dockerfile +3 -0
- patchground-0.1.0/examples/order-notifications-boundary/solution/reference.patch +77 -0
- patchground-0.1.0/examples/order-notifications-boundary/workspace/pyproject.toml +8 -0
- patchground-0.1.0/examples/order-notifications-boundary/workspace/src/orders/__init__.py +6 -0
- patchground-0.1.0/examples/order-notifications-boundary/workspace/src/orders/app.py +9 -0
- patchground-0.1.0/examples/order-notifications-boundary/workspace/src/orders/models.py +25 -0
- patchground-0.1.0/examples/order-notifications-boundary/workspace/src/orders/service.py +31 -0
- patchground-0.1.0/examples/order-notifications-boundary/workspace/src/orders/smtp_gateway.py +19 -0
- patchground-0.1.0/examples/order-notifications-boundary/workspace/src/orders/sqlite_store.py +44 -0
- patchground-0.1.0/examples/pagination-last-page/TICKET.md +44 -0
- patchground-0.1.0/examples/pagination-last-page/challenge.yaml +31 -0
- patchground-0.1.0/examples/pagination-last-page/checks/hidden/tests/test_pagination_hidden.py +32 -0
- patchground-0.1.0/examples/pagination-last-page/checks/visible/tests/test_pagination.py +37 -0
- patchground-0.1.0/examples/pagination-last-page/debrief/questions.md +9 -0
- patchground-0.1.0/examples/pagination-last-page/environment/Dockerfile +3 -0
- patchground-0.1.0/examples/pagination-last-page/solution/reference.patch +22 -0
- patchground-0.1.0/examples/pagination-last-page/workspace/pyproject.toml +8 -0
- patchground-0.1.0/examples/pagination-last-page/workspace/src/catalog/__init__.py +1 -0
- patchground-0.1.0/examples/pagination-last-page/workspace/src/catalog/pagination.py +52 -0
- patchground-0.1.0/examples/release-version-ordering/TICKET.md +72 -0
- patchground-0.1.0/examples/release-version-ordering/challenge.yaml +31 -0
- patchground-0.1.0/examples/release-version-ordering/checks/hidden/tests/releasekit_contract.test.ts +92 -0
- patchground-0.1.0/examples/release-version-ordering/checks/visible/tests/releasekit.test.ts +31 -0
- patchground-0.1.0/examples/release-version-ordering/debrief/questions.md +9 -0
- patchground-0.1.0/examples/release-version-ordering/environment/Dockerfile +7 -0
- patchground-0.1.0/examples/release-version-ordering/environment/package-lock.json +1054 -0
- patchground-0.1.0/examples/release-version-ordering/environment/package.json +13 -0
- patchground-0.1.0/examples/release-version-ordering/environment/vitest-runner.mjs +124 -0
- patchground-0.1.0/examples/release-version-ordering/solution/reference.patch +36 -0
- patchground-0.1.0/examples/release-version-ordering/workspace/README.md +7 -0
- patchground-0.1.0/examples/release-version-ordering/workspace/src/releasekit/index.ts +2 -0
- patchground-0.1.0/examples/release-version-ordering/workspace/src/releasekit/ordering.ts +41 -0
- patchground-0.1.0/examples/release-version-ordering/workspace/src/releasekit/parse.ts +24 -0
- patchground-0.1.0/goal-and-requirements.md +187 -0
- patchground-0.1.0/pyproject.toml +102 -0
- patchground-0.1.0/registry/README.md +43 -0
- patchground-0.1.0/registry/SPEC.md +79 -0
- patchground-0.1.0/registry/crawl.py +627 -0
- patchground-0.1.0/registry/index.json +16 -0
- patchground-0.1.0/registry/sources.yaml +6 -0
- patchground-0.1.0/registry/status.json +6 -0
- patchground-0.1.0/schema/challenge-0.1.schema.json +376 -0
- patchground-0.1.0/schema/pack-0.1.schema.json +50 -0
- patchground-0.1.0/src/patchground/__init__.py +10 -0
- patchground-0.1.0/src/patchground/__main__.py +3 -0
- patchground-0.1.0/src/patchground/authoring/SPEC.md +49 -0
- patchground-0.1.0/src/patchground/authoring/__init__.py +5 -0
- patchground-0.1.0/src/patchground/authoring/create.py +179 -0
- patchground-0.1.0/src/patchground/authoring/templates/python-debug/TICKET.md.tmpl +11 -0
- patchground-0.1.0/src/patchground/authoring/templates/python-debug/challenge.yaml.tmpl +21 -0
- patchground-0.1.0/src/patchground/authoring/templates/python-debug/checks/hidden/tests/test_starter_hidden.py.tmpl +13 -0
- patchground-0.1.0/src/patchground/authoring/templates/python-debug/checks/visible/tests/test_starter.py.tmpl +9 -0
- patchground-0.1.0/src/patchground/authoring/templates/python-debug/environment/Dockerfile.tmpl +3 -0
- patchground-0.1.0/src/patchground/authoring/templates/python-debug/solution/reference.patch.tmpl +8 -0
- patchground-0.1.0/src/patchground/authoring/templates/python-debug/workspace/pyproject.toml.tmpl +8 -0
- patchground-0.1.0/src/patchground/authoring/templates/python-debug/workspace/src/starter.py.tmpl +3 -0
- patchground-0.1.0/src/patchground/authoring/templates/python-feature/TICKET.md.tmpl +13 -0
- patchground-0.1.0/src/patchground/authoring/templates/python-feature/challenge.yaml.tmpl +22 -0
- patchground-0.1.0/src/patchground/authoring/templates/python-feature/checks/hidden/tests/test_starter_hidden.py.tmpl +13 -0
- patchground-0.1.0/src/patchground/authoring/templates/python-feature/checks/visible/tests/test_starter.py.tmpl +9 -0
- patchground-0.1.0/src/patchground/authoring/templates/python-feature/environment/Dockerfile.tmpl +3 -0
- patchground-0.1.0/src/patchground/authoring/templates/python-feature/solution/reference.patch.tmpl +9 -0
- patchground-0.1.0/src/patchground/authoring/templates/python-feature/workspace/pyproject.toml.tmpl +8 -0
- patchground-0.1.0/src/patchground/authoring/templates/python-feature/workspace/src/starter.py.tmpl +8 -0
- patchground-0.1.0/src/patchground/authoring/templates/typescript-debug/TICKET.md.tmpl +11 -0
- patchground-0.1.0/src/patchground/authoring/templates/typescript-debug/challenge.yaml.tmpl +21 -0
- patchground-0.1.0/src/patchground/authoring/templates/typescript-debug/checks/hidden/tests/starter-hidden.test.ts.tmpl +14 -0
- patchground-0.1.0/src/patchground/authoring/templates/typescript-debug/checks/visible/tests/starter.test.ts.tmpl +10 -0
- patchground-0.1.0/src/patchground/authoring/templates/typescript-debug/solution/reference.patch.tmpl +9 -0
- patchground-0.1.0/src/patchground/authoring/templates/typescript-debug/workspace/package.json.tmpl +6 -0
- patchground-0.1.0/src/patchground/authoring/templates/typescript-debug/workspace/src/starter.ts.tmpl +4 -0
- patchground-0.1.0/src/patchground/authoring/templates/typescript-feature/TICKET.md.tmpl +13 -0
- patchground-0.1.0/src/patchground/authoring/templates/typescript-feature/challenge.yaml.tmpl +22 -0
- patchground-0.1.0/src/patchground/authoring/templates/typescript-feature/checks/hidden/tests/starter-hidden.test.ts.tmpl +14 -0
- patchground-0.1.0/src/patchground/authoring/templates/typescript-feature/checks/visible/tests/starter.test.ts.tmpl +10 -0
- patchground-0.1.0/src/patchground/authoring/templates/typescript-feature/solution/reference.patch.tmpl +10 -0
- patchground-0.1.0/src/patchground/authoring/templates/typescript-feature/workspace/package.json.tmpl +6 -0
- patchground-0.1.0/src/patchground/authoring/templates/typescript-feature/workspace/src/starter.ts.tmpl +9 -0
- patchground-0.1.0/src/patchground/automation/SPEC.md +84 -0
- patchground-0.1.0/src/patchground/automation/__init__.py +5 -0
- patchground-0.1.0/src/patchground/automation/__main__.py +131 -0
- patchground-0.1.0/src/patchground/automation/workflow.py +214 -0
- patchground-0.1.0/src/patchground/cli/SPEC.md +52 -0
- patchground-0.1.0/src/patchground/cli/__init__.py +1 -0
- patchground-0.1.0/src/patchground/cli/app.py +59 -0
- patchground-0.1.0/src/patchground/cli/authoring.py +41 -0
- patchground-0.1.0/src/patchground/cli/consent.py +77 -0
- patchground-0.1.0/src/patchground/cli/discovery.py +189 -0
- patchground-0.1.0/src/patchground/cli/doctor.py +249 -0
- patchground-0.1.0/src/patchground/cli/packs.py +142 -0
- patchground-0.1.0/src/patchground/cli/practice.py +141 -0
- patchground-0.1.0/src/patchground/cli/render.py +58 -0
- patchground-0.1.0/src/patchground/cli/reports.py +53 -0
- patchground-0.1.0/src/patchground/cli/sessions.py +195 -0
- patchground-0.1.0/src/patchground/cli/validate.py +117 -0
- patchground-0.1.0/src/patchground/packs/SPEC.md +82 -0
- patchground-0.1.0/src/patchground/packs/__init__.py +23 -0
- patchground-0.1.0/src/patchground/packs/builtin.py +53 -0
- patchground-0.1.0/src/patchground/packs/discovery.py +285 -0
- patchground-0.1.0/src/patchground/packs/execution.py +420 -0
- patchground-0.1.0/src/patchground/packs/models.py +98 -0
- patchground-0.1.0/src/patchground/packs/registry.py +318 -0
- patchground-0.1.0/src/patchground/packs/source.py +230 -0
- patchground-0.1.0/src/patchground/packs/store.py +420 -0
- patchground-0.1.0/src/patchground/py.typed +0 -0
- patchground-0.1.0/src/patchground/report/SPEC.md +57 -0
- patchground-0.1.0/src/patchground/report/__init__.py +6 -0
- patchground-0.1.0/src/patchground/report/analysis.py +188 -0
- patchground-0.1.0/src/patchground/report/models.py +59 -0
- patchground-0.1.0/src/patchground/report/regression.py +597 -0
- patchground-0.1.0/src/patchground/report/render.py +92 -0
- patchground-0.1.0/src/patchground/report/workflow.py +84 -0
- patchground-0.1.0/src/patchground/runner_assets/Dockerfile +7 -0
- patchground-0.1.0/src/patchground/runner_assets/SPEC.md +46 -0
- patchground-0.1.0/src/patchground/runner_assets/__init__.py +16 -0
- patchground-0.1.0/src/patchground/runner_assets/package-lock.json +1054 -0
- patchground-0.1.0/src/patchground/runner_assets/package.json +13 -0
- patchground-0.1.0/src/patchground/runner_assets/vitest-runner.mjs +124 -0
- patchground-0.1.0/src/patchground/safeio/SPEC.md +26 -0
- patchground-0.1.0/src/patchground/safeio/__init__.py +115 -0
- patchground-0.1.0/src/patchground/sandbox/SPEC.md +80 -0
- patchground-0.1.0/src/patchground/sandbox/__init__.py +39 -0
- patchground-0.1.0/src/patchground/sandbox/_engine.py +23 -0
- patchground-0.1.0/src/patchground/sandbox/build.py +202 -0
- patchground-0.1.0/src/patchground/sandbox/errors.py +23 -0
- patchground-0.1.0/src/patchground/sandbox/junit.py +76 -0
- patchground-0.1.0/src/patchground/sandbox/probe.py +118 -0
- patchground-0.1.0/src/patchground/sandbox/run.py +535 -0
- patchground-0.1.0/src/patchground/sessions/SPEC.md +219 -0
- patchground-0.1.0/src/patchground/sessions/__init__.py +44 -0
- patchground-0.1.0/src/patchground/sessions/diagnostics.py +145 -0
- patchground-0.1.0/src/patchground/sessions/files.py +113 -0
- patchground-0.1.0/src/patchground/sessions/git.py +70 -0
- patchground-0.1.0/src/patchground/sessions/history.py +386 -0
- patchground-0.1.0/src/patchground/sessions/history_ignore.py +279 -0
- patchground-0.1.0/src/patchground/sessions/models.py +126 -0
- patchground-0.1.0/src/patchground/sessions/practice.py +202 -0
- patchground-0.1.0/src/patchground/sessions/reset.py +121 -0
- patchground-0.1.0/src/patchground/sessions/store.py +203 -0
- patchground-0.1.0/src/patchground/sessions/submission.py +344 -0
- patchground-0.1.0/src/patchground/sessions/workflow.py +297 -0
- patchground-0.1.0/src/patchground/spec/SPEC.md +37 -0
- patchground-0.1.0/src/patchground/spec/__init__.py +35 -0
- patchground-0.1.0/src/patchground/spec/_schema.py +53 -0
- patchground-0.1.0/src/patchground/spec/loader.py +713 -0
- patchground-0.1.0/src/patchground/spec/models.py +394 -0
- patchground-0.1.0/src/patchground/spec/schema.py +50 -0
- patchground-0.1.0/src/patchground/verify/SPEC.md +84 -0
- patchground-0.1.0/src/patchground/verify/__init__.py +29 -0
- patchground-0.1.0/src/patchground/verify/errors.py +10 -0
- patchground-0.1.0/src/patchground/verify/pytest_adapter.py +95 -0
- patchground-0.1.0/src/patchground/verify/record.py +94 -0
- patchground-0.1.0/src/patchground/verify/reference.py +158 -0
- patchground-0.1.0/src/patchground/verify/result.py +199 -0
- patchground-0.1.0/src/patchground/verify/runners.py +47 -0
- patchground-0.1.0/src/patchground/verify/tampering.py +106 -0
- patchground-0.1.0/src/patchground/verify/validate.py +686 -0
- patchground-0.1.0/src/patchground/verify/vitest_adapter.py +252 -0
- patchground-0.1.0/tests/conftest.py +99 -0
- patchground-0.1.0/tests/fixtures/README.md +31 -0
- patchground-0.1.0/tests/fixtures/broken-state-passes/TICKET.md +41 -0
- patchground-0.1.0/tests/fixtures/broken-state-passes/challenge.yaml +31 -0
- patchground-0.1.0/tests/fixtures/broken-state-passes/checks/hidden/tests/test_pagination_hidden.py +16 -0
- patchground-0.1.0/tests/fixtures/broken-state-passes/checks/visible/tests/test_pagination.py +37 -0
- patchground-0.1.0/tests/fixtures/broken-state-passes/debrief/questions.md +9 -0
- patchground-0.1.0/tests/fixtures/broken-state-passes/environment/Dockerfile +3 -0
- patchground-0.1.0/tests/fixtures/broken-state-passes/solution/reference.patch +22 -0
- patchground-0.1.0/tests/fixtures/broken-state-passes/workspace/pyproject.toml +8 -0
- patchground-0.1.0/tests/fixtures/broken-state-passes/workspace/src/catalog/__init__.py +1 -0
- patchground-0.1.0/tests/fixtures/broken-state-passes/workspace/src/catalog/pagination.py +52 -0
- patchground-0.1.0/tests/fixtures/build-fails/TICKET.md +41 -0
- patchground-0.1.0/tests/fixtures/build-fails/challenge.yaml +31 -0
- patchground-0.1.0/tests/fixtures/build-fails/checks/hidden/tests/test_pagination_hidden.py +32 -0
- patchground-0.1.0/tests/fixtures/build-fails/checks/visible/tests/test_pagination.py +37 -0
- patchground-0.1.0/tests/fixtures/build-fails/debrief/questions.md +9 -0
- patchground-0.1.0/tests/fixtures/build-fails/environment/Dockerfile +2 -0
- patchground-0.1.0/tests/fixtures/build-fails/solution/reference.patch +22 -0
- patchground-0.1.0/tests/fixtures/build-fails/workspace/pyproject.toml +8 -0
- patchground-0.1.0/tests/fixtures/build-fails/workspace/src/catalog/__init__.py +1 -0
- patchground-0.1.0/tests/fixtures/build-fails/workspace/src/catalog/pagination.py +52 -0
- patchground-0.1.0/tests/fixtures/conftest.py +3 -0
- patchground-0.1.0/tests/fixtures/invalid-manifest/TICKET.md +41 -0
- patchground-0.1.0/tests/fixtures/invalid-manifest/challenge.yaml +24 -0
- patchground-0.1.0/tests/fixtures/invalid-manifest/checks/hidden/tests/test_pagination_hidden.py +32 -0
- patchground-0.1.0/tests/fixtures/invalid-manifest/checks/visible/tests/test_pagination.py +37 -0
- patchground-0.1.0/tests/fixtures/invalid-manifest/debrief/questions.md +9 -0
- patchground-0.1.0/tests/fixtures/invalid-manifest/environment/Dockerfile +3 -0
- patchground-0.1.0/tests/fixtures/invalid-manifest/solution/reference.patch +22 -0
- patchground-0.1.0/tests/fixtures/invalid-manifest/workspace/pyproject.toml +8 -0
- patchground-0.1.0/tests/fixtures/invalid-manifest/workspace/src/catalog/__init__.py +1 -0
- patchground-0.1.0/tests/fixtures/invalid-manifest/workspace/src/catalog/pagination.py +52 -0
- patchground-0.1.0/tests/fixtures/reference-fails/TICKET.md +41 -0
- patchground-0.1.0/tests/fixtures/reference-fails/challenge.yaml +31 -0
- patchground-0.1.0/tests/fixtures/reference-fails/checks/hidden/tests/test_pagination_hidden.py +32 -0
- patchground-0.1.0/tests/fixtures/reference-fails/checks/visible/tests/test_pagination.py +37 -0
- patchground-0.1.0/tests/fixtures/reference-fails/debrief/questions.md +9 -0
- patchground-0.1.0/tests/fixtures/reference-fails/environment/Dockerfile +3 -0
- patchground-0.1.0/tests/fixtures/reference-fails/solution/reference.patch +13 -0
- patchground-0.1.0/tests/fixtures/reference-fails/workspace/pyproject.toml +8 -0
- patchground-0.1.0/tests/fixtures/reference-fails/workspace/src/catalog/__init__.py +1 -0
- patchground-0.1.0/tests/fixtures/reference-fails/workspace/src/catalog/pagination.py +52 -0
- patchground-0.1.0/tests/fixtures/services-unsupported/TICKET.md +41 -0
- patchground-0.1.0/tests/fixtures/services-unsupported/challenge.yaml +31 -0
- patchground-0.1.0/tests/fixtures/services-unsupported/checks/hidden/tests/test_pagination_hidden.py +32 -0
- patchground-0.1.0/tests/fixtures/services-unsupported/checks/visible/tests/test_pagination.py +37 -0
- patchground-0.1.0/tests/fixtures/services-unsupported/debrief/questions.md +9 -0
- patchground-0.1.0/tests/fixtures/services-unsupported/environment/Dockerfile +3 -0
- patchground-0.1.0/tests/fixtures/services-unsupported/solution/reference.patch +22 -0
- patchground-0.1.0/tests/fixtures/services-unsupported/workspace/pyproject.toml +8 -0
- patchground-0.1.0/tests/fixtures/services-unsupported/workspace/src/catalog/__init__.py +1 -0
- patchground-0.1.0/tests/fixtures/services-unsupported/workspace/src/catalog/pagination.py +52 -0
- patchground-0.1.0/tests/integration/test_authoring.py +39 -0
- patchground-0.1.0/tests/integration/test_automation.py +62 -0
- patchground-0.1.0/tests/integration/test_history.py +68 -0
- patchground-0.1.0/tests/integration/test_packs.py +51 -0
- patchground-0.1.0/tests/integration/test_practice.py +60 -0
- patchground-0.1.0/tests/integration/test_regression_evidence.py +158 -0
- patchground-0.1.0/tests/integration/test_reports.py +73 -0
- patchground-0.1.0/tests/integration/test_sandbox_docker.py +644 -0
- patchground-0.1.0/tests/integration/test_sessions.py +93 -0
- patchground-0.1.0/tests/integration/test_validate_examples.py +152 -0
- patchground-0.1.0/tests/integration/test_vitest_runner.py +259 -0
- patchground-0.1.0/tests/unit/authoring/test_create.py +277 -0
- patchground-0.1.0/tests/unit/authoring/test_wheel.py +30 -0
- patchground-0.1.0/tests/unit/automation/__init__.py +1 -0
- patchground-0.1.0/tests/unit/automation/test_action.py +125 -0
- patchground-0.1.0/tests/unit/automation/test_entrypoint.py +188 -0
- patchground-0.1.0/tests/unit/automation/test_workflow.py +305 -0
- patchground-0.1.0/tests/unit/cli/test_cli_authoring.py +91 -0
- patchground-0.1.0/tests/unit/cli/test_cli_discovery.py +154 -0
- patchground-0.1.0/tests/unit/cli/test_cli_doctor.py +91 -0
- patchground-0.1.0/tests/unit/cli/test_cli_m1_app.py +35 -0
- patchground-0.1.0/tests/unit/cli/test_cli_packs.py +107 -0
- patchground-0.1.0/tests/unit/cli/test_cli_practice.py +87 -0
- patchground-0.1.0/tests/unit/cli/test_cli_reset.py +38 -0
- patchground-0.1.0/tests/unit/cli/test_cli_sessions.py +100 -0
- patchground-0.1.0/tests/unit/cli/test_cli_validate.py +207 -0
- patchground-0.1.0/tests/unit/packs/test_builtin.py +83 -0
- patchground-0.1.0/tests/unit/packs/test_packs_discovery.py +214 -0
- patchground-0.1.0/tests/unit/packs/test_registry.py +878 -0
- patchground-0.1.0/tests/unit/packs/test_source.py +252 -0
- patchground-0.1.0/tests/unit/packs/test_state_safety.py +75 -0
- patchground-0.1.0/tests/unit/registry/__init__.py +0 -0
- patchground-0.1.0/tests/unit/registry/test_crawl.py +100 -0
- patchground-0.1.0/tests/unit/report/conftest.py +43 -0
- patchground-0.1.0/tests/unit/report/test_analysis.py +100 -0
- patchground-0.1.0/tests/unit/report/test_cli_reports.py +79 -0
- patchground-0.1.0/tests/unit/report/test_regression.py +422 -0
- patchground-0.1.0/tests/unit/report/test_vitest_regression.py +275 -0
- patchground-0.1.0/tests/unit/report/test_workflow.py +88 -0
- patchground-0.1.0/tests/unit/runner_assets/test_assets.py +63 -0
- patchground-0.1.0/tests/unit/sandbox/test_sandbox_engine.py +44 -0
- patchground-0.1.0/tests/unit/sandbox/test_sandbox_errors.py +244 -0
- patchground-0.1.0/tests/unit/sandbox/test_sandbox_junit.py +126 -0
- patchground-0.1.0/tests/unit/sandbox/test_sandbox_limits.py +63 -0
- patchground-0.1.0/tests/unit/sandbox/test_sandbox_output.py +62 -0
- patchground-0.1.0/tests/unit/sandbox/test_sandbox_probe.py +111 -0
- patchground-0.1.0/tests/unit/sandbox/test_sandbox_tar.py +185 -0
- patchground-0.1.0/tests/unit/sessions/__init__.py +0 -0
- patchground-0.1.0/tests/unit/sessions/conftest.py +82 -0
- patchground-0.1.0/tests/unit/sessions/test_diagnostics.py +92 -0
- patchground-0.1.0/tests/unit/sessions/test_git_safety.py +186 -0
- patchground-0.1.0/tests/unit/sessions/test_history.py +542 -0
- patchground-0.1.0/tests/unit/sessions/test_practice.py +124 -0
- patchground-0.1.0/tests/unit/sessions/test_reset.py +90 -0
- patchground-0.1.0/tests/unit/sessions/test_security.py +275 -0
- patchground-0.1.0/tests/unit/sessions/test_sessions_root.py +55 -0
- patchground-0.1.0/tests/unit/sessions/test_submission.py +387 -0
- patchground-0.1.0/tests/unit/sessions/test_workflow.py +376 -0
- patchground-0.1.0/tests/unit/spec/test_spec_loader.py +767 -0
- patchground-0.1.0/tests/unit/spec/test_spec_schema.py +71 -0
- patchground-0.1.0/tests/unit/test_docker_gate.py +41 -0
- patchground-0.1.0/tests/unit/test_package.py +10 -0
- patchground-0.1.0/tests/unit/verify/test_tampering.py +97 -0
- patchground-0.1.0/tests/unit/verify/test_verify_reference.py +152 -0
- patchground-0.1.0/tests/unit/verify/test_verify_validate.py +994 -0
- patchground-0.1.0/tests/unit/verify/test_vitest_adapter.py +201 -0
- patchground-0.1.0/tests/unit/verify/test_vitest_tampering.py +69 -0
- patchground-0.1.0/uv.lock +914 -0
- patchground-0.1.0/web/README.md +73 -0
- patchground-0.1.0/web/SPEC.md +74 -0
- patchground-0.1.0/web/package.json +14 -0
- patchground-0.1.0/web/public/catalog.json +168 -0
- patchground-0.1.0/web/public/favicon.svg +1 -0
- patchground-0.1.0/web/public/index.html +325 -0
- patchground-0.1.0/web/public/main.js +102 -0
- patchground-0.1.0/web/public/status.html +29 -0
- patchground-0.1.0/web/public/styles.css +465 -0
- patchground-0.1.0/web/scripts/build.mjs +12 -0
- patchground-0.1.0/web/scripts/catalog.mjs +279 -0
- patchground-0.1.0/web/scripts/site.test.mjs +299 -0
- 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/
|