Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Contributing

This page documents the label taxonomy, epic model, and issue conventions used in this project.


Label Taxonomy

Every issue carries exactly one type:*, one or more area:*, at most one layer:*, one wave:*, and one priority:*.

ScopeLabels
Typetype:epic-wave, type:epic-theme, type:feature, type:design, type:chore, type:docs, type:bug, type:research
Areaarea:engineering, area:testing, area:docs, area:code — extend per project (e.g. area:algorithmic, area:integration)
Layerlayer:0-primitives, layer:1-components, layer:2-models — only if the project has a formal layer stack
Wavewave:0, wave:1, wave:2, … — release-scoped phases (add wave:N-<slug> variants for descriptive labels)
Prioritypriority:p0 (blocker), priority:p1 (high), priority:p2 (normal)

Bootstrap the standard set on a fresh repo:

make gh-labels

The script lives at .github/scripts/create-labels.sh. Edit the hard-coded create … entries in the script to customise area:*, layer:*, and wave:* for the project, then re-run — the script is idempotent.


Two-Layer Epic Model

Work is organised as Wave → Theme → Issue:

[EPIC] Wave N   (L1)   —   release-scoped mega-epic, owns a milestone
  ├── [EPIC] <theme>   (L2)   —   parallel-safe group of issues
  │     ├── feature / design / chore / bug issue
  │     └── …
  └── [EPIC] <theme>   (L2)
        └── …

Stragglers are discouraged: any type:feature issue should attach to a Theme epic. If no suitable theme exists, create one first.


Issue Format

Issue templates live in .github/ISSUE_TEMPLATE/:

TemplateWhen to use
Epic — Wave (L1)Opening a new release-scoped wave
Epic — Theme (L2)Grouping related issues inside a wave
Feature / EnhancementOne substantial deliverable
Design / ADRResolve an open design question for a new API
Bug reportSomething isn’t working
Research / Comparative AnalysisStudy prior art (external repo, paper) and produce a prioritized roadmap of follow-up issues

Feature + Design templates include two optional sections for context-heavy issues:

Delete either section if not relevant. Both exist so that an implementer (human or AI agent) can work on the issue without opening other repos or channels.


Creating issues

New issues should use one of the templates in .github/ISSUE_TEMPLATE/ so that labels, required sections, and title conventions are consistent.

From the UI

Click New issue on the repo’s Issues tab and pick a template. The body is pre-filled with the section scaffolding — fill in, apply labels and milestone, submit.

From the CLI

For drafted content, write the body to a file (minus the template’s YAML frontmatter), then:

gh issue create \
  --title "feat(primitives): diagonal BLR update step" \
  --body-file /tmp/issue-body.md \
  --label "type:feature,area:algorithmic,layer:0-primitives,wave:1,priority:p1" \
  --milestone "v0.1-diagonal"

To open the UI in a browser with a template pre-selected:

gh issue create --web

The web flow opens /issues/new/choose so you can pick the template in the UI. gh issue create does not support pre-selecting a template for the web path; the --template flag is only used as starting body text in the non-web flow.

After the issue is created, apply native parent / blocked-by links via the Makefile targets documented below in the Relationships section (or make gh-sub / make gh-block).

From Claude Code

The create-gh-issue skill (at .claude/skills/create-gh-issue/SKILL.md) guides Claude through:

Example prompts:


Drafting a wave backlog

For large planning exercises (new wave, new release, large refactor), draft the whole backlog as one markdown file before opening GitHub issues. A template lives at docs/templates/wave-backlog.md.

Why:

Workflow:

  1. Copy docs/templates/wave-backlog.md into your project’s .plans/ directory (gitignored). Rename to describe the wave — e.g. .plans/wave-1-backlog.md.

  2. Pick a short project prefix (e.g. PYX for pyrox, OBX for optax_bayes). Number drafts sequentially.

  3. Fill in shared context at the top, then draft each issue body.

  4. When the file is ready, open each draft as a real GitHub issue using the matching .github/ISSUE_TEMPLATE/. Copy the body verbatim.

  5. Record GitHub issue numbers next to draft IDs in the backlog file, or replace draft IDs throughout. Update cross-references.

  6. Either delete the backlog file or archive it in .plans/archive/.


