Claude
Skills
Sign in
Back

policyengine-standards

Included with Lifetime
$97 forever

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"

Cloud & DevOps

What this skill does


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

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

### Running Tests

```bash
# 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 format` or `ruff format .`
- **Check without changes**: `ruff format --check .`

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

```bash
# 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.js` pattern 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:**
1. Check if the repo has a `changelog.d/` directory -- if yes, use **towncrier** (new system)
2. If no `changelog.d/` but the repo uses `changelog_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.

```bash
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:
```yaml
- 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 changelog` manually during PR creation
- Commit `CHANGELOG.md` in 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 format` before committing
- Never create versioned files (app_v2.py, component_new.jsx)

Related in Cloud & DevOps