Skip to content
Back to skills

003 Name Skill 70307e51

ASecurity

PolicyEngine coding standards, formatters, CI requirements, and development best practices

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
documentationjavascriptpythongojavabashreactnodetestinggitapi

Works with

  • api

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 003-name-skill_70307e51 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 003 Name Skill 70307e51?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 003 Name Skill 70307e51
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-003-name-skill-70307e51/badge)](https://www.skillsdirectory.com/skills/tools-only-003-name-skill-70307e51)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
---
name: policyengine-standards
description: PolicyEngine coding standards, formatters, CI requirements, and development best practices
---

# 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.toml` to 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` (NOT `jb build`)
- Use MyST markdown syntax

## Before Committing - Checklist

1. **Write tests first** (TDD - see below)
2. **Format code**: `make format` or language-specific formatter
3. **Run tests**: `make test` to ensure all tests pass
4. **Check linting**: Ensure no linting errors
5. **Use config files**: Prefer config files over environment variables
6. **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:**

```bash
/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:**

```bash
# 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.
```

### DO NOT Say "I'll Check Back Later"

**❌ 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."
```

### When to Create Draft vs Ready

**Always create as draft when:**
- CI checks are configured
- User asks to wait for CI
- Making automated changes
- Unsure if CI will pass

**Create as ready only when:**
- User explicitly requests ready PR
- No CI configured
- CI already verified locally

### PR Workflow Standards

**Standard flow:**
```bash
# 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."
```

## Test-Driven Development (TDD)

PolicyEngine follows Test-Driven Development practices across all repositories.

### TDD Workflow

**1. Write test first (RED):**
```python
# 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):**
```python
# 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):**
```python
# Clean up, optimize, add documentation
# All while tests continue to pass
```

### TDD Benefits

**Why PolicyEngine uses TDD:**
- ✅ **Accuracy** - Tests verify implementation matches regulations
- ✅ **Documentation** - Tests show expected behavior
- ✅ **Regression prevention** - Changes don't break existing features
- ✅ **Confidence** - Safe to refactor
- ✅ **Isolation** - Multi-agent workflow (test-creator and rules-engineer work separately)

### TDD in Multi-Agent Workflow

**Country model development:**
1. **@document-collector** gathers regulations
2. **@test-creator** writes tests from regulations (isolated, no implementation access)
3. **@rules-engineer** implements from regulations (isolated, no test access)
4. Both work from same source → tests verify implementation accuracy

**See policyengine-core-skill and country-models agents for details.**

### Test Examples

**Python (pytest):**
```python
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):**
```javascript
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();
});
```

### Test Organization

**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
```

### Running Tests

**Python:**
```bash
# 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:**
```bash
# All tests
make test

# Watch mode
npm test -- --watch

# Specific test
npm test -- TaxCalculator.test.jsx

# Coverage
npm test -- --coverage
```

### Test Quality Standards

**Good tests:**
- ✅ Test behavior, not implementation
- ✅ Clear, descriptive names
- ✅ Single assertion per test (when possible)
- ✅ Include documentation (docstrings)
- ✅ Based on official regulations with citations

**Bad tests:**
- ❌ Testing private methods
- ❌ Mocking everything
- ❌ No assertion messages
- ❌ Magic numbers without explanation

### Example: TDD for New Feature

```python
# 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
```

## Python Standards

### Formatting
- **Formatter**: Black with 79-character line length
- **Command**: `make format` or `black . -l 79`
- **Check without changes**: `black . -l 79 --check`

```bash
# Format all Python files
make format

# Check if formatting is needed (CI-style)
black . -l 79 --check
```

### Code Style
```python
# 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}")
```

### Testing
```python
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:**
```bash
# 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
```

## JavaScript/React Standards

### Formatting
- **Formatters**: Prettier + ESLint
- **Command**: `npm run lint -- --fix && npx prettier --write .`
- **CI Check**: `npm run lint -- --max-warnings=0`

```bash
# 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
```

### Code Style
```javascript
// 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;
```

### React Component Size
- Keep components under 150 lines after formatting
- Extract complex logic into custom hooks
- Split large components into smaller ones

## Version Control Standards

### Changelog Management

**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:
- ✅ Create/update `changelog_entry.yaml` (the PR-level entry file)
- ❌ NOT editing `CHANGELOG.md` (the main changelog file)
- ❌ NOT editing `changelog.yaml` (the compiled changelog)

**Correct Workflow:**
1. Create `changelog_entry.yaml` at repository root:
   ```yaml
   - bump: patch  # or minor, major
     changes:
       added:
       - Description of new feature
       fixed:
       - Description of bug fix
       changed:
       - Description of change
   ```

2. Commit ONLY `changelog_entry.yaml` with your code changes

3. GitHub Actions automatically updates `CHANGELOG.md` and `changelog.yaml` on merge

**DO NOT:**
- ❌ Run `make changelog` manually during PR creation
- ❌ Commit `CHANGELOG.md` or `changelog.yaml` in your PR
- ❌ Modify main changelog files directly

