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:

uv run poe 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 poe tasks documented below in the Relationships section (or uv run poe gh-sub / uv run poe 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 featureREST endpoint
Parent: / Theme Epics / Issues checklistSub-issues (parent ↔ child hierarchy)POST …/sub_issues / DELETE …/sub_issue
Blocked by:Typed dependencyPOST …/dependencies/blocked_by
Blocks:Inverse — applied on the OTHER issuePOST …/dependencies/blocked_by (on the blocked side)
Related:No native featureprose only

From the UI

From the CLI (poe tasks)

For scripted work, the repo ships a helper script wrapped by three poe tasks (defined in pyproject.toml):

# Link children as sub-issues under a parent
uv run poe gh-sub 7 42 43 44

# Mark issue 44 as blocked by 43
uv run poe gh-block 44 43

# Inspect parent / sub-issues / blocking / blocked-by for an issue
uv run poe gh-show 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 calls the REST API, resolves issue numbers to the numeric issue ids the link endpoints take, and treats “already linked” as a no-op so re-runs are safe. Set GH_REPO=owner/repo to act on another repository.

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 REST calls (for reference)

Under the hood the helper runs these (both link endpoints take the issue’s numeric id, not its number):

gh api repos/{owner}/{repo}/issues/<parent>/sub_issues -F sub_issue_id=<child id>
gh api repos/{owner}/{repo}/issues/<issue>/dependencies/blocked_by -F issue_id=<blocker id>

An issue’s id comes from gh api repos/{owner}/{repo}/issues/<N> --jq .id. The script uses REST rather than GraphQL because GraphQL is blocked in Claude sessions.


Pre-commit checklist

Run these locally before opening a PR:

uv run poe format       # ruff format . + ruff check --fix .   (applies changes)
uv run poe lint         # ruff check .                         (CI-style check)
uv run poe typecheck    # ty check
uv run poe test         # pytest

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

Pre-commit hooks run ruff on every commit. Run uv run poe 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.