Agent skill
policyengine-standards
PolicyEngine coding standards, formatters, CI requirements, and development best practices. Triggers: "CI failing", "linting", "formatting", "before committing", "PR standards", "code style", "ruff formatter", "prettier", "pre-commit"
Install this agent skill to your Project
npx add-skill https://github.com/PolicyEngine/policyengine-claude/tree/main/skills/documentation/policyengine-standards-skill
SKILL.md
PolicyEngine Standards Skill
Use this skill to ensure code meets PolicyEngine's development standards and passes CI checks.
When to Use This Skill
- Before committing code to any PolicyEngine repository
- When CI checks fail with linting/formatting errors
- Setting up a new PolicyEngine repository
- Reviewing PRs for standard compliance
- When AI tools generate code that needs standardization
Critical Requirements
Python Version
⚠️ MUST USE Python 3.13 - Do NOT downgrade to older versions
- Check version:
python --version - Use
pyproject.tomlto specify version requirements
Command Execution
⚠️ ALWAYS use uv run for Python commands - Never use bare python or pytest
- ✅ Correct:
uv run python script.py,uv run pytest tests/ - ❌ Wrong:
python script.py,pytest tests/ - This ensures correct virtual environment and dependencies
Documentation (Python Projects)
⚠️ MUST USE Jupyter Book 2.0 (MyST-NB) - NOT Jupyter Book 1.x
- Build docs:
myst build docs(NOTjb build) - Use MyST markdown syntax
Before Committing - Checklist
- Write tests first (TDD - see below)
- Format code:
make formator language-specific formatter - Run tests:
make testto ensure all tests pass - Check linting: Ensure no linting errors
- Use config files: Prefer config files over environment variables
- Reference issues: Include "Fixes #123" in commit message
Creating Pull Requests
The CI Waiting Problem
Common failure pattern:
User: "Create a PR and mark it ready when CI passes"
Claude: "I've created the PR as draft. CI will take a while, I'll check back later..."
[Chat ends - Claude never checks back]
Result: PR stays in draft, user has to manually check CI and mark ready
Solution: Use /create-pr Command
When creating PRs, use the /create-pr command:
/create-pr
This command:
- ✅ Creates PR as draft
- ✅ Actually waits for CI (polls every 15 seconds)
- ✅ Marks ready when CI passes
- ✅ Reports failures with details
- ✅ Handles timeouts gracefully
Why this works: The command contains explicit polling logic that Claude executes, so it actually waits instead of giving up.
If /create-pr is Not Available
If the command isn't installed, implement the pattern directly:
# 1. Create PR as draft
# CRITICAL: Use --repo flag to create PR in upstream repo from fork
gh pr create --repo PolicyEngine/policyengine-us --draft --title "Title" --body "Body"
PR_NUMBER=$(gh pr view --json number --jq '.number')
# 2. Wait for CI (ACTUALLY WAIT - don't give up!)
POLL_INTERVAL=15
ELAPSED=0
while true; do # No timeout - wait as long as needed
CHECKS=$(gh pr checks $PR_NUMBER --json status,conclusion)
TOTAL=$(echo "$CHECKS" | jq '. | length')
COMPLETED=$(echo "$CHECKS" | jq '[.[] | select(.status == "COMPLETED")] | length')
echo "[$ELAPSED s] CI: $COMPLETED/$TOTAL completed"
if [ "$COMPLETED" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
FAILED=$(echo "$CHECKS" | jq '[.[] | select(.conclusion == "FAILURE")] | length')
if [ "$FAILED" -eq 0 ]; then
echo "✅ All CI passed! Marking ready..."
gh pr ready $PR_NUMBER
break
else
echo "❌ CI failed. PR remains draft."
gh pr checks $PR_NUMBER
break
fi
fi
sleep $POLL_INTERVAL
ELAPSED=$((ELAPSED + POLL_INTERVAL))
done
# Important: No timeout! Population simulations can take 30+ minutes.
CRITICAL: Never say "I'll check back later" — the chat session ends. Always use the polling loop above to actually wait. Default to creating PRs as draft; only create as ready when user explicitly requests it or CI is already verified.
Test-Driven Development (TDD)
PolicyEngine follows TDD: write test first (RED), implement (GREEN), refactor. In multi-agent workflows, @test-creator and @rules-engineer work independently from the same regulations. See policyengine-core-skill for details.
Example test:
def test_ctc_for_two_children():
"""Test CTC calculation for married couple with 2 children."""
situation = create_married_couple(income_1=75000, income_2=50000, num_children=2, child_ages=[5, 8])
sim = Simulation(situation=situation)
ctc = sim.calculate("ctc", 2026)[0]
assert ctc == 4400, "CTC should be $2,200 per child"
Running Tests
# Python
make test # All tests
uv run pytest tests/ -v # With uv
uv run pytest tests/test_credits.py::test_ctc -v # Specific test
# React
make test # All tests
bun test -- --watch # Watch mode
Test quality
- Test behavior, not implementation; clear names; docstrings with regulation citations
- Avoid: testing private methods, mocking everything, magic numbers without explanation
Python Standards
Formatting
- Formatter: Ruff
- Command:
make formatorruff format . - Check without changes:
ruff format --check .
# Format all Python files
make format
# Check if formatting is needed (CI-style)
ruff format --check .
Code Style
- Imports: Grouped (stdlib, third-party, local) and alphabetized
- Naming: CamelCase for classes, snake_case for functions/variables
- Type hints: Recommended, especially for public APIs
- Docstrings: Required for public functions/classes (Google style)
- Error handling: Catch specific exceptions, not bare
except
JavaScript/React Standards
Formatting
- Formatters: Prettier + ESLint
- Command:
bun run lint -- --fix && bunx prettier --write . - CI Check:
bun run lint -- --max-warnings=0
# Format all files
make format
# Or manually
bun run lint -- --fix
bunx prettier --write .
# Check if formatting is needed (CI-style)
bun run lint -- --max-warnings=0
Code Style
- Functional components only (no class components), hooks for state
- File naming: PascalCase.jsx for components, camelCase.js for utilities
- Use
src/config/environment.jspattern for env config (not REACT_APP_ env vars) - Keep components under 150 lines; extract complex logic into custom hooks
Version Control Standards
Changelog Management
CRITICAL: NEVER manually update CHANGELOG.md. Check which system the repo uses, then follow that system.
How to tell which system a repo uses:
- Check if the repo has a
changelog.d/directory -- if yes, use towncrier (new system) - If no
changelog.d/but the repo useschangelog_entry.yaml, use the legacy system
New system: towncrier (changelog.d/ fragments)
Used by: policyengine-skills, the generated policyengine-claude wrapper, and newer repos with a changelog.d/ directory.
echo "Description of change." > changelog.d/branch-name.added.md
Fragment filename format: {name}.{type}.md
Types: added (minor), changed (patch), fixed (patch), removed (minor), breaking (major)
GitHub Actions runs towncrier build on merge to compile fragments into CHANGELOG.md.
Legacy system: changelog_entry.yaml
Used by: policyengine-us, policyengine-uk, and other country model repos.
Create changelog_entry.yaml at repository root:
- bump: patch # or minor, major
changes:
added:
- Description of new feature
fixed:
- Description of bug fix
GitHub Actions automatically updates CHANGELOG.md and changelog.yaml on merge.
DO NOT (either system):
- Run
make changelogmanually during PR creation - Commit
CHANGELOG.mdin your PR - Modify main changelog files directly
Git workflow and common pitfalls
See the parent PolicyEngine/CLAUDE.md for full git workflow, branch naming, commit message format, and common AI pitfalls (file versioning, formatter not run, env vars, wrong Python version). Key points:
- Create branches on PolicyEngine repos, NOT forks (forks fail CI)
- Always run
make formatbefore committing - Never create versioned files (app_v2.py, component_new.jsx)
- Include "Fixes #123" in PR descriptions
Repository Setup Patterns
Python Package Structure
policyengine-package/
├── policyengine_package/
│ ├── __init__.py
│ ├── core/
│ ├── calculations/
│ └── utils/
├── tests/
│ ├── test_calculations.py
│ └── test_core.py
├── pyproject.toml
├── Makefile
├── CLAUDE.md
├── CHANGELOG.md
└── README.md
React App Structure
policyengine-app/
├── src/
│ ├── components/
│ ├── pages/
│ ├── config/
│ │ └── environment.js
│ └── App.jsx
├── public/
├── package.json
├── .eslintrc.json
├── .prettierrc
└── README.md
Makefile Commands
Standard commands across PolicyEngine repos:
make install # Install dependencies
make test # Run tests
make format # Format code
make changelog # Update changelog (automation only, not manual)
make debug # Start dev server (apps)
make build # Production build (apps)
CI stability
See PolicyEngine/CLAUDE.md for full CI stability details (fork failures, rate limits, linting). Quick fixes: use make format before committing, use uv run pytest not bare pytest, create branches on PolicyEngine repos not forks.
Repo rename checklist
When renaming a PolicyEngine repository, references to the old name are often hardcoded across the org. Follow this checklist to avoid broken links, builds, and embeds.
1. Search the org for all references
# Find every file in the org that mentions the old repo name
gh api "/search/code?q=org:PolicyEngine+OLD_REPO_NAME" --paginate | jq '.items[] | {repo: .repository.full_name, path: .path}'
Review every result -- some will be docs/changelogs (safe to update later), others will break builds if not updated before the rename.
2. Common places where repo names are hardcoded
| Location | What to look for | Example |
|---|---|---|
| GitHub Actions workflows | PUBLIC_URL, checkout paths, artifact names |
PUBLIC_URL: https://policyengine.github.io/OLD_NAME |
| Iframe embeds in policyengine-app-v2 | src URLs in page components |
app/src/pages/*.jsx referencing OLD_NAME.github.io |
| README badges and links | Shield.io badges, repo links |  |
| package.json / pyproject.toml | name, repository, homepage fields |
"name": "old-name" |
| GitHub Pages URLs | Any URL containing policyengine.github.io/OLD_NAME |
Links in docs, blog posts, other READMEs |
| CLAUDE.md | Repo-specific instructions that reference the old name | Paths, URLs, skill references |
| Import paths (Python) | Package name derived from repo name | from old_name import ... |
| Vercel / deployment configs | Project names, domain aliases | vercel.json, Vercel dashboard settings |
| policyengine-skills source | Skill files that reference the repo | Links in SKILL.md files across the canonical source repo |
3. Cross-repo coordination
If the renamed repo is embedded in another site (e.g., via iframe or GitHub Pages), both repos need updates:
- In the renamed repo: Update
PUBLIC_URLand any self-referencing URLs in workflows, configs, and docs. - In the embedding repo: Update iframe
srcURLs, links, and any CI that depends on the old name. - Deploy order: Push the renamed repo's changes first (so the new URL is live), then update the embedding repo.
4. After renaming
- GitHub automatically redirects the old repo URL, but GitHub Pages URLs do not redirect --
policyengine.github.io/old-namewill 404. - Verify GitHub Pages is re-enabled under the new repo settings if it was active.
- Run the org-wide search again to catch anything you missed:
bash
gh api "/search/code?q=org:PolicyEngine+OLD_REPO_NAME" --paginate | jq '.total_count' - Update any external references (blog posts on policyengine.org, Notion docs, etc.) that link to the old GitHub Pages URL.
Resources
- Main CLAUDE.md:
/PolicyEngine/CLAUDE.md - Python Style: PEP 8, Ruff documentation
- React Style: Airbnb React/JSX Style Guide
- Testing: pytest documentation, Jest/RTL documentation
- Writing Style: See policyengine-writing-skill for blog posts, PR descriptions, and documentation
Examples
See PolicyEngine repositories for examples of standard-compliant code:
- policyengine-us: Python package standards
- policyengine-app: React app standards
- crfb-tob-impacts: Analysis repository standards
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
policyengine-healthcare
Healthcare program modeling in PolicyEngine-US — Medicaid, ACA marketplace, CHIP, and Medicare. Covers encoding rules, running analyses, and navigating the unique complexity of US healthcare programs. Triggers: "healthcare", "health insurance", "Medicaid", "ACA", "CHIP", "Medicare", "marketplace", "premium tax credit", "APTC", "PTC", "SLCSP", "benchmark plan", "rating area", "age curve", "family tier", "coverage gap", "Medicaid expansion", "MAGI", "medicaid_magi", "aca_magi", "medicaid_income_level", "medicaid_category", "enrollment", "takeup", "take-up", "per capita", "CSR", "cost sharing", "insurance premium", "second lowest silver", "required contribution percentage", "42 CFR", "IRC 36B", "categorical eligibility", "expansion adult", "healthcare reform", "healthcare analysis", "health policy".
policyengine-us
ALWAYS LOAD THIS SKILL FIRST before writing any PolicyEngine-US code. Contains the correct API patterns for household calculations and population simulations using the new policyengine package. Covers US federal and state taxes/benefits. Triggers: "what would", "how much would a", "benefit be", "eligible for", "qualify for", "single parent", "married couple", "family of", "household of", "if they earn", "earning $", "making $", "calculate benefits", "calculate taxes", "benefit for a", "what would I get", "what is the maximum", "what is the rate", "poverty line", "income limit", "benefit amount", "maximum benefit", "compare states", "TANF", "SNAP", "EITC", "CTC", "SSI", "WIC", "Section 8", "Medicaid", "ACA", "child tax credit", "earned income", "supplemental security", "housing voucher", "microsimulation", "population", "reform", "policy impact", "budgetary", "decile".
policyengine-uk
ALWAYS LOAD THIS SKILL FIRST before writing any PolicyEngine-UK code. Contains the correct API patterns for household calculations and population simulations using the new policyengine package (not policyengine_uk directly). Triggers: "what would", "how much would a", "benefit be", "eligible for", "qualify for", "single parent", "married couple", "family of", "household of", "if they earn", "with income of", "earning £", "making £", "calculate benefits", "calculate taxes", "benefit for a", "tax for a", "what would I get", "what would they get", "what is the rate", "what is the threshold", "personal allowance", "maximum benefit", "income limit", "benefit amount", "how much is", "Universal Credit", "child benefit", "pension credit", "housing benefit", "council tax", "income tax", "national insurance", "JSA", "ESA", "PIP", "disability living allowance", "working tax credit", "child tax credit", "Scotland", "Wales", "UK", "microsimulation", "population", "reform", "policy impact", "budgetary", "decile".
policyengine-canada
ALWAYS LOAD THIS SKILL FIRST before writing any PolicyEngine-Canada code. Contains Canadian federal and provincial tax/benefit rules for household calculations. IMPORTANT: PolicyEngine-Canada does NOT have representative population microdata. Do NOT attempt microsimulation or population-level estimates for Canada. Only provide household-level analysis (single-family impacts, eligibility, benefit amounts). Triggers: "what would", "how much would a", "benefit be", "eligible for", "qualify for", "single parent", "married couple", "family of", "household of", "if they earn", "earning $", "making $", "calculate benefits", "calculate taxes", "benefit for a", "what would I get", "what is the maximum", "what is the rate", "income limit", "benefit amount", "maximum benefit", "compare provinces", "CCB", "Canada Child Benefit", "GST credit", "HST credit", "GST/HST", "OAS", "Old Age Security", "GIS", "Guaranteed Income Supplement", "CWB", "Canada Workers Benefit", "EI", "Employment Insurance", "CPP", "Canada Pension Plan", "RRSP", "TFSA", "Ontario Child Benefit", "OCB", "Ontario Trillium Benefit", "OTB", "BC Climate Action", "Alberta Child Benefit", "Quebec", "CRA", "Canada Revenue Agency", "Canadian", "Canada", "Ontario", "British Columbia", "Alberta", "Saskatchewan", "Manitoba", "Nova Scotia", "New Brunswick", "PEI", "Newfoundland", "Yukon", "NWT", "Nunavut", "provincial tax", "federal tax Canada".
policyengine-ui-kit-consumer
This skill should be used when setting up a new project that uses @policyengine/ui-kit, debugging CSS or styling issues in a consumer app, or when Tailwind utility classes are not being generated. Also use when creating globals.css, configuring PostCSS, or troubleshooting "no styles", "no spacing", or "no layout" problems. Triggers: "ui-kit import", "globals.css setup", "Tailwind not working", "styles not applying", "utility classes missing", "setup ui-kit", "PostCSS config", "no styling", "CSS broken", "import ui-kit", "theme.css", "no layout", "no spacing", "@tailwindcss/postcss"
policyengine-tailwind-shadcn
Tailwind CSS v4 + shadcn/ui integration patterns for PolicyEngine frontend projects. Covers @theme namespaces, CSS variable conventions, SVG var() usage, and common mistakes. Triggers: "Tailwind v4", "@theme", "shadcn", "CSS variables", "design tokens CSS", "theme.css", "@theme inline"
Didn't find tool you were looking for?