Relationships

Use an explicit ## Relationships block at the bottom of each issue / epic body:

## Relationships
- Parent: #<theme-epic>
- Blocked by: #
- Blocks: #
- Related: #

GitHub’s task-list feature links bidirectionally from the parent, so checklist items in a Theme epic body auto-show in the referenced issues.

Applying native GitHub relationships

The prose ## Relationships block above is a human-readable record. GitHub also exposes two native relationship features that power the UI sub-issue panel and the dependency graph — apply both:

Prose lineNative featureMutation
Parent: / Theme Epics / Issues checklistSub-issues (parent ↔ child hierarchy)addSubIssue / removeSubIssue
Blocked by:Typed dependencyaddBlockedBy
Blocks:Inverse — applied on the OTHER issueaddBlockedBy (on the blocked side)
Related:No native featureprose only

From the UI

From the CLI (Makefile targets)

For scripted work, the repo ships a helper script wrapped by three make targets:

# Link children as sub-issues under a parent
make gh-sub PARENT=7 CHILDREN="42 43 44"

# Mark issue 44 as blocked by 43
make gh-block ISSUE=44 BLOCKED_BY=43

# Inspect parent / sub-issues / blocking / blocked-by for an issue
make gh-show ISSUE=44

Direct script usage (same functionality plus unlink commands):

bash .github/scripts/link-issues.sh sub     <parent> <child> [<child> ...]
bash .github/scripts/link-issues.sh block   <issue> <blocker>
bash .github/scripts/link-issues.sh unsub   <parent> <child>
bash .github/scripts/link-issues.sh unblock <issue> <blocker>
bash .github/scripts/link-issues.sh show    <issue>

The script resolves issue numbers to GraphQL node IDs automatically, and treats “already linked” as a no-op so re-runs are safe.

From Claude Code

The link-gh-issues skill (at .claude/skills/link-gh-issues/SKILL.md) guides Claude through the same operations — useful for bulk-applying relationships from a drafted wave backlog, parsing the Issues checklist out of a theme epic body, or verifying that the native links match the prose.

Example prompts:

Raw GraphQL (for reference)

Under the hood the helper runs:

mutation { addSubIssue(input: { issueId: "<parent-node-id>", subIssueId: "<child-node-id>" }) { subIssue { number } } }
mutation { addBlockedBy(input: { issueId: "<this-node-id>", blockingIssueId: "<blocker-node-id>" }) { issue { number } } }

Issue node IDs are resolved via gh api repos/:owner/:repo/issues/<N> --jq .node_id.


Pre-commit checklist

Run these locally before opening a PR:

make format       # ruff format . + ruff check --fix .   (applies changes)
make lint         # ruff check .                         (CI-style check)
make typecheck    # ty check
make test         # pytest (whole suite, parallel)

Test lanes

The suite is split by marker so that PRs get a fast signal:

MarkerWhat it coversWhen it runs
(unmarked)primitives, oracles, gradients, extraction, simulationsevery PR — make test-fast, ~1 min
slowpoint-process samplers, Monte-Carlo statistics, jit+grad sweepsmerge to main, v* tags, weekly, workflow_dispatch
integrationend-to-end MCMC fits through numpyro, subprocess runssame as slow
make test-fast    # fast lane only — mirrors the PR gate
make test-heavy   # slow + integration only
make test         # everything
make test-cov     # everything, with a coverage report

When adding a test, mark it slow if it draws point-process realisations or does a statistical check. Sampler-heavy modules set pytestmark = pytest.mark.slow at module level rather than decorating every function.

A test that looks slow in --durations output is often just paying the first XLA compile for a whole parametrized group — the sibling cases are milliseconds. Mark by what the test does, not by its stopwatch reading.

Coverage runs over the whole suite in the Extended Tests workflow, so it is deliberately absent from addopts; use make test-cov locally.

Note that make format mutates files — it formats and applies autofixes. make lint is the CI-parity read-only check. Run make format first, commit the result, then run make lint / make test to verify.

Pre-commit hooks run ruff on every commit. Run make precommit to apply them to all files manually.

Commit messages follow the Conventional Commits specification — enforced on PR titles by .github/workflows/conventional-commits.yml.