### Git Workflow

1. **Create branches on PolicyEngine repos, NOT forks**
   - Forks cause CI failures due to missing secrets
   - Request write access if needed

2. **Branch naming**: `feature-name` or `fix-issue-123`

3. **Commit messages**:
   ```
   Add CTC reform analysis for CRFB report

   - Implement household-level calculations
   - Add state-by-state comparison
   - Create visualizations

   Fixes #123
   ```

4. **PR description**: Include "Fixes #123" to auto-close issues

### Common Git Pitfalls

**Never do these:**
- ❌ Force push to main/master
- ❌ Commit secrets or `.env` files
- ❌ Skip hooks with `--no-verify`
- ❌ Create versioned files (app_v2.py, component_new.jsx)

**Always do:**
- ✅ Fix original files in place
- ✅ Run formatters before pushing
- ✅ Reference issue numbers in commits
- ✅ Watch CI after filing PR

## Common AI Pitfalls

Since many PRs are AI-generated, watch for these common mistakes:

### 1. File Versioning
**❌ Wrong:**
```bash
# Creating new versions instead of fixing originals
app_new.py
app_v2.py
component_refactored.jsx
```

**✅ Correct:**
```bash
# Always modify the original file
app.py  # Fixed in place
```

### 2. Formatter Not Run
**❌ Wrong:** Committing without formatting (main cause of CI failures)

**✅ Correct:**
```bash
# Python
make format
black . -l 79

# React
npm run lint -- --fix
npx prettier --write .
```

### 3. Environment Variables
**❌ Wrong:**
```javascript
// React env vars without REACT_APP_ prefix
const API_URL = process.env.API_URL;  // Won't work!
```

**✅ Correct:**
```javascript
// Use config file pattern instead
import config from './config/environment';
const API_URL = config.API_URL;
```

### 4. Using Wrong Python Version
**❌ Wrong:** Downgrading to Python 3.10 or older

**✅ Correct:** Use Python 3.13 as specified in project requirements

### 5. Manual Changelog Updates
**❌ Wrong:** Running `make changelog` and committing `CHANGELOG.md`

**✅ Correct:** Only create `changelog_entry.yaml` in PR

## 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:

```bash
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

### Common CI Issues

**1. Fork PRs Fail**
- **Problem**: PRs from forks don't have access to repository secrets
- **Solution**: Create branches directly on PolicyEngine repos

**2. GitHub API Rate Limits**
- **Problem**: Smoke tests fail with 403 errors
- **Solution**: Re-run failed jobs (different runners have different limits)

**3. Linting Failures**
- **Problem**: Code not formatted before commit
- **Solution**: Always run `make format` before committing

**4. Test Failures in CI but Pass Locally**
- **Problem**: Missing `uv run` prefix
- **Solution**: Use `uv run pytest` instead of `pytest`

## 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

```bash
# 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 | `![CI](https://github.com/PolicyEngine/OLD_NAME/actions/...)` |
| **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 |

### 3. Cross-repo coordination

If the renamed repo is embedded in another site (e.g., via iframe or GitHub Pages), **both repos need updates**:

1. **In the renamed repo**: Update `PUBLIC_URL` and any self-referencing URLs in workflows, configs, and docs.
2. **In the embedding repo**: Update iframe `src` URLs, links, and any CI that depends on the old name.
3. **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-name` will 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.

## Best Practices Checklist

### Code Quality
- [ ] Code formatted with Black (Python) or Prettier (JS)
- [ ] No linting errors
- [ ] All tests pass
- [ ] Type hints added (Python, where applicable)
- [ ] Docstrings for public functions/classes
- [ ] Error handling with specific exceptions

### Version Control
- [ ] Only `changelog_entry.yaml` created (not CHANGELOG.md)
- [ ] Commit message references issue number
- [ ] Branch created on PolicyEngine repo (not fork)
- [ ] No secrets or .env files committed
- [ ] Original files modified (no _v2 or _new files)

### Testing
- [ ] Tests written for new functionality
- [ ] Tests pass locally with `make test`
- [ ] Coverage maintained or improved
- [ ] Edge cases handled

### Documentation
- [ ] README updated if needed
- [ ] Code comments for complex logic
- [ ] API documentation updated if needed
- [ ] Examples provided for new features

## Quick Reference

### Format Commands by Language

**Python:**
```bash
make format                # Format code
black . -l 79 --check      # Check formatting
uv run pytest tests/ -v    # Run tests
```

**React:**
```bash
make format                              # Format code
npm run lint -- --max-warnings=0         # Check linting
npm test                                 # Run tests
```

### Pre-Commit Checklist
```bash
# 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
```

## Resources

- **Main CLAUDE.md**: `/PolicyEngine/CLAUDE.md`
- **Python Style**: PEP 8, Black 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
- **givecalc**: Streamlit app standards
- **crfb-tob-impacts**: Analysis repository standards

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…