skilldrop-cli 0.16.29 → 0.16.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/skilldrop.js +160 -33
- package/catalogue.json +10 -4
- package/model-routing.json +24 -0
- package/package.json +1 -1
- package/packs/ai-engineering/skills/mcp-pairing/SKILL.md +93 -0
- package/packs/ai-engineering/skills/mcp-pairing/evals/eval_queries.json +13 -0
- package/packs/ai-engineering/skills/mcp-pairing/evals/evals.json +76 -0
- package/packs/ai-engineering/skills/mcp-pairing/evals/files/2/mcp.json +32 -0
- package/packs/ai-engineering/skills/mcp-pairing/evals/files/4/.mcp.json +47 -0
- package/packs/ai-engineering/skills/mcp-pairing/evals/files/4/docs/pr-loop.md +12 -0
- package/packs/ai-engineering/skills/mcp-pairing/evals/files/5/docs/oncall-triage.md +18 -0
- package/packs/ai-engineering/skills/mcp-pairing/examples/build-loop-github-jira-datadog.md +69 -0
- package/packs/ai-engineering/skills/mcp-pairing/manifest.json +17 -0
- package/packs/ai-engineering/skills/mcp-pairing/reference.md +136 -0
- package/packs/ai-engineering/skills/mcp-pairing/templates/mcp-pairing.md +43 -0
- package/packs/dev-team/loops/build/LOOP.md +4 -2
- package/packs/dev-team/loops/build/loop.json +2 -2
- package/packs/dev-team/loops/release/LOOP.md +7 -5
- package/packs/dev-team/loops/release/loop.json +2 -2
- package/packs/dev-team/skills/codebase-tour/SKILL.md +115 -0
- package/packs/dev-team/skills/codebase-tour/evals/eval_queries.json +13 -0
- package/packs/dev-team/skills/codebase-tour/evals/evals.json +77 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/1/.github/workflows/ci.yml +23 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/1/README.md +23 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/1/openapi.yaml +21 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/1/package-lock.json +7 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/1/package.json +29 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/1/src/config.ts +20 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/1/src/domain/shipments/filter.ts +24 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/1/src/domain/shipments/repository.ts +15 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/1/src/errors.ts +9 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/1/src/generated/api-types.ts +9 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/1/src/http/handlers/shipments.ts +38 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/1/src/http/routes.ts +7 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/1/src/jobs/export-shipments.ts +22 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/1/src/server.ts +18 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/1/tests/domain/filter.test.ts +16 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/1/tests/http/shipments.test.ts +18 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/1/tsconfig.json +10 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/1/vitest.config.ts +5 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/2/.github/workflows/ci.yml +16 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/2/README.md +16 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/2/noxfile.py +14 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/2/pyproject.toml +22 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/2/src/ledgerline/__init__.py +3 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/2/src/ledgerline/_version.py +4 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/2/src/ledgerline/cli.py +32 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/2/src/ledgerline/detect.py +15 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/2/src/ledgerline/errors.py +2 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/2/src/ledgerline/money.py +14 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/2/src/ledgerline/parsers/__init__.py +8 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/2/src/ledgerline/parsers/acme.py +18 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/2/src/ledgerline/parsers/base.py +24 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/2/src/ledgerline/parsers/harbor.py +13 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/2/tests/fixtures/acme_sample.csv +3 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/2/tests/test_acme.py +23 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/2/tests/test_detect.py +13 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/3/.github/workflows/ci.yml +28 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/3/Makefile +16 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/3/README.md +16 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/3/cmd/tidepool/main.go +24 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/3/go.mod +5 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/3/internal/config/config.go +38 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/3/internal/dispatch/dispatcher.go +52 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/3/internal/dispatch/dispatcher_test.go +42 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/3/internal/dispatch/sender.go +32 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/3/internal/replay/replay.go +31 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/3/internal/store/db.go +35 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/3/internal/store/query.sql +11 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/3/internal/store/query.sql.go +30 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/3/internal/store/schema.sql +6 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/3/internal/store/store_integration_test.go +21 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/3/sqlc.yaml +9 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/.env.example +4 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/.github/workflows/ci.yml +16 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/Makefile +16 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/README.md +23 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/data/rates.csv +4 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/pyproject.toml +19 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/scripts/gen_rates.py +29 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/src/shipcalc/__init__.py +0 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/src/shipcalc/api/__init__.py +0 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/src/shipcalc/api/preview.py +14 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/src/shipcalc/api/routes.py +36 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/src/shipcalc/config.py +15 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/src/shipcalc/discounts.py +8 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/src/shipcalc/generated/__init__.py +0 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/src/shipcalc/generated/rates_table.py +8 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/src/shipcalc/main.py +8 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/src/shipcalc/shipping.py +18 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/src/shipcalc/tax.py +7 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/tests/conftest.py +3 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/tests/unit/test_discounts.py +11 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/4/tests/unit/test_tax.py +13 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/.env.example +3 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/.github/workflows/ci.yml +13 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/README.md +18 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/package.json +13 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/api/openapi.json +30 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/api/package.json +15 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/api/src/db.js +19 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/api/src/flags.js +8 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/api/src/generated/schema.js +32 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/api/src/middleware/validate.js +20 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/api/src/routes/bookings.js +15 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/api/src/server.js +15 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/api/src/services/bookings.js +23 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/api/src/services/pricing.js +9 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/api/test/pricing.test.js +15 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/web/package.json +9 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/web/src/bookingForm.js +11 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/web/src/format.js +7 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/web/test/format.test.js +12 -0
- package/packs/dev-team/skills/codebase-tour/evals/files/5/scripts/gen-schema.mjs +27 -0
- package/packs/dev-team/skills/codebase-tour/examples/refund-window.md +158 -0
- package/packs/dev-team/skills/codebase-tour/manifest.json +47 -0
- package/packs/dev-team/skills/codebase-tour/scripts/repo_facts.py +403 -0
- package/packs/dev-team/skills/codebase-tour/templates/codebase-tour.md +85 -0
- package/packs/dev-team/skills/dependency-upgrade-review/SKILL.md +101 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/eval_queries.json +11 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/evals.json +77 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/1/.github/workflows/ci.yml +20 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/1/pr.diff +87 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/1/quillmark-CHANGELOG.md +24 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/1/src/render/comment.js +6 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/1/src/render/note.js +8 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/1/test/note.test.js +12 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/2/.github/workflows/ci.yml +23 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/2/.github/workflows/release.yml +23 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/2/pr.diff +32 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/2/release-notes.md +20 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/4/.github/workflows/ci.yml +18 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/4/Dockerfile +8 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/4/PR.md +36 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/4/pr.diff +47 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/4/src/ingest/client.py +24 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/4/src/ingest/reader.py +32 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/4/tests/test_reader.py +15 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/4/upstream/quillstream-CHANGELOG.md +37 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/5/.github/workflows/ci.yml +22 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/5/.github/workflows/release.yml +25 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/5/.nvmrc +1 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/5/PR-412.md +16 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/5/PR-413.md +15 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/5/package.json +21 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/5/pr-412.diff +26 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/5/pr-413.diff +84 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/5/scripts/release.mjs +18 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/5/upstream/release-notary-CHANGELOG.md +16 -0
- package/packs/dev-team/skills/dependency-upgrade-review/evals/files/5/upstream/setup-toolchain-RELEASES.md +22 -0
- package/packs/dev-team/skills/dependency-upgrade-review/examples/inputs/ledgerline.diff +38 -0
- package/packs/dev-team/skills/dependency-upgrade-review/examples/ledgerline-tidyparse.md +91 -0
- package/packs/dev-team/skills/dependency-upgrade-review/manifest.json +50 -0
- package/packs/dev-team/skills/dependency-upgrade-review/reference.md +99 -0
- package/packs/dev-team/skills/dependency-upgrade-review/scripts/upgrade_diff.py +718 -0
- package/packs/dev-team/skills/refactor-plan/SKILL.md +168 -0
- package/packs/dev-team/skills/refactor-plan/evals/eval_queries.json +46 -0
- package/packs/dev-team/skills/refactor-plan/evals/evals.json +106 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/1/debt-register-row.md +5 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/1/in-flight-prs.md +7 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/1/order_service.py +74 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/1/test-coverage.txt +14 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/3/imports.txt +22 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/3/register.md +3 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/4/docs/open-prs.md +6 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/4/docs/tech-debt-register.md +7 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/4/reports/monthly_revenue.py +44 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/4/src/billing/invoice.py +233 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/4/src/partners/statement.py +29 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/4/tests/billing/test_payments.py +13 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/5/ci-notes.md +5 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/5/open-prs.txt +4 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/5/scripts/nightly-digest.js +36 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/5/src/auth/passwordReset.ts +16 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/5/src/notify/index.ts +2 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/5/src/notify/legacyMailer.ts +149 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/5/src/orders/onOrderShipped.ts +15 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/5/tests/notify/legacyMailer.test.ts +7 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/6/billing-service/src/main/kotlin/billing/DunningJob.kt +29 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/6/billing-service/src/main/kotlin/billing/InvoiceService.kt +157 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/6/billing-service/src/main/kotlin/billing/pdf/InvoicePdfRenderer.kt +36 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/6/billing-service/src/test/kotlin/billing/InvoiceServiceTest.kt +40 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/6/docs/adr-007-persistence.md +26 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/6/docs/open-prs.md +9 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/7/notes/open-work.md +19 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/7/web/src/components/CheckoutPanel.tsx +207 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/7/web/src/components/MiniCart.tsx +33 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/7/web/src/components/__tests__/CheckoutPanel.test.tsx +13 -0
- package/packs/dev-team/skills/refactor-plan/evals/files/7/web/src/pages/OrderReview.tsx +24 -0
- package/packs/dev-team/skills/refactor-plan/examples/search-ranker.md +104 -0
- package/packs/dev-team/skills/refactor-plan/manifest.json +36 -0
- package/packs/dev-team/skills/refactor-plan/reference.md +149 -0
- package/packs/dev-team/skills/refactor-plan/templates/refactor-plan.md +82 -0
- package/packs/dev-team/skills/rollout-plan/SKILL.md +157 -0
- package/packs/dev-team/skills/rollout-plan/evals/eval_queries.json +12 -0
- package/packs/dev-team/skills/rollout-plan/evals/evals.json +109 -0
- package/packs/dev-team/skills/rollout-plan/evals/files/2/metrics-snapshot.txt +11 -0
- package/packs/dev-team/skills/rollout-plan/evals/files/2/pr-description.md +28 -0
- package/packs/dev-team/skills/rollout-plan/evals/files/4/PR_DESCRIPTION.md +24 -0
- package/packs/dev-team/skills/rollout-plan/evals/files/4/metrics_snapshot.txt +7 -0
- package/packs/dev-team/skills/rollout-plan/evals/files/5/docs/tax-engine-v2.md +24 -0
- package/packs/dev-team/skills/rollout-plan/evals/files/6/flags/smart_reminders_v2.yaml +14 -0
- package/packs/dev-team/skills/rollout-plan/evals/files/6/notes/push-pipeline.md +10 -0
- package/packs/dev-team/skills/rollout-plan/evals/files/7/docs/ratelimiter-v2.md +21 -0
- package/packs/dev-team/skills/rollout-plan/evals/files/7/ops/traffic-summary.txt +9 -0
- package/packs/dev-team/skills/rollout-plan/examples/sync-engine-workspaces.md +102 -0
- package/packs/dev-team/skills/rollout-plan/manifest.json +35 -0
- package/packs/dev-team/skills/rollout-plan/reference.md +100 -0
- package/packs/dev-team/skills/rollout-plan/scripts/canary_math.py +261 -0
- package/packs/dev-team/skills/rollout-plan/templates/rollout-plan.md +50 -0
- package/packs/product-manager/loops/discover/LOOP.md +5 -4
- package/packs/product-manager/loops/discover/loop.json +2 -2
- package/packs/product-manager/skills/feedback-synthesis/SKILL.md +182 -0
- package/packs/product-manager/skills/feedback-synthesis/evals/eval_queries.json +46 -0
- package/packs/product-manager/skills/feedback-synthesis/evals/evals.json +76 -0
- package/packs/product-manager/skills/feedback-synthesis/evals/files/1/interview-notes.md +47 -0
- package/packs/product-manager/skills/feedback-synthesis/evals/files/1/nps-verbatims.csv +13 -0
- package/packs/product-manager/skills/feedback-synthesis/evals/files/1/support-tickets.csv +36 -0
- package/packs/product-manager/skills/feedback-synthesis/evals/files/3/churn-exit-survey.csv +11 -0
- package/packs/product-manager/skills/feedback-synthesis/evals/files/3/sales-call-notes.md +55 -0
- package/packs/product-manager/skills/feedback-synthesis/evals/files/4/csat_comments_q3.csv +17 -0
- package/packs/product-manager/skills/feedback-synthesis/evals/files/4/support_tickets_q3.csv +49 -0
- package/packs/product-manager/skills/feedback-synthesis/evals/files/5/app_reviews_last_60_days.csv +32 -0
- package/packs/product-manager/skills/feedback-synthesis/evals/files/5/interview_notes_round1.md +48 -0
- package/packs/product-manager/skills/feedback-synthesis/examples/booking-app-feedback.md +139 -0
- package/packs/product-manager/skills/feedback-synthesis/examples/inputs/booking-app-coded.csv +17 -0
- package/packs/product-manager/skills/feedback-synthesis/manifest.json +53 -0
- package/packs/product-manager/skills/feedback-synthesis/reference.md +89 -0
- package/packs/product-manager/skills/feedback-synthesis/scripts/theme_counts.py +355 -0
- package/packs/product-manager/skills/feedback-synthesis/templates/coding-sheet.csv +6 -0
- package/packs/product-manager/skills/feedback-synthesis/templates/synthesis.md +42 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
|
|
3
|
+
LOYALTY_RATES = {"bronze": Decimal("0.00"), "silver": Decimal("0.03"), "gold": Decimal("0.05")}
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def loyalty_discount(tier: str, subtotal: Decimal) -> Decimal:
|
|
7
|
+
rate = LOYALTY_RATES.get(tier, Decimal("0.00"))
|
|
8
|
+
return (subtotal * rate).quantize(Decimal("0.01"))
|
|
File without changes
|
package/packs/dev-team/skills/codebase-tour/evals/files/4/src/shipcalc/generated/rates_table.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# DO NOT EDIT. This file is generated by scripts/gen_rates.py from data/rates.csv.
|
|
2
|
+
# Edit the CSV and run `make gen` instead. CI fails if this file is out of date.
|
|
3
|
+
|
|
4
|
+
RATES = {
|
|
5
|
+
"EU": {"currency": "EUR", "base_rate": 4.9, "per_kg": 1.2, "free_threshold": 50.0},
|
|
6
|
+
"UK": {"currency": "GBP", "base_rate": 3.95, "per_kg": 1.1, "free_threshold": 40.0},
|
|
7
|
+
"US": {"currency": "USD", "base_rate": 5.5, "per_kg": 0.95, "free_threshold": 75.0},
|
|
8
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Shipping cost for a cart. Rates come from the generated table."""
|
|
2
|
+
import os
|
|
3
|
+
from decimal import Decimal, ROUND_HALF_UP
|
|
4
|
+
|
|
5
|
+
from shipcalc.generated.rates_table import RATES
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _free_shipping_enabled() -> bool:
|
|
9
|
+
# Rolled out per environment; off unless explicitly turned on.
|
|
10
|
+
return os.environ.get("SHIPCALC_FF_FREE_SHIPPING", "off") == "on"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def shipping_cost(region: str, subtotal: Decimal, weight_kg: Decimal) -> Decimal:
|
|
14
|
+
rate = RATES[region]
|
|
15
|
+
if _free_shipping_enabled() and subtotal >= Decimal(str(rate["free_threshold"])):
|
|
16
|
+
return Decimal("0.00")
|
|
17
|
+
cost = Decimal(str(rate["base_rate"])) + Decimal(str(rate["per_kg"])) * weight_kg
|
|
18
|
+
return cost.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
|
|
3
|
+
from shipcalc.discounts import loyalty_discount
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_gold_gets_five_percent():
|
|
7
|
+
assert loyalty_discount("gold", Decimal("100.00")) == Decimal("5.00")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_unknown_tier_gets_nothing():
|
|
11
|
+
assert loyalty_discount("platinum", Decimal("100.00")) == Decimal("0.00")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from shipcalc.tax import vat
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.parametrize(
|
|
9
|
+
"region,taxable,expected",
|
|
10
|
+
[("EU", Decimal("100.00"), Decimal("21.00")), ("UK", Decimal("100.00"), Decimal("20.00")), ("US", Decimal("100.00"), Decimal("0.00"))],
|
|
11
|
+
)
|
|
12
|
+
def test_vat_by_region(region, taxable, expected):
|
|
13
|
+
assert vat(region, taxable) == expected
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on: [push, pull_request]
|
|
3
|
+
jobs:
|
|
4
|
+
test:
|
|
5
|
+
runs-on: ubuntu-latest
|
|
6
|
+
steps:
|
|
7
|
+
- uses: actions/checkout@v4
|
|
8
|
+
- uses: actions/setup-node@v4
|
|
9
|
+
with:
|
|
10
|
+
node-version: 20
|
|
11
|
+
- run: npm install --no-audit --no-fund
|
|
12
|
+
- run: npm run gen:check
|
|
13
|
+
- run: npm test
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# cabinbook
|
|
2
|
+
|
|
3
|
+
Holiday-cabin booking: the booking API and the web booking form.
|
|
4
|
+
|
|
5
|
+
## Getting started
|
|
6
|
+
|
|
7
|
+
yarn install
|
|
8
|
+
cd api && yarn start
|
|
9
|
+
|
|
10
|
+
The API listens on http://localhost:3000.
|
|
11
|
+
|
|
12
|
+
## Running tests
|
|
13
|
+
|
|
14
|
+
yarn test
|
|
15
|
+
|
|
16
|
+
## Max stay
|
|
17
|
+
|
|
18
|
+
Bookings are limited to 14 nights; the limit lives in `api/config.js`.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cabinbook",
|
|
3
|
+
"private": true,
|
|
4
|
+
"workspaces": [
|
|
5
|
+
"packages/*"
|
|
6
|
+
],
|
|
7
|
+
"scripts": {
|
|
8
|
+
"gen": "node scripts/gen-schema.mjs",
|
|
9
|
+
"gen:check": "node scripts/gen-schema.mjs --check",
|
|
10
|
+
"test": "npm test --workspaces",
|
|
11
|
+
"dev:api": "npm run dev -w @cabinbook/api"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapi": "3.0.3",
|
|
3
|
+
"info": { "title": "cabinbook", "version": "1.4.0" },
|
|
4
|
+
"paths": {
|
|
5
|
+
"/bookings": {
|
|
6
|
+
"post": {
|
|
7
|
+
"requestBody": {
|
|
8
|
+
"content": {
|
|
9
|
+
"application/json": { "schema": { "$ref": "#/components/schemas/BookingRequest" } }
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"responses": { "201": { "description": "Created" } }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"components": {
|
|
17
|
+
"schemas": {
|
|
18
|
+
"BookingRequest": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"required": ["cabinId", "checkIn", "nights"],
|
|
21
|
+
"properties": {
|
|
22
|
+
"cabinId": { "type": "string" },
|
|
23
|
+
"checkIn": { "type": "string", "format": "date" },
|
|
24
|
+
"nights": { "type": "integer", "minimum": 1, "maximum": 14 },
|
|
25
|
+
"guests": { "type": "integer", "minimum": 1, "maximum": 8 }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cabinbook/api",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/server.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "node --env-file=../../.env --watch src/server.js",
|
|
9
|
+
"start": "node src/server.js",
|
|
10
|
+
"test": "node --test"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"express": "^4.19.2"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// In production this is a pg pool built from DATABASE_URL; locally an in-memory stand-in.
|
|
2
|
+
const url = process.env.DATABASE_URL;
|
|
3
|
+
if (!url) {
|
|
4
|
+
throw new Error('DATABASE_URL is not set. Copy .env.example to .env.');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const bookings = [];
|
|
8
|
+
|
|
9
|
+
export const db = {
|
|
10
|
+
url,
|
|
11
|
+
async insertBooking(row) {
|
|
12
|
+
const saved = { id: String(bookings.length + 1), ...row };
|
|
13
|
+
bookings.push(saved);
|
|
14
|
+
return saved;
|
|
15
|
+
},
|
|
16
|
+
async bookingsForCabin(cabinId) {
|
|
17
|
+
return bookings.filter((b) => b.cabinId === cabinId);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Feature flags come from a comma-separated env var, e.g. FEATURE_FLAGS=long-stays,new-pricing
|
|
2
|
+
const enabled = new Set(
|
|
3
|
+
(process.env.FEATURE_FLAGS ?? '').split(',').map((f) => f.trim()).filter(Boolean),
|
|
4
|
+
);
|
|
5
|
+
|
|
6
|
+
export function isEnabled(flag) {
|
|
7
|
+
return enabled.has(flag);
|
|
8
|
+
}
|
package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/api/src/generated/schema.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// DO NOT EDIT. Generated by scripts/gen-schema.mjs from packages/api/openapi.json.
|
|
2
|
+
// Change openapi.json and run `npm run gen`.
|
|
3
|
+
|
|
4
|
+
export const schemas = {
|
|
5
|
+
"BookingRequest": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"cabinId",
|
|
9
|
+
"checkIn",
|
|
10
|
+
"nights"
|
|
11
|
+
],
|
|
12
|
+
"properties": {
|
|
13
|
+
"cabinId": {
|
|
14
|
+
"type": "string"
|
|
15
|
+
},
|
|
16
|
+
"checkIn": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"format": "date"
|
|
19
|
+
},
|
|
20
|
+
"nights": {
|
|
21
|
+
"type": "integer",
|
|
22
|
+
"minimum": 1,
|
|
23
|
+
"maximum": 14
|
|
24
|
+
},
|
|
25
|
+
"guests": {
|
|
26
|
+
"type": "integer",
|
|
27
|
+
"minimum": 1,
|
|
28
|
+
"maximum": 8
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/api/src/middleware/validate.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { schemas } from '../generated/schema.js';
|
|
2
|
+
|
|
3
|
+
export function validate(schemaName) {
|
|
4
|
+
const schema = schemas[schemaName];
|
|
5
|
+
return (req, res, next) => {
|
|
6
|
+
const errors = [];
|
|
7
|
+
for (const field of schema.required ?? []) {
|
|
8
|
+
if (req.body?.[field] === undefined) errors.push(`${field} is required`);
|
|
9
|
+
}
|
|
10
|
+
for (const [field, rule] of Object.entries(schema.properties)) {
|
|
11
|
+
const value = req.body?.[field];
|
|
12
|
+
if (value === undefined) continue;
|
|
13
|
+
if (rule.type === 'integer' && !Number.isInteger(value)) errors.push(`${field} must be an integer`);
|
|
14
|
+
if (rule.minimum !== undefined && value < rule.minimum) errors.push(`${field} must be >= ${rule.minimum}`);
|
|
15
|
+
if (rule.maximum !== undefined && value > rule.maximum) errors.push(`${field} must be <= ${rule.maximum}`);
|
|
16
|
+
}
|
|
17
|
+
if (errors.length) return res.status(400).json({ errors });
|
|
18
|
+
next();
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { validate } from '../middleware/validate.js';
|
|
3
|
+
import { createBooking } from '../services/bookings.js';
|
|
4
|
+
|
|
5
|
+
export const bookingsRouter = Router();
|
|
6
|
+
|
|
7
|
+
bookingsRouter.post('/bookings', validate('BookingRequest'), async (req, res, next) => {
|
|
8
|
+
try {
|
|
9
|
+
const booking = await createBooking(req.body, req.member);
|
|
10
|
+
res.status(201).json(booking);
|
|
11
|
+
} catch (err) {
|
|
12
|
+
if (err.status) return res.status(err.status).json({ error: err.message });
|
|
13
|
+
next(err);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { bookingsRouter } from './routes/bookings.js';
|
|
3
|
+
|
|
4
|
+
const app = express();
|
|
5
|
+
app.use(express.json());
|
|
6
|
+
app.use((req, _res, next) => {
|
|
7
|
+
// Members are identified by the upstream proxy.
|
|
8
|
+
const id = req.get('x-member-id');
|
|
9
|
+
req.member = id ? { id } : null;
|
|
10
|
+
next();
|
|
11
|
+
});
|
|
12
|
+
app.use(bookingsRouter);
|
|
13
|
+
|
|
14
|
+
const port = Number(process.env.PORT ?? 3000);
|
|
15
|
+
app.listen(port, () => console.log(`cabinbook api on :${port}`));
|
package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/api/src/services/bookings.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { db } from '../db.js';
|
|
2
|
+
import { isEnabled } from '../flags.js';
|
|
3
|
+
import { quote } from './pricing.js';
|
|
4
|
+
|
|
5
|
+
// Business rule: no stay may be longer than two weeks.
|
|
6
|
+
const MAX_NIGHTS = 14;
|
|
7
|
+
const LONG_STAY_MAX_NIGHTS = 28;
|
|
8
|
+
|
|
9
|
+
export function maxNightsFor(member) {
|
|
10
|
+
if (member && isEnabled('long-stays')) return LONG_STAY_MAX_NIGHTS;
|
|
11
|
+
return MAX_NIGHTS;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function createBooking({ cabinId, checkIn, nights, guests = 1 }, member) {
|
|
15
|
+
const max = maxNightsFor(member);
|
|
16
|
+
if (nights > max) {
|
|
17
|
+
const err = new Error(`Stays are limited to ${max} nights`);
|
|
18
|
+
err.status = 422;
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
21
|
+
const price = quote({ nights, guests });
|
|
22
|
+
return db.insertBooking({ cabinId, checkIn, nights, guests, price, memberId: member?.id ?? null });
|
|
23
|
+
}
|
package/packs/dev-team/skills/codebase-tour/evals/files/5/packages/api/src/services/pricing.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const NIGHTLY_RATE = 120;
|
|
2
|
+
const EXTRA_GUEST_FEE = 15;
|
|
3
|
+
const WEEKLY_DISCOUNT = 0.1;
|
|
4
|
+
|
|
5
|
+
export function quote({ nights, guests = 1 }) {
|
|
6
|
+
const base = nights * NIGHTLY_RATE + Math.max(0, guests - 2) * EXTRA_GUEST_FEE * nights;
|
|
7
|
+
const discount = nights >= 7 ? base * WEEKLY_DISCOUNT : 0;
|
|
8
|
+
return Math.round((base - discount) * 100) / 100;
|
|
9
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { quote } from '../src/services/pricing.js';
|
|
4
|
+
|
|
5
|
+
test('charges the nightly rate for short stays', () => {
|
|
6
|
+
assert.equal(quote({ nights: 3 }), 360);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('adds a fee per extra guest per night', () => {
|
|
10
|
+
assert.equal(quote({ nights: 2, guests: 4 }), 300);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('applies the weekly discount from 7 nights', () => {
|
|
14
|
+
assert.equal(quote({ nights: 7 }), 756);
|
|
15
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Client-side checks for the booking form, run before POST /bookings.
|
|
2
|
+
export const MAX_NIGHTS = 14;
|
|
3
|
+
export const MAX_GUESTS = 8;
|
|
4
|
+
|
|
5
|
+
export function validateBookingForm({ nights, guests }) {
|
|
6
|
+
const errors = {};
|
|
7
|
+
if (!Number.isInteger(nights) || nights < 1) errors.nights = 'Choose at least one night';
|
|
8
|
+
else if (nights > MAX_NIGHTS) errors.nights = `Stays are limited to ${MAX_NIGHTS} nights`;
|
|
9
|
+
if (!Number.isInteger(guests) || guests < 1 || guests > MAX_GUESTS) errors.guests = `1 to ${MAX_GUESTS} guests`;
|
|
10
|
+
return errors;
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { formatNights, formatPrice } from '../src/format.js';
|
|
4
|
+
|
|
5
|
+
test('formats prices in euros with two decimals', () => {
|
|
6
|
+
assert.equal(formatPrice(756), '€756.00');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('pluralises nights', () => {
|
|
10
|
+
assert.equal(formatNights(1), '1 night');
|
|
11
|
+
assert.equal(formatNights(14), '14 nights');
|
|
12
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Generates packages/api/src/generated/schema.js from packages/api/openapi.json.
|
|
2
|
+
// Usage (from the repo root): node scripts/gen-schema.mjs [--check]
|
|
3
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
const SRC = 'packages/api/openapi.json';
|
|
6
|
+
const DEST = 'packages/api/src/generated/schema.js';
|
|
7
|
+
|
|
8
|
+
const spec = JSON.parse(readFileSync(SRC, 'utf8'));
|
|
9
|
+
const schemas = spec.components.schemas;
|
|
10
|
+
|
|
11
|
+
const out = [
|
|
12
|
+
'// DO NOT EDIT. Generated by scripts/gen-schema.mjs from packages/api/openapi.json.',
|
|
13
|
+
'// Change openapi.json and run `npm run gen`.',
|
|
14
|
+
'',
|
|
15
|
+
`export const schemas = ${JSON.stringify(schemas, null, 2)};`,
|
|
16
|
+
'',
|
|
17
|
+
].join('\n');
|
|
18
|
+
|
|
19
|
+
if (process.argv.includes('--check')) {
|
|
20
|
+
const current = readFileSync(DEST, 'utf8');
|
|
21
|
+
if (current !== out) {
|
|
22
|
+
console.error(`${DEST} is out of date. Run \`npm run gen\`.`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
writeFileSync(DEST, out);
|
|
27
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Worked example: tallyhall, before adding a refund window
|
|
2
|
+
|
|
3
|
+
A fictional FastAPI ticketing service. The README is two releases stale, one rule has a second copy, and one module is generated.
|
|
4
|
+
|
|
5
|
+
## Input given to the skill
|
|
6
|
+
|
|
7
|
+
> I've just joined and my first ticket is "refunds must be requested within 14 days of the event ending". I've never opened this repo. Get me oriented. The repo is the current directory.
|
|
8
|
+
|
|
9
|
+
The files that mattered, with the line numbers the tour cites:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
README.md
|
|
13
|
+
5 ## Run it
|
|
14
|
+
7 make dev
|
|
15
|
+
12 Settings live in `settings.toml`.
|
|
16
|
+
|
|
17
|
+
Makefile
|
|
18
|
+
1 .PHONY: run test lint
|
|
19
|
+
3 run:
|
|
20
|
+
4 uvicorn tallyhall.main:app --reload
|
|
21
|
+
7 test:
|
|
22
|
+
8 pytest -q -m "not e2e"
|
|
23
|
+
10 lint:
|
|
24
|
+
11 ruff check tallyhall tests
|
|
25
|
+
|
|
26
|
+
.github/workflows/ci.yml
|
|
27
|
+
17 - run: pip install -e ".[dev]"
|
|
28
|
+
19 - run: make lint
|
|
29
|
+
21 - run: make test
|
|
30
|
+
23 env:
|
|
31
|
+
24 TALLYHALL_DB_URL: sqlite:///ci.db
|
|
32
|
+
|
|
33
|
+
pyproject.toml
|
|
34
|
+
14 [tool.pytest.ini_options]
|
|
35
|
+
15 markers = ["e2e: needs the payment sandbox"]
|
|
36
|
+
|
|
37
|
+
tallyhall/main.py
|
|
38
|
+
6 app = FastAPI()
|
|
39
|
+
7 app.include_router(refunds.router)
|
|
40
|
+
|
|
41
|
+
tallyhall/api/refunds.py
|
|
42
|
+
18 @router.post("/orders/{order_id}/refunds", status_code=201)
|
|
43
|
+
19 def create_refund(order_id: int, db=Depends(get_db)):
|
|
44
|
+
20 order = orders.get(db, order_id)
|
|
45
|
+
22 check_refundable(order, now=utcnow())
|
|
46
|
+
24 except RefundRejected as e:
|
|
47
|
+
25 raise HTTPException(422, {"code": e.code, "message": str(e)})
|
|
48
|
+
27 return refunds.create(db, order)
|
|
49
|
+
|
|
50
|
+
tallyhall/rules/refunds.py
|
|
51
|
+
4 class RefundRejected(Exception):
|
|
52
|
+
9 def check_refundable(order, now):
|
|
53
|
+
10 if order.status != "paid":
|
|
54
|
+
11 raise RefundRejected("not_paid", "only paid orders can be refunded")
|
|
55
|
+
12 if order.refunded_at is not None:
|
|
56
|
+
13 raise RefundRejected("already_refunded", "order already refunded")
|
|
57
|
+
|
|
58
|
+
tallyhall/admin/bulk_refund.py
|
|
59
|
+
27 if order.status == "paid" and order.refunded_at is None:
|
|
60
|
+
28 refunds.create(db, order)
|
|
61
|
+
|
|
62
|
+
tallyhall/models_gen.py
|
|
63
|
+
1 # generated by datamodel-codegen:
|
|
64
|
+
2 # filename: schemas/order.json
|
|
65
|
+
|
|
66
|
+
tallyhall/settings.py
|
|
67
|
+
6 DB_URL = os.environ["TALLYHALL_DB_URL"]
|
|
68
|
+
|
|
69
|
+
tests/rules/test_refunds.py (test_rejects_unpaid, test_rejects_already_refunded)
|
|
70
|
+
tests/api/test_refunds_e2e.py (marked e2e)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The fact lister also reported: `make dev` named at `README.md:7` with no `dev` target in the Makefile, and `settings.toml` named at `README.md:12` and not present. No test file imports `tallyhall/admin/bulk_refund.py`. The Order model (`tallyhall/models_gen.py`) has an `event_ends_at` field.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Output
|
|
78
|
+
|
|
79
|
+
**What matters for this change**
|
|
80
|
+
|
|
81
|
+
- The rule goes in `check_refundable` (`tallyhall/rules/refunds.py:9`). Every other refund rule lives there, and the API calls it at `tallyhall/api/refunds.py:22`.
|
|
82
|
+
- There is a second copy of the refund rules in the support bulk-refund tool (`tallyhall/admin/bulk_refund.py:27`). It doesn't call `check_refundable`, so it won't pick up the window unless someone changes it. Whether it should is the first question for a maintainer.
|
|
83
|
+
- Ignore the README's `make dev` and `settings.toml`. Neither exists. Run with `make run` (`Makefile:3`); settings come from env vars (`tallyhall/settings.py:6`).
|
|
84
|
+
- `tallyhall/models_gen.py` is generated from `schemas/order.json`. Don't edit it. The field the window needs, `event_ends_at`, is already there.
|
|
85
|
+
- `make test` skips the e2e tests (`Makefile:8`, marker at `pyproject.toml:15`). The rule tests in `tests/rules/` do run.
|
|
86
|
+
|
|
87
|
+
# Tour: tallyhall, for "refunds within 14 days of the event ending"
|
|
88
|
+
|
|
89
|
+
_Read on 2026-10-20. Scope: `POST /orders/{order_id}/refunds` from the app bootstrap to `refunds.create`. Skipped on purpose: ticket purchase, the payment-provider client, deployment config._
|
|
90
|
+
|
|
91
|
+
[assumption] "Within 14 days of the event ending" means `now - order.event_ends_at <= 14 days`, with the boundary day allowed.
|
|
92
|
+
|
|
93
|
+
## Run it
|
|
94
|
+
|
|
95
|
+
| To | Command | Source | Status |
|
|
96
|
+
|---|---|---|---|
|
|
97
|
+
| Install | `pip install -e ".[dev]"` | `.github/workflows/ci.yml:17` | read, not run |
|
|
98
|
+
| Test (whole suite) | `make test` → `pytest -q -m "not e2e"` | `.github/workflows/ci.yml:21`, `Makefile:8` | ran: passed (14 passed, 3 deselected) |
|
|
99
|
+
| Test (this area) | `pytest -q -m "not e2e" tests/rules/test_refunds.py` | derived from `Makefile:8` | ran: passed (2 passed) |
|
|
100
|
+
| Lint | `make lint` → `ruff check tallyhall tests` | `.github/workflows/ci.yml:19`, `Makefile:11` | ran: passed |
|
|
101
|
+
| Run locally | `make run` | `Makefile:3` | read, not run (needs `TALLYHALL_DB_URL`) |
|
|
102
|
+
|
|
103
|
+
Needs: `TALLYHALL_DB_URL`, read at `tallyhall/settings.py:6` with no default; CI sets `sqlite:///ci.db` at `.github/workflows/ci.yml:24`.
|
|
104
|
+
|
|
105
|
+
## The path this change touches
|
|
106
|
+
|
|
107
|
+
1. `tallyhall/main.py:7`: the refunds router is mounted on the app.
|
|
108
|
+
2. `tallyhall/api/refunds.py:19` `create_refund`: loads the order (line 20).
|
|
109
|
+
3. `tallyhall/api/refunds.py:22`: calls `check_refundable(order, now=utcnow())`.
|
|
110
|
+
4. `tallyhall/rules/refunds.py:9` `check_refundable`: raises `RefundRejected` for an unpaid order (line 10) or one already refunded (line 12).
|
|
111
|
+
5. `tallyhall/api/refunds.py:25`: `RefundRejected` becomes a 422 with `{code, message}`.
|
|
112
|
+
6. `tallyhall/api/refunds.py:27`: `refunds.create` writes the refund. This is the far edge.
|
|
113
|
+
|
|
114
|
+
## Where the change goes
|
|
115
|
+
|
|
116
|
+
- `tallyhall/rules/refunds.py` `check_refundable`: add the window check after line 13, raising `RefundRejected("refund_window_closed", …)`. This follows the two rules already there.
|
|
117
|
+
- Must change with it: nothing generated. `event_ends_at` already exists in `tallyhall/models_gen.py`. Whether `tallyhall/admin/bulk_refund.py:27` changes too is open (question 1).
|
|
118
|
+
|
|
119
|
+
## Conventions the code follows
|
|
120
|
+
|
|
121
|
+
- **Errors:** a rule raises `RefundRejected(code, message)` with a snake_case code (`tallyhall/rules/refunds.py:11`); the route maps it to 422 (`tallyhall/api/refunds.py:25`).
|
|
122
|
+
- **Time:** the route passes `now` in, so rules are tested without a clock (`tallyhall/api/refunds.py:22`).
|
|
123
|
+
- **Tests:** one `test_rejects_<reason>` per rule, under `tests/rules/` (`tests/rules/test_refunds.py`).
|
|
124
|
+
- **Config:** env vars read in `tallyhall/settings.py`; a missing var fails at import (`tallyhall/settings.py:6`).
|
|
125
|
+
|
|
126
|
+
## Traps
|
|
127
|
+
|
|
128
|
+
- **Generated:** `tallyhall/models_gen.py` from `schemas/order.json` (`tallyhall/models_gen.py:1-2`). No regenerate command is defined in the Makefile, pyproject or CI; see question 2. CI drift check: none found.
|
|
129
|
+
- **Second copy:** `tallyhall/admin/bulk_refund.py:27` repeats the paid and not-refunded checks inline.
|
|
130
|
+
- **Untested on the path:** none on the API path. Off the path, `tallyhall/admin/bulk_refund.py` has no test.
|
|
131
|
+
- **Tests the default command skips:** `e2e` (`Makefile:8`, `pyproject.toml:15`): `tests/api/test_refunds_e2e.py`, which needs the payment sandbox.
|
|
132
|
+
- **Env vars and flags on the path:** `TALLYHALL_DB_URL` only. No feature flags found.
|
|
133
|
+
- **Vendored:** none.
|
|
134
|
+
|
|
135
|
+
## Where the README is wrong
|
|
136
|
+
|
|
137
|
+
- README says `make dev` (`README.md:7`); the Makefile has `run`, `test` and `lint` only (`Makefile:1`). Use `make run`.
|
|
138
|
+
- README says settings live in `settings.toml` (`README.md:12`); there is no such file, and settings are env vars (`tallyhall/settings.py:6`).
|
|
139
|
+
|
|
140
|
+
## Tests for this area
|
|
141
|
+
|
|
142
|
+
- Covered by: `tests/rules/test_refunds.py` (unpaid and already-refunded rejections).
|
|
143
|
+
- Run just these: `pytest -q -m "not e2e" tests/rules/test_refunds.py` (derived from `Makefile:8`).
|
|
144
|
+
- Not covered: the 422 mapping is exercised only by the e2e test, which `make test` skips.
|
|
145
|
+
|
|
146
|
+
## First step
|
|
147
|
+
|
|
148
|
+
Add `test_rejects_refund_after_window` to `tests/rules/test_refunds.py`, with an order whose `event_ends_at` is 15 days before `now`. Run `pytest -q -m "not e2e" tests/rules/test_refunds.py`. It fails until `check_refundable` checks the window. Add a 14-day case that passes, to pin the boundary.
|
|
149
|
+
|
|
150
|
+
## Open questions for a maintainer
|
|
151
|
+
|
|
152
|
+
1. Should the bulk-refund tool (`tallyhall/admin/bulk_refund.py:27`) apply the window, or is it the support override that skips it?
|
|
153
|
+
2. How is `tallyhall/models_gen.py` regenerated? Nothing in the repo defines the command.
|
|
154
|
+
3. Is the window measured from `event_ends_at` in UTC? `utcnow()` is used at `tallyhall/api/refunds.py:22`, but the schema doesn't say what timezone the event times are in.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
Why this passes the bar: every command has a source and a status, both README claims are contradicted with citations, the second copy is named and left as a question rather than silently changed or ignored, the generated file is called out before anyone edits it, and the first step names a file, a command and the failure that proves it.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "codebase-tour",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Get a newcomer (a new engineer, or the agent itself) oriented in an unfamiliar repository before they make one specific change, with a short tour scoped to that change. It gives the install, run and test commands taken from CI, package scripts and the Makefile; the path the change touches, traced through the code with file:line citations; where the change goes; the conventions the code actually follows; the traps (generated files, a second copy of the logic, untested code, env vars, flags); and a first step that can be checked. Use when someone says \"I'm new to this repo\", \"get me oriented\", \"where would I make this change\", \"walk me through the code for X\", \"onboard me before I start this ticket\", or \"how do I run and test this\".",
|
|
5
|
+
"entrypoint": "SKILL.md",
|
|
6
|
+
"deps": {
|
|
7
|
+
"npm": [],
|
|
8
|
+
"pip": []
|
|
9
|
+
},
|
|
10
|
+
"env": {
|
|
11
|
+
"required": [],
|
|
12
|
+
"optional": []
|
|
13
|
+
},
|
|
14
|
+
"related": [
|
|
15
|
+
"agents-md-generator",
|
|
16
|
+
"architecture-diagrams",
|
|
17
|
+
"feature-implement-loop",
|
|
18
|
+
"interactive-diagram",
|
|
19
|
+
"pre-merge-review",
|
|
20
|
+
"reverse-architecture"
|
|
21
|
+
],
|
|
22
|
+
"handoff": [
|
|
23
|
+
{
|
|
24
|
+
"to": "feature-implement-loop",
|
|
25
|
+
"when": "the tour names where the change goes and a first step",
|
|
26
|
+
"purpose": "Implement the change against acceptance criteria, starting from the tour's first step and running the area's test command it found.",
|
|
27
|
+
"fallback": "If feature-implement-loop is absent, take the tour's first step: write the failing test it names, run the area's test command before and after the change, and update every place listed under 'Must change with it'."
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"tags": [
|
|
31
|
+
"onboarding",
|
|
32
|
+
"codebase",
|
|
33
|
+
"repo-orientation",
|
|
34
|
+
"provenance",
|
|
35
|
+
"developer-experience"
|
|
36
|
+
],
|
|
37
|
+
"permissions": {
|
|
38
|
+
"network": [],
|
|
39
|
+
"commands": [],
|
|
40
|
+
"files": "none",
|
|
41
|
+
"notes": "scripts/repo_facts.py reads files under the repository root it is given and prints to stdout. It writes nothing and runs no commands."
|
|
42
|
+
},
|
|
43
|
+
"model": {
|
|
44
|
+
"tier": "standard",
|
|
45
|
+
"rationale": "Reading a repo and citing what it finds is generation over evidence; every claim is checked against a file, so a mid tier is enough."
|
|
46
|
+
}
|
|
47
|
+
}
|