Skip to content

Commit 2127e6d

Browse files
authored
Add the fvtk Claude Code plugin (#139)
1 parent 6bf3d96 commit 2127e6d

10 files changed

Lines changed: 713 additions & 0 deletions

File tree

.claude/CLAUDE.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# fvtk: Claude Code guide
2+
3+
fvtk is a trimmed, fast fork of VTK 9.6.2, maintained by the PyVista community as a
4+
drop-in graphics layer for [PyVista](https://github.com/pyvista/pyvista). It is public,
5+
BSD-3 licensed, and developed in the open at `pyvista/fvtk`. Read `README.md` for the
6+
public story and `CONTRIBUTING.md` for the rules of engagement.
7+
8+
This file is the always-on context for working in this repo. The skills and commands under
9+
`.claude/` encode the recurring maintenance jobs.
10+
11+
## The prime directive: parity
12+
13+
fvtk is a **bit-exact drop-in for stock VTK 9.6.2 by default** (`maxULP = 0`). Every change
14+
falls into one of two buckets:
15+
16+
1. **Byte-identical → ships on by default.** Must pass `tests/bitexact/` at `maxULP = 0`,
17+
including thread-count determinism for anything threaded.
18+
2. **Correct but reorders output → opt-in behind `fvtk.EnableFast()`.** Relaxed-order parity
19+
gate plus an engagement check proving the fast path ran.
20+
21+
Positions and values are sacred. Only emission order is ever negotiable. Backported upstream
22+
bug fixes are the one deliberate exception: they intentionally diverge from stock 9.6.2 to fix
23+
a real defect, and must be recorded as such. See the `parity-contract` skill before touching
24+
any filter, kernel, or render path.
25+
26+
## Repo map
27+
28+
- `fvtk-config/`: the build profiles and the trim lists (`_modules_minimal.cmake`,
29+
`_nowrap_classes.cmake`, `_nocompile_classes.cmake`). See the `module-trimming` skill.
30+
- `ci/`: build and validation scripts (`build-fvtk.sh` entry, `run-bitexact.sh`,
31+
`run-renderexact.sh`, `run-regression.sh`, `run-pyvista.sh`, `build-sdk.sh`) and the
32+
cibuildwheel backend under `ci/cibw/`.
33+
- `tests/bitexact/`: the byte-exact gate. `tests/renderexact/`: pixel-exact render gate.
34+
`tests/regression/`: fvtk's own regression scenes. `tests/pyvista/`: the PyVista parity
35+
suite and its pinned ref (lands with PR #112).
36+
- `docs/`: `build-internals.md` (the lever set and parity gates, read this first for build
37+
work), `versioning.md`, `abi3-feasibility.md`, `threading-bitexact.md`, `ci-closers.md`,
38+
`ci-runners.md`.
39+
- `tools/`: `pgo-train.py`, `fvtk_shim.py` (the `vtkmodules``fvtk` import redirect used by
40+
every parity harness).
41+
- The module trees (`Common/`, `Filters/`, `Rendering/`, `IO/`, …) are the forked VTK source.
42+
43+
## Build and validate
44+
45+
The build runs inside `nix-shell` (pinned cmake 4.1.2, Python 3.13, GL/EGL/OSMesa). Entry:
46+
47+
```bash
48+
./build-fvtk.sh # default profile (minimal), LTO on
49+
PROFILE=fast ./build-fvtk.sh # fast iteration, LTO off
50+
```
51+
52+
Validation gates (also run in CI):
53+
54+
```bash
55+
ci/run-bitexact.sh # byte-exact vs stock vtk==9.6.2 (maxULP=0)
56+
ci/run-renderexact.sh # pixel-exact offscreen render (EGL/Mesa)
57+
ci/run-regression.sh # fvtk's regression scenes
58+
ci/run-pyvista.sh # PyVista's own suite against the built wheel (PR #112)
59+
```
60+
61+
The parity harnesses install stock `vtk==9.6.2` and the fvtk wheel on separate venvs and
62+
redirect `vtkmodules``fvtk` via `tools/fvtk_shim.py`. Never install stock `vtk` into the
63+
fvtk venv. Details and env vars are in `tests/bitexact/README.md` and the `parity-contract`
64+
skill.
65+
66+
## Contribution workflow
67+
68+
- Branch from `main`; **never push to `main`**. Every change lands through a pull request.
69+
- One logical change per PR. Backports are one MR per PR (see the `backport-vtk` skill).
70+
- State which parity bucket the change is in, with its evidence, in the PR body.
71+
- CI on `main` is always green. A red branch is the branch's fault; do not wave it off as
72+
flaky. Run the relevant gate locally before pushing. If main's CI is genuinely failing
73+
that is an issue that needs to be rectified immediately.
74+
75+
## Commits and PRs
76+
77+
- **Single-line commit messages**, matching the conventional-commit style already in the
78+
history: `fix(opengl): …`, `perf(SMP): …`, `test(bitexact): …`, `ci: …`, `docs: …`,
79+
`chore: …`, `build: …`. Pick the scope from the touched area.
80+
- Never `git add -A` or `git add .`; stage explicit paths by name.
81+
- Never `--amend` a pushed commit, never use a HEREDOC commit body.
82+
- **No additional attribution anywhere**: no `Co-Authored-By: Claude`,
83+
`🤖 Generated with…`, `noreply@anthropic.com`, or `[Claude Code]` in commits, PR bodies,
84+
PR/issue comments, or release notes.
85+
86+
## Scope discipline
87+
88+
fvtk is a community fork for PyVista, and every public artifact stays framed that way: it
89+
exists to give PyVista a graphics layer that is tested, fast, and released on a community
90+
cadence. Keep rationale in commits, PRs, issues, and docs anchored to PyVista and scientific
91+
visualization. Do not reference, name, or speculate about specific downstream commercial users
92+
or private infrastructure in anything that lands in this repo or on GitHub. When a motivation
93+
would only make sense by pointing at a particular downstream consumer, frame it generically
94+
("for large-scale visualization workloads") instead. When unsure, ask.
95+
96+
## Skills and commands
97+
98+
- `/backport-vtk`: sweep recently merged upstream VTK merge requests, triage the ones that
99+
matter to fvtk's shipped modules, and land each as its own PR linked back to the MR.
100+
- `/pyvista-bump`: advance the pinned PyVista ref and triage the parity suite (real
101+
regression vs image-cache drift).
102+
- `/fvtk-release`: cut an fvtk release (tag-driven publish to PyPI for the wheel and SDK).
103+
- `/triage-ci`: diagnose a failing CI run or PR check and propose the fix.
104+
- `parity-contract` (skill): the bit-exact contract, the gates, and the fast-filter checklist.
105+
- `module-trimming` (skill): the three trim levers and how to add, remove, or restore classes.
106+
- `vtk-backport-engineer` (agent): lands a single backport end to end; driven by
107+
`/backport-vtk`.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
name: vtk-backport-engineer
3+
description: Lands a single upstream VTK merge request as one fvtk PR, linked back to the MR. Spawned by the backport-vtk skill (usually several in parallel, one per MR). Given an MR iid, title, web_url, and changed-file list, it branches, ports the diff to the 9.6.2 tree, handles the parity story, validates, and opens the PR.
4+
tools: Glob, Grep, Read, Edit, Write, Bash
5+
model: sonnet
6+
color: blue
7+
---
8+
9+
You land exactly one upstream VTK merge request as one fvtk pull request. You are given the MR
10+
iid, title, web_url, and changed-file list. Read `.claude/skills/backport-vtk/SKILL.md`,
11+
`.claude/skills/backport-vtk/references/upstream-api.md`, `.claude/skills/parity-contract/SKILL.md`,
12+
and `.claude/CLAUDE.md` first. Stay within this one MR; do not sweep or triage others.
13+
14+
## Context you must hold
15+
16+
- fvtk is frozen on VTK 9.6.2 and has **no shared git history** with upstream, so `git
17+
cherry-pick` is impossible. You apply the MR's diff and adapt it to the 9.6.2 tree by hand
18+
where context has drifted.
19+
- fvtk's default build is a bit-exact drop-in for stock 9.6.2. A behavioral fix is a deliberate
20+
divergence and must be recorded; a degenerate-input guard usually leaves valid output
21+
unchanged.
22+
- This is a public, community-facing repo. Keep all prose framed around PyVista and VTK. No
23+
AI attribution. No reference to downstream commercial users or private infrastructure.
24+
25+
## Procedure
26+
27+
1. Branch `backport/vtk-mr-<iid>-<short-slug>` from `main`.
28+
2. Fetch the MR diff (`https://gitlab.kitware.com/vtk/vtk/-/merge_requests/<iid>.diff`). Try
29+
`git apply --3way --whitespace=nowarn`. For rejected hunks, open the file and port the change
30+
by hand, reading the diff and the MR description for intent. Keep the change minimal and
31+
matched to surrounding VTK style. Do not pull in unrelated upstream churn.
32+
3. Parity story:
33+
- Behavioral fix (changes valid-input output): update the affected `tests/bitexact/` op to
34+
the corrected expectation and note the divergence in the divergence ledger /
35+
`tests/precision_audit.md`.
36+
- Degenerate-input guard (crash/nullptr on bad input): add a regression test for the
37+
degenerate case; valid-input output stays byte-exact.
38+
4. Build with `PROFILE=fast ./build-fvtk.sh` and run the relevant gate
39+
(`ci/run-bitexact.sh` / `ci/run-renderexact.sh` / `ci/run-regression.sh`) to prove the fix
40+
and the parity outcome. If you cannot build in this environment, say so plainly and leave the
41+
PR as a draft with the gate steps listed; do not claim a gate passed that you did not run.
42+
5. Stage explicit paths, single-line conventional commit (`fix(<scope>): …`), no `git add -A`,
43+
no AI attribution.
44+
6. Open the PR. The body must:
45+
- link the MR: `Backports https://gitlab.kitware.com/vtk/vtk/-/merge_requests/<iid>`
46+
- describe the upstream defect in fvtk's own words
47+
- state the parity bucket and attach the gate evidence
48+
- pass the prose-hygiene rules in `.claude/CLAUDE.md` (no em dashes, no triads, no promo
49+
vocab, straight quotes, absolute dates)
50+
7. Report the PR number, the parity outcome, and whether any gate could not be run here.
51+
52+
If the diff turns out to touch only trimmed modules, or the change does not actually apply to
53+
the 9.6.2 tree in a meaningful way, stop and report that the MR should be skipped rather than
54+
forcing a PR.

.claude/commands/fvtk-release.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
description: Cut an fvtk release — pick the next 9.6.2.N segment, draft release notes from merged PRs, create the GitHub Release that tag-fires trusted publishing for the fvtk wheel and fvtk-sdk.
3+
argument-hint: "[next | 9.6.2.N | rc]"
4+
allowed-tools: Read, Bash, Grep, Glob
5+
---
6+
7+
Cut an fvtk release. The full scheme is `docs/versioning.md`; this is the operational runbook.
8+
fvtk publishes two PyPI projects in lockstep: `fvtk` (runtime wheel, via `ci.yml`, environment
9+
`pypi`) and `fvtk-sdk` (headers + CMake config, via `sdk.yml`, environment `pypi-sdk`). Both use
10+
OIDC trusted publishing — no stored tokens.
11+
12+
Argument: `next` (default, increment the last segment), an explicit `9.6.2.N`, or `rc` for a
13+
pre-release (`9.6.2.Nrc1`).
14+
15+
## The scheme
16+
17+
Version is `9.6.2.N`: the frozen VTK base `9.6.2` plus the fvtk iteration `N`. The version is
18+
**tag-driven** through `setuptools_scm` — there is no version file to edit. A bare `9.6.2` tag is
19+
reserved for an unmodified byte-exact build; fork releases always carry `.N`. Publishing is gated
20+
on a **published GitHub Release**, not a bare `git push --tags`.
21+
22+
## Steps
23+
24+
1. **Confirm `main` is green.** `gh run list --branch main --limit 5`. Do not release off a red
25+
`main`. Confirm you are releasing the intended commit.
26+
2. **Pick `N`.** List existing tags (`git tag`), find the last published `9.6.2.K`, and take
27+
`9.6.2.{K+1}` (or the explicit argument). For a pre-release, `9.6.2.{K+1}rc1`.
28+
3. **Draft release notes** from the merged PRs since the last release tag:
29+
`gh pr list --state merged --base main --search "merged:>=<last-release-date>"`. Group by
30+
conventional-commit scope (perf, fix, build, ci, test, docs). Lead with parity-relevant
31+
changes (new default-on optimizations, new opt-in fast filters, backported upstream fixes —
32+
link their MRs). Run the notes through the prose-hygiene rules in `.claude/CLAUDE.md`: no em
33+
dashes, no triads, no promo vocab, straight quotes, absolute dates, no AI attribution.
34+
4. **Create the GitHub Release** with the tag `9.6.2.N` targeting the chosen commit. Mark `rc`
35+
tags as pre-release. This is the single action that triggers publishing; only run it once the
36+
notes are reviewed.
37+
38+
```bash
39+
gh release create 9.6.2.N --target <sha> --title "fvtk 9.6.2.N" --notes-file <notes> [--prerelease]
40+
```
41+
5. **Watch the release workflows.** Publishing the Release fires `ci.yml` (builds the shipped
42+
LTO abi3 Linux + macOS + Windows wheels, runs the bit-exact and pixel-exact gates, then the
43+
`publish` job to PyPI `pypi`) and `sdk.yml` (`publish-sdk` to `pypi-sdk`). Watch both:
44+
`gh run watch`. If a gate fails, the publish does not run — fix forward and re-release; do not
45+
hand-upload.
46+
6. **Verify** both projects landed at the same version: `pip index versions fvtk` and
47+
`pip index versions fvtk-sdk` (or check the PyPI project pages). Pin `fvtk` and `fvtk-sdk`
48+
together.
49+
50+
## First-release note
51+
52+
The first time either project publishes (before it exists on PyPI), a **pending** trusted
53+
publisher must be registered on PyPI first: `fvtk` → workflow `ci.yml`, environment `pypi`;
54+
`fvtk-sdk` → workflow `sdk.yml`, environment `pypi-sdk`. Without it the publish job fails auth.
55+
56+
## Rebasing onto a new VTK
57+
58+
Out of scope for a normal release. When fvtk moves to a new upstream VTK, bump
59+
`VTK_BASE_VERSION` in `ci/cibw/fvtk_backend.py` and the base in `pyproject.toml`, then restart at
60+
`9.7.0.0`. See `docs/versioning.md`.

.claude/commands/pyvista-bump.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
description: Advance the pinned PyVista ref and triage the parity suite — separate real fvtk regressions (fix them) from image-cache drift (deselect them).
3+
argument-hint: "[pyvista-sha-or-tag | latest]"
4+
allowed-tools: Read, Edit, Bash, Grep, Glob
5+
---
6+
7+
Bump the PyVista pin fvtk tests against, run the parity suite, and triage the result. The suite
8+
and its machinery land with PR #112 (`tests/pyvista/`, `ci/run-pyvista.sh`,
9+
`.github/workflows/pyvista-bump.yml`). If `tests/pyvista/` is absent, #112 has not merged yet —
10+
say so and stop.
11+
12+
Argument: a PyVista SHA or tag to pin, or `latest` (default) to take the current tip of
13+
PyVista `main`.
14+
15+
## What the suite is
16+
17+
- `tests/pyvista/PYVISTA_REF` — the pinned PyVista commit. The only thing a bump edits.
18+
- `ci/run-pyvista.sh` — clones PyVista at the pin, installs it `--no-deps` over the built fvtk
19+
wheel with the `vtkmodules``fvtk` redirect, runs core + plotting offscreen under Xvfb +
20+
Mesa llvmpipe (software GLX, matching PyVista's own CI). Stock `vtk` is never installed, so an
21+
un-redirected import fails loud.
22+
- `tests/pyvista/deselect.txt` — the triage surface for image-cache drift. `needs_download`
23+
(network) and snake_case opt-out tests are handled automatically and stay out of it.
24+
- The CI job runs on the merge queue, `workflow_dispatch`, and the `pyvista-full` PR label.
25+
`pyvista-bump.yml` opens the weekly bump PR automatically; this command is the manual path and
26+
the triage helper.
27+
28+
## Steps
29+
30+
1. Confirm `tests/pyvista/` exists. Read the current `tests/pyvista/PYVISTA_REF` and
31+
`tests/pyvista/README.md`.
32+
2. Resolve the target ref. For `latest`, get the current `main` tip:
33+
`gh api repos/pyvista/pyvista/commits/main --jq .sha`. Show the old → new ref and the
34+
compare URL.
35+
3. Edit `tests/pyvista/PYVISTA_REF` to the new ref.
36+
4. Run the suite against a built fvtk wheel: `ci/run-pyvista.sh <wheel-dir>` locally if a wheel
37+
and the Xvfb + Mesa stack are available, otherwise dispatch CI on the branch with
38+
`gh workflow run ci.yml --ref <branch>` (or push the branch and apply the `pyvista-full`
39+
label, or just change `PYVISTA_REF`/`deselect.txt` — the `changes` detector triggers the
40+
gate) and watch it with `gh run watch`.
41+
5. **Triage every failure. This is the real work — distinguish two kinds:**
42+
- **Image-cache drift** — a plotting test whose rendered image differs from PyVista's
43+
committed baseline because of a benign rendering difference, not a parity break. These go in
44+
`tests/pyvista/deselect.txt` with a one-line reason. Only deselect drift; never use it to
45+
hide a real regression.
46+
- **Real fvtk regression** — a parity break, a crash, a wrong numerical result, an import
47+
that should have redirected but did not. **Do not deselect these.** Reproduce against the
48+
bitexact / renderexact gate, find the fvtk cause, and fix it (or file it and leave the pin
49+
unbumped). A real regression means fvtk diverged from the contract, which is the whole point
50+
of the gate.
51+
6. Re-run until green (real fixes applied, drift deselected with reasons).
52+
7. Open the bump PR: title `test(pyvista): bump pin to <short-sha>`, body stating the old → new
53+
ref, the compare link, what was deselected and why, and any fvtk fix that rode along. Follow
54+
the commit and prose rules in `.claude/CLAUDE.md`. Do not push to `main`.
55+
56+
If a failure is ambiguous (could be drift or a subtle regression), reproduce it through the
57+
bitexact or renderexact gate before deciding. When still unsure, leave it failing and surface it
58+
rather than deselecting.

.claude/commands/triage-ci.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
description: Diagnose a failing fvtk CI run or PR check — fetch the failing job logs, classify the failure against the known gates, and propose or apply the fix.
3+
argument-hint: "[PR# | run-id | branch]"
4+
allowed-tools: Read, Bash, Grep, Glob, Edit
5+
---
6+
7+
Diagnose a failing CI run and get to the fix. The gate layout is in `docs/ci-closers.md` and
8+
`docs/ci-runners.md`; this command maps a red check to its cause.
9+
10+
Argument: a PR number, a run id, or a branch. Default to the current branch's latest run.
11+
12+
## Pull the failure
13+
14+
```bash
15+
gh pr checks <PR#> # which checks are red
16+
gh run list --branch <branch> --limit 5
17+
gh run view <run-id> --log-failed # the failing job's log tail
18+
```
19+
20+
Read the failing job's log, not just its name. Identify which gate failed and the first real
21+
error (not the cascade after it).
22+
23+
## Classify against the gates
24+
25+
`ci.yml` runs these job groups. Match the red one:
26+
27+
- **build (Linux / macOS / Windows cibuildwheel)** — compile, link, codegen, wheel-repair, or
28+
smoke failure. Common causes: a missing class a kept path references (a trim closure broke —
29+
see the `module-trimming` skill), a configure-time module cascade, a platform-specific compile
30+
error (MSVC vcvars, AppleClang), an `abi3` / `python3.lib` link issue on Windows. The smoke
31+
step (`ci/smoke_test.py` / `smoke_min.py`) failing means the wheel imports or renders wrong.
32+
33+
- **bitexact** — output diverged from stock VTK 9.6.2 (`maxULP > 0`). This is a **parity
34+
break**. A change that was meant to be byte-identical is not. Either make it byte-identical
35+
again, move it behind `fvtk.EnableFast()` as a Bucket-2 opt-in, or — if it is an intentional
36+
backport divergence — update the affected op and record it in the divergence ledger. See the
37+
`parity-contract` skill. Never relax the comparison to make it pass.
38+
39+
- **renderexact** — offscreen RGBA+Z framebuffer diverged. A render-path change broke pixel
40+
exactness. Reproduce with `ci/run-renderexact.sh` under software EGL; treat like a bitexact
41+
break.
42+
43+
- **regression** — an fvtk regression scene failed. Read the specific test; it usually points at
44+
a concrete filter or render behavior.
45+
46+
- **pyvista** (PR #112, merge-queue / `pyvista-full` label) — PyVista's own suite failed against
47+
the built wheel. Split image-cache drift from a real fvtk regression exactly as the
48+
`/pyvista-bump` command describes. Real regression → fix in fvtk; drift → `deselect.txt` with a
49+
reason. Never deselect a real regression.
50+
51+
- **\*-lto legs** (main only) — the shipped LTO+ICF config failed to build or validate. The
52+
fast-O2 PR build passed but the LTO build did not. Usually an LTO/ICF-sensitive construct or a
53+
link-time issue; check the LTO build log specifically.
54+
55+
- **sdk / sdk-gate** — the `fvtk-sdk` build or its external-project consumer test failed (the SDK
56+
ships headers + CMake config + import libs). Check `ci/build-sdk.sh` / `ci/test-sdk.sh` and the
57+
per-platform link setup (Windows `python3.lib`, macOS).
58+
59+
- **publish** (release only) — runs only on a published GitHub Release; a failure here is
60+
trusted-publisher / environment config, not code. See `/fvtk-release`.
61+
62+
## Reproduce, then fix
63+
64+
Reproduce locally before changing anything: `PROFILE=fast ./build-fvtk.sh` plus the matching
65+
gate script (`ci/run-bitexact.sh`, `ci/run-renderexact.sh`, `ci/run-regression.sh`,
66+
`ci/run-pyvista.sh`). A gate that fails in CI but not locally is usually environment (runner
67+
arch, Mesa version, contention), not flakiness — `docs/ci-runners.md` covers the runner matrix.
68+
CI on `main` is always green; a red branch is the branch's fault.
69+
70+
Propose the fix with its root cause and the gate evidence. Apply it only on a branch, follow the
71+
commit and prose rules in `.claude/CLAUDE.md`, and re-run the failed gate to confirm before
72+
handing back.

0 commit comments

Comments
 (0)