用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/tools-only/X-Skills --skill policyengine-standards命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | policyengine-standards |
| description | PolicyEngine coding standards, formatters, CI requirements, and development best practices |
Use this skill to ensure code meets PolicyEngine's development standards and passes CI checks.
⚠️ MUST USE Python 3.13 - Do NOT downgrade to older versions
python --versionpyproject.toml to specify version requirements⚠️ ALWAYS use uv run for Python commands - Never use bare python or pytest
uv run python script.py, uv run pytest tests/python script.py, pytest tests/⚠️ MUST USE Jupyter Book 2.0 (MyST-NB) - NOT Jupyter Book 1.x
myst build docs (NOT jb build)make format or language-specific formattermake test to ensure all tests passCommon 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
When creating PRs, use the /create-pr command:
/create-pr
This command:
Why this works: The command contains explicit polling logic that Claude executes, so it actually waits instead of giving up.
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
gh checks
ELAPSED=$((ELAPSED + POLL_INTERVAL))
❌ WRONG:
"I've created the PR as draft. CI checks will take a few minutes.
I'll check back later once they complete."
Why wrong: You cannot check back later. The chat session ends.
✅ CORRECT:
"I've created the PR as draft. Now polling CI status every 15 seconds..."
[Actually polls using while loop]
"CI checks completed. All passed! Marking PR as ready for review."
Always create as draft when:
Create as ready only when:
Standard flow:
# 1. Ensure branch is pushed
git push -u origin feature-branch
# 2. Create PR as draft (use --repo for cross-fork PRs)
gh pr create --repo PolicyEngine/policyengine-us --draft --title "..." --body "..."
# 3. Wait for CI (use polling loop - see pattern above)
# 4. If CI passes:
gh pr ready $PR_NUMBER
# 5. If CI fails:
echo "CI failed. PR remains draft. Fix issues and push again."
PolicyEngine follows Test-Driven Development practices across all repositories.
1. Write test first (RED):
# tests/test_new_feature.py
def test_california_eitc_calculation():
"""Test California EITC for family with 2 children earning $30,000."""
situation = create_family(income=30000, num_children=2, state="CA")
sim = Simulation(situation=situation)
ca_eitc = sim.calculate("ca_eitc", 2026)[0]
# Test fails initially (feature not implemented yet)
assert ca_eitc == 3000, "CA EITC should be $3,000 for this household"
2. Implement feature (GREEN):
# policyengine_us/variables/gov/states/ca/tax/income/credits/ca_eitc.py
class ca_eitc(Variable):
value_type = float
entity = TaxUnit
definition_period = YEAR
def formula(tax_unit, period, parameters):
# Implementation to make test pass
federal_eitc = tax_unit("eitc", period)
return federal_eitc * parameters(period).gov.states.ca.tax.eitc.match
3. Refactor (REFACTOR):
# Clean up, optimize, add documentation
# All while tests continue to pass
Why PolicyEngine uses TDD:
Country model development:
See policyengine-core-skill and country-models agents for details.
Python (pytest):
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"
React (Jest + RTL):
import { render, screen } from '@testing-library/react';
import TaxCalculator from './TaxCalculator';
test('displays calculated tax', () => {
render(<TaxCalculator income={50000} />);
// Test what user sees, not implementation
expect(screen.getByText(/\$5,000/)).toBeInTheDocument();
});
Python:
tests/
├── test_variables/
│ ├── test_income.py
│ ├── test_deductions.py
│ └── test_credits.py
├── test_parameters/
└── test_simulations/
React:
src/
├── components/
│ └── TaxCalculator/
│ ├── TaxCalculator.jsx
│ └── TaxCalculator.test.jsx
Python:
# All tests
make test
# With uv
uv run pytest tests/ -v
# Specific test
uv run pytest tests/test_credits.py::test_ctc_for_two_children -v
# With coverage
uv run pytest tests/ --cov=policyengine_us --cov-report=html
React:
# All tests
make test
# Watch mode
npm test -- --watch
# Specific test
npm test -- TaxCalculator.test.jsx
# Coverage
npm test -- --coverage
Good tests:
Bad tests:
# Step 1: Write test (RED)
def test_new_york_empire_state_child_credit():
"""Test NY Empire State Child Credit for family with 1 child.
Based on NY Tax Law Section 606(c-1).
Family earning $50,000 with 1 child under 4 should receive $330.
"""
situation = create_family(
income=50000,
num_children=1,
child_ages=[2],
state="NY"
)
sim = Simulation(situation=situation)
credit = sim.calculate("ny_empire_state_child_credit", 2026)[0]
assert credit == 330, "Should receive $330 for child under 4"
# Test fails - feature doesn't exist yet
# Step 2: Implement (GREEN)
# Create variable in policyengine_us/variables/gov/states/ny/...
# Test passes
# Step 3: Refactor
# Optimize, add documentation, maintain passing tests
make format or black . -l 79black . -l 79 --check# Format all Python files
make format
# Check if formatting is needed (CI-style)
black . -l 79 --check
# Imports: Grouped and alphabetized
import os
import sys
from pathlib import Path # stdlib
import numpy as np
import pandas as pd # third-party
from policyengine_us import Simulation # local
# Naming conventions
class TaxCalculator: # CamelCase for classes
pass
def calculate_income_tax(income): # snake_case for functions
annual_income = income * 12 # snake_case for variables
return annual_income
# Type hints (recommended)
def calculate_tax(income: float, state: str) -> float:
"""Calculate state income tax.
Args:
income: Annual income in dollars
state: Two-letter state code
Returns:
Tax liability in dollars
"""
pass
# Error handling - catch specific exceptions
try:
result = simulation.calculate("income_tax", 2026)
except KeyError as e:
raise ValueError(f"Invalid variable name: {e}")
import pytest
def test_ctc_calculation():
"""Test Child Tax Credit calculation for family with 2 children."""
situation = create_family(income=50000, num_children=2)
sim = Simulation(situation=situation)
ctc = sim.calculate("ctc", 2026)[0]
assert ctc == 4400, "CTC should be $2200 per child"
Run tests:
# All tests
make test
# Or with uv
uv run pytest tests/ -v
# Specific test
uv run pytest tests/test_tax.py::test_ctc_calculation -v
# With coverage
uv run pytest tests/ --cov=policyengine_us --cov-report=html
npm run lint -- --fix && npx prettier --write .npm run lint -- --max-warnings=0# Format all files
make format
# Or manually
npm run lint -- --fix
npx prettier --write .
# Check if formatting is needed (CI-style)
npm run lint -- --max-warnings=0
// Use functional components only (no class components)
import { useState, useEffect } from "react";
function TaxCalculator({ income, state }) {
const [tax, setTax] = useState(0);
useEffect(() => {
// Calculate tax when inputs change
calculateTax(income, state).then(setTax);
}, [income, state]);
return (
<div>
<p>Tax: ${tax.toLocaleString()}</p>
</div>
);
}
// File naming
// - Components: PascalCase.jsx (TaxCalculator.jsx)
// - Utilities: camelCase.js (formatCurrency.js)
// Environment config - use config file pattern
// src/config/environment.js
const config = {
API_URL: process.env.NODE_ENV === 'production'
? 'https://api.policyengine.org'
: 'http://localhost:5000'
};
export default config;
CRITICAL: For PRs, ONLY modify changelog_entry.yaml. NEVER manually update CHANGELOG.md or changelog.yaml.
Terminology Note: When someone says "add a changelog entry" or "needs a changelog entry" in PolicyEngine context, they mean:
changelog_entry.yaml (the PR-level entry file)CHANGELOG.md (the main changelog file)changelog.yaml (the compiled changelog)Correct Workflow:
Create changelog_entry.yaml at repository root:
- bump: patch # or minor, major
changes:
added:
- Description of new feature
fixed:
- Description of bug fix
changed:
- Description of change
Commit ONLY changelog_entry.yaml with your code changes
GitHub Actions automatically updates CHANGELOG.md and changelog.yaml on merge
DO NOT:
make changelog manually during PR creationCHANGELOG.md or changelog.yaml in your PRCreate branches on PolicyEngine repos, NOT forks
Branch naming: feature-name or fix-issue-123
Commit messages:
Add CTC reform analysis for CRFB report
- Implement household-level calculations
- Add state-by-state comparison
- Create visualizations
Fixes #123
PR description: Include "Fixes #123" to auto-close issues
Never do these:
.env files--no-verifyAlways do:
Since many PRs are AI-generated, watch for these common mistakes:
❌ Wrong:
# Creating new versions instead of fixing originals
app_new.py
app_v2.py
component_refactored.jsx
✅ Correct:
# Always modify the original file
app.py # Fixed in place
❌ Wrong: Committing without formatting (main cause of CI failures)
✅ Correct:
# Python
make format
black . -l 79
# React
npm run lint -- --fix
npx prettier --write .
❌ Wrong:
// React env vars without REACT_APP_ prefix
const API_URL = process.env.API_URL; // Won't work!
✅ Correct:
// Use config file pattern instead
import config from './config/environment';
const API_URL = config.API_URL;
❌ Wrong: Downgrading to Python 3.10 or older
✅ Correct: Use Python 3.13 as specified in project requirements
❌ Wrong: Running make changelog and committing CHANGELOG.md
✅ Correct: Only create changelog_entry.yaml in PR
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
policyengine-app/
├── src/
│ ├── components/
│ ├── pages/
│ ├── config/
│ │ └── environment.js
│ └── App.jsx
├── public/
├── package.json
├── .eslintrc.json
├── .prettierrc
└── README.md
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)
1. Fork PRs Fail
2. GitHub API Rate Limits
3. Linting Failures
make format before committing4. Test Failures in CI but Pass Locally
uv run prefixuv run pytest instead of pytestWhen 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.
# 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.
| 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-claude skills | Skill files that reference the repo | Links in SKILL.md files across this plugin |
If the renamed repo is embedded in another site (e.g., via iframe or GitHub Pages), both repos need updates:
PUBLIC_URL and any self-referencing URLs in workflows, configs, and docs.src URLs, links, and any CI that depends on the old name.policyengine.github.io/old-name will 404.gh api "/search/code?q=org:PolicyEngine+OLD_REPO_NAME" --paginate | jq '.total_count'
changelog_entry.yaml created (not CHANGELOG.md)make testPython:
make format # Format code
black . -l 79 --check # Check formatting
uv run pytest tests/ -v # Run tests
React:
make format # Format code
npm run lint -- --max-warnings=0 # Check linting
npm test # Run tests
# 1. Format
make format
# 2. Test
make test
# 3. Check linting
# Python: black . -l 79 --check
# React: npm run lint -- --max-warnings=0
# 4. Stage and commit
git add .
git commit -m "Description
Fixes #123"
# 5. Push and watch CI
git push
/PolicyEngine/CLAUDE.mdSee PolicyEngine repositories for examples of standard-compliant code: