Contributor setup, fresh-clone bootstrap and rebuild discipline

End-user installation lives at installation.md; the clone-and-bootstrap path there also applies to contributors. This page covers the contributor-specific bits: per-clone venvs, the editable-install rebuild loop, and the silently-stale-.so failure mode the binding-sanity test now catches.

Clone and bootstrap

After installing the system prerequisites listed in installation.md § Requirements for your platform (Homebrew on macOS; apt / dnf / pacman package groups on Linux), clone the source and bootstrap the native/Python environment:

Clone the public source over HTTPS; no account or deploy key is required:

git clone https://github.com/vibyverse/vibe-qc.git

Authorized maintainers use their separately configured upstream remote. Private access recipes belong in the external operations documentation.

Then enter the new checkout and install:

cd vibe-qc
./scripts/install.sh --dev --extras test --venv .venv \
    --python /opt/homebrew/bin/python3.14

The repository directory lists public GitHub source snapshots for all four projects; they can be cloned anonymously. Canonical maintainer development access is configured separately. Public snapshots have separate commit history, so keep contributions based on the selected host and project. See CONTRIBUTING.md for the GitLab merge-request path for GitHub contributions.

install.sh drives, in order: preflight (verify build prerequisites on $PATH) → vendored native deps (libint, libxc, spglib, FFTW3, libecpint, basis library, idempotent, each per-dep script short-circuits when its third_party/<dep>/install/ tree is present) → .venv creation → editable pip install -e .[<extras>] → banner. A clean native build can take hours: the default 5_4_4 Libint recipe alone is estimated at 1.5 to 2 hours in installer help. Incremental builds reuse current dependencies and compiler outputs; elapsed time depends on the machine, selected kernels and cache state.

The command above is the tested macOS development bootstrap. On another platform, select your installed Python with --python.

The release branch advances only from a tagged core release; --dev follows main, which is where contributions land. For a pinned install, choose a tag actually present in this repository; pre-split branches and historical tags were not transferred. Viewer and queue contributors clone their respective repositories separately. vibe-basis/ remains here.

If you already have the prereqs installed and only need a different branch / venv / extras combination, install.sh --help enumerates the flags.

Per-clone venvs

Reuse the existing checkout and its environment when they are appropriate for the task. If independent checkouts already exist, each needs its own installation; a linked worktree also needs correctly associated dependencies and build state. An editable pip install -e . records source paths, and the native extension uses the dependency trees selected at build time. Sharing a venv across checkouts can load Python from one tree and native code from another.

Run install.sh once per fresh clone; it lands the venv at <checkout-root>/.venv by default. To keep multiple branches in one tree with side-by-side venvs, use --venv .venv-<name>.

Choose the smallest useful check

Before changing source, inspect git status --short and fetch incoming work. Preserve other sessions’ changes, environments and running jobs. Source synchronization and runtime rebuilding are separate operations:

Change

Check or rebuild

Documentation prose

Check text, links and anchors; no native rebuild

Python behavior

Run the affected test with the intended interpreter

C++ implementation or bindings

Incrementally rebuild the core, then run the affected test

Native dependency recipe

Rebuild the affected dependency and any core code that consumes changed headers or ABI

No relevant source/runtime change

Reuse qualifying passing evidence

Check both Python and native module paths when a test depends on the compiled core. The troubleshooting checklist provides commands and explains what to record. An import check establishes loadability, not numerical correctness. Scientific acceptance uses a complete calculation and matched reference under the testing policy.

Rebuild after upstream binding changes

The editable install rebuilds the C++ extension only when you ask it to. So when upstream lands a commit that adds a new pybind11 binding, e.g. 39bf8629 adding compute_nuclear_with_charges to both cpp/src/bindings.cpp and the from ._vibeqc_core import (...) block in python/vibeqc/__init__.py, your locally-built _vibeqc_core.so is silently stale until you rebuild. The next import vibeqc then fails partway through the module body:

ImportError: cannot import name 'compute_nuclear_with_charges'
from 'vibeqc._vibeqc_core'

The recovery is one command:

.venv/bin/pip install -e . --no-build-isolation

--no-build-isolation keeps pip from spinning up a fresh build environment, which would re-download every build dep and discard the incremental CMake cache. Without it the rebuild takes minutes instead of seconds.

tests/test_binding_sanity.py catches this mechanically, it walks every from ._vibeqc_core import X site in the package and asserts each symbol resolves against the loaded .so. If it fails, the assertion message tells you exactly which rebuild to run and which files referenced the missing symbol. Run this test first when import vibeqc starts failing after a git pull:

.venv/bin/python -m pytest tests/test_binding_sanity.py

Rebuild Libxc after its recipe changes

The vendored Libxc recipe patches the PBE and r2SCAN correlation kernels so they evaluate 1 ± zeta without cancellation at full spin polarization (scripts/patch_libxc_spin_fractions.py). The editable install never re-runs the dependency scripts, so a clone whose third_party/libxc/install/ predates the patch keeps the old kernels after a git pull and a core rebuild. The test session then refuses with STALE LIBXC and exit status 97, the same refusal as a stale core. Rebuild Libxc with:

scripts/build_libxc.sh

It rebuilds an unpatched install and admits a patched one unchanged. The core loads Libxc at run time, so it needs no rebuild afterwards. A Libxc outside the checkout, such as a system installation, is not checked.

Libint capabilities required during configuration

The vendored Libint recipe applies header fixes for the Boys-table endpoint and for scratch allocation in the short-range Coulomb evaluator. The latter keeps the evaluator’s object layout and arithmetic, uses local scratch through order 31, and retains dynamic storage at higher orders. Run the normal dependency update and rebuild vibe-qc after a recipe change: updating a header does not change an already compiled native extension. System Libint installations are not patched by this recipe.

Libint builds with the same version can contain different generated kernels. The selected Libint2::cxx target must expose ordinary one-body integrals (LIBINT2_SUPPORT_ONEBODY=1) and their geometric derivatives through at least order 1 (LIBINT2_DERIV_ONEBODY_ORDER>=1). CMake compiles and links a small witness against that target’s headers and library before building the core. Missing capability macros or first-derivative overlap, kinetic or electrostatic symbols cause a configuration error with compiler/linker details and the selected Libint2_DIR. The witness is not executed.

If configuration rejects a system installation, run scripts/build_libint.sh and set Libint2_DIR to the CMake package directory in its third_party/libint/install/ tree when reconfiguring. A cached Libint2_DIR can keep selecting an older system dependency despite a newly created local install. The capability check runs on every configure, including after replacement at the same path. Setting VIBEQC_REQUIRE_VENDORED=OFF permits system discovery but does not bypass the capability requirement. The probe log is cpp/CMakeFiles/vibeqc-libint-onebody.log beneath the build directory.

The standard build recipe retains its existing order-2 and property-derivative settings. This minimum check for ordinary first derivatives does not certify Hessians, property derivatives, every angular momentum or numerical accuracy. Continue to verify the built core and the library actually loaded at runtime after a rebuild; configuration cannot protect against a later library swap.

Vendored headers come first, and a stale libecpint is refused

The compiler searches the vendored third_party/<dep>/install include directories before every other dependency’s. Imported packages otherwise contribute include directories in link order, and on macOS libint’s installed package links Boost, whose include directory is the whole Homebrew prefix. A Homebrew libecpint, libxc, spglib or FFTW there used to shadow the vendored header of the same name (#461): with Homebrew’s libecpint installed, the build stopped in cpp/src/ecp.cpp with “no member named ‘shell_pair_negligible’”. Nothing needs to be uninstalled.

A header can still resolve to another copy through a CPATH entry or an -I in CXXFLAGS or CMAKE_CXX_FLAGS, which the compiler searches before every system directory unless it repeats one. The build then fails with a message that names the header: cpp/src/xc.cpp and cpp/src/basis.cpp compare libxc’s and libint’s version macros with the packages CMake selected, and cpp/src/ecp.cpp checks for the vendored libecpint’s patched API, since that copy keeps upstream’s version number. Remove the directory from those variables and rebuild. spglib and FFTW have no version macro and rely on the search order alone.

scripts/build_libecpint.sh records the version of its libecpint patch in third_party/libecpint/install/.vibeqc-radial-quadrature. The editable rebuild above does not rerun that script, and a patch that changes only libecpint’s sources would otherwise be linked from an older install without notice (#460). Configuration therefore compares the record with the script’s RADIAL_PATCH_VERSION. A mismatch is an error under VIBEQC_REQUIRE_VENDORED and a warning otherwise. Run scripts/build_libecpint.sh, which rebuilds only libecpint in a few minutes, then the editable rebuild again.

Native sources from a local mirror, and clones that cannot hang

scripts/build_<dep>.sh fetches libint, libxc, spglib, OpenBLAS and libecpint’s sources with git clone, pinned to a commit SHA that the build verifies. Three environment variables control that step; all of them are read by scripts/_verify_source.sh and none changes what is built.

VIBEQC_GIT_REFERENCE_DIR names one or more directories (colon separated) that may already hold the pinned source. Point it at another checkout’s third_party/ directory, or at a directory of mirrors named after the upstream repositories (libint, libint.git, libint/src and libint-src are all recognised). A local repository is used only when it already contains the pinned commit, and the result still passes the same HEAD check as a network clone, so a mirror can never substitute a different commit. This turns a GitHub outage, or a transfer that keeps resetting, into a non-event on a machine that has built vibe-qc before:

VIBEQC_GIT_REFERENCE_DIR=/path/to/other-checkout/third_party ./scripts/update.sh

VIBEQC_GIT_CLONE_ATTEMPTS (default 3) bounds how often a network clone is retried, and VIBEQC_GIT_CLONE_TIMEOUT (default 1800, seconds per attempt) bounds how long one attempt may run. A clone past its deadline receives SIGTERM and, ten seconds later, SIGKILL for its whole process tree; a stalled HTTP transfer is cut earlier by git’s own low-speed limit (1 KiB/s over 60 s unless GIT_HTTP_LOW_SPEED_LIMIT and GIT_HTTP_LOW_SPEED_TIME are set). The failure message names the override to reach for. Ctrl-C on the build script still stops the clone.

When the editable install fails on pybind11

If pip install -e . errors out at the CMake configure step with

Could not find a package configuration file provided by "pybind11"

your venv is missing the pybind11 Python package (which ships the pybind11Config.cmake file CMake reads via pybind11::get_cmake_dir()). On a fresh python3 -m venv-created environment this can happen if pip install -e . skips the build-system isolation that would normally pull pybind11 from pyproject.toml’s build-system.requires. Fix:

.venv/bin/pip install pybind11
.venv/bin/pip install -e . --no-build-isolation

install.sh and update.sh handle this automatically: before the editable install they put the build requirements into the venv: scikit-build-core and pybind11 for the core, and meson-python, meson, cffi and setuptools for the dftd3 / dftd4 source distributions that the dispersion, test and dev extras can pull in. --no-build-isolation applies to those dependency builds as well, so a manual --no-build-isolation install with those extras needs the same packages (.venv/bin/pip install meson-python meson cffi setuptools). Manual paths (see installation.md § Manual bootstrap) should always run the editable install without --no-build-isolation the first time so pip pulls the build prereqs; only switch to --no-build-isolation for the incremental-rebuild loop above.

Pre-commit hook

CONTRIBUTING: pre-commit hook covers activation. The hook enforces the current personal-information policy: author home paths, private LAN IPs and the employer string are blocked at commit time. The prose policy also rejects em and en dashes outside its documented exemptions. Never bypass hooks with --no-verify; follow the branch and checkout policy.

For website and companion documentation work, follow the site publishing and CSS export contract.

See also

  • installation.md, full per-platform recipe, build prerequisites, BLAS-backend selection, verifying the install with the banner.

  • ../CONTRIBUTING.md, where to report what, MR / patch flow, pre-commit hook activation.

  • Current contributor policies, branch and checkout rules, privacy hygiene and documentation prose.

  • release_process.md, branch model in depth, release procedure, Patch-candidate trailer.

Queue cooperation during builds

The install and update scripts probe the local vq daemon before heavy compilation. When it is available, they pause only local running jobs with a vibeqc-build-<pid> tag and resume only that tag at exit. Existing operator pauses remain in place. Nested dependency builds share the outer build’s pause; the outer build owns its cleanup. An empty queue is a successful no-op.

Set VIBEQC_VQ_COOPERATE=0 to skip this coordination, or VQ_BIN to choose the queue executable. A missing local daemon is reported as a skip. Probe failures, command failures and incomplete/error summaries produce a warning; check the local jobs carrying the build tag if resume reports a problem.

FFTW, libxc, spglib and OpenBLAS cache identity

FFTW, libxc, spglib and OpenBLAS cache reuse check that the version entry point comes from the requested library, using its resolved path and SHA-256 digest. An unloadable or substituted library stops setup and ordinary updates before a stamp refresh or automatic rebuild. Inspect the cache before explicitly selecting a rebuild with scripts/update_native_deps.sh --dep fftw, --dep libxc, --dep spglib, --dep openblas, or --all. Selecting one dependency does not authorize replacing another invalid cache. The dry-run forms only preview that selection.

The reported exported version is distinct from the recipe source pin. Upstream FFTW 3.3.10’s CMake build exports 3.3.9; the string alone does not identify the source release or build options. Cache admission reports those as not attested. Existing qualified recipe stamps remain recipe records; legacy unknown source provenance is not upgraded into a source/build attestation.

For libxc, the loaded image owning xc_version is checked before the version function is called. Its reported version is also an observation, not source or build-option attestation. Other dependency gates retain their existing checks.

For spglib, the image owning spg_get_version is bound before calling it. Its exported version is kept separate from the source recipe pin; successful artifact admission does not establish source/build-option provenance.

OpenBLAS also requires lapacke.h and binds the BLAS, LAPACK and LAPACKE exports to the requested image before calling openblas_get_config. Header-only or unloadable installs cannot satisfy cache admission. The current consumer uses LP64 indices, so an exported USE64BITINT configuration is refused. Runtime version and configuration (including threading) are reported separately from the recipe pin; they do not attest the source release or build options. Optional OpenBLAS remains opt-in, and an explicit REBUILD=1 builder request or update_native_deps.sh --dep openblas can replace a refused cache.

Libint cache reuse requires the C shared library that Libint2::cxx links through its header-only C++ interface. The admission probe binds libint2_static_init, libint2_cleanup_eri and the storage address of libint2_build_eri to that requested image without calling initialization or integral kernels. A config/marker-only, static-only, ambiguous or unloadable cache stops before installed-header patches. The optional compiled C++ library is not a substitute for the required C image. Runtime version and generated max-AM are not attested; marker values remain recorded settings. Proven valid caches retain header patches and max-AM rebuild decisions. Fetch-only staging bypasses the installed runtime, and update_native_deps.sh --dep libint remains the explicit recovery route for a refused cache.

Optional native build receipts

Fresh FFTW, libxc and spglib CMake builds can call an external provenance collector. Set VIBEQC_NATIVE_BUILD_COLLECTOR to its absolute executable path and VIBEQC_NATIVE_RECEIPT_DIR to an existing absolute directory outside the product checkout and install tree. Each build uses <dependency>.json there; choose a fresh directory to preserve earlier receipts. No collector is bundled. With both variables unset, the existing build commands are unchanged. Cache hits and VIBEQC_FETCH_ONLY=1 do not call the collector or create historical receipts.

The collector is invoked once, after configuration and before build/install:

COLLECTOR record --source NAME=SOURCE --recipe BUILDER --recipe HELPER ...
  --cmake-build BUILD --install INSTALL
  --artifact-manifest BUILD/install_manifest.txt
  --cwd PRODUCT --output RECEIPT_DIRECTORY/NAME.json
  -- cmake --build BUILD --target install

Arguments are passed directly, without shell evaluation. Configuration enables CMAKE_EXPORT_COMPILE_COMMANDS=ON. The recipe arguments include the builder, collector adapter, build-pressure helper, source verification helper and build lock helper. The install manifest produced by the actual install identifies the artifacts, including platform-specific names and lib or lib64 locations. The external collector must observe the configured build, preserve source, compiler and option identities, and verify the resulting install. Its nonzero exit stops the builder without retry: the build may already have run.

These receipts do not themselves prove which image a process loaded or whether its data files work from a copied prefix. Those checks remain separate. This hook does not cover libint’s generated-library build, OpenBLAS Make, or the libecpint multistage build. Existing installs without a qualified build-time receipt retain unknown source and build provenance.

Generated basis overlay integrity

Setup requires Python >=3.11. A nonempty PYTHON_BIN explicitly selects the interpreter; otherwise setup tries a usable project .venv/bin/python, then $VIRTUAL_ENV/bin/python, then python3 on PATH. An incompatible explicit override fails without falling back. Interpreter refusal occurs before any output is changed. To select a supported interpreter, use PYTHON_BIN=/path/to/python bash scripts/setup_basis_library.sh.

After changing or copying a generated build/basis_library directory, run bash scripts/setup_basis_library.sh. Setup records the actual content of every committed orbital basis and ECP sidecar in a readable receipt. Imports check all recorded file identities and content digests without writing to the installation. If shared-filesystem clients assign different device numbers to the same files, the guard re-reads their content and verifies the recorded SHA256 values; all other identity fields must still match. This read-only fallback does not reseal the receipt. Its cost scales with the catalog size on each importing process; the ordinary same-client path uses the prepared receipt without rehashing files. Missing, corrupt, unreadable or changed receipts refuse overlay selection; ordinary imports do not regenerate them. Explicit LIBINT_DATA_PATH overrides retain their existing behavior and remain the caller’s responsibility.

Library callers receive BasisOverlayIntegrityError, a RuntimeError subclass. The vibeqc and vibe-qc commands, and their supported python -m entry points, use exit status 78 for this integrity refusal, before native integral evaluation. This is a filesystem freshness check for ordinary writes and replacements, not a security boundary against forged receipts or privileged metadata changes. Filesystems without usable POSIX change times refuse cached selection.

Resume an interrupted libint build

A matching interrupted build now reuses its Ninja objects. The saved contract binds tracked libint source content, the build recipe, compiler bytes and CMake configuration. A changed input refuses without deleting the retained build. Use the explicit native-dependency rebuild workflow when changing the recipe; do not remove locks or edit build markers to force reuse.

For an older build with no resume contract, the supported recovery command is:

bash scripts/recover_libint_build.sh /path/to/original/source EXACT_COMMIT_SHA
bash scripts/recover_libint_build.sh /path/to/original/source EXACT_COMMIT_SHA --apply

The first command verifies and previews; the second repeats those proofs under the original checkout’s build lock and resumes its existing CMake build. The recovery script may run from a newer checkout: it does not edit the old product source or change its commit identity. Legacy adoption is deliberately limited to the reviewed libint 2.13.1 recipe, default orbital/fitting limits, and the recorded GNU compiler version whose executable predates configuration. It checks the exact source patches and configuration, then records current content hashes; it does not claim that historical compiler bytes were cryptographically recorded. Unsupported or ambiguous configurations remain preserved for explicit review.

On success the original builder’s artifact checks and angular-momentum metadata finalization run, followed by a loaded-library identity check and the original partial-build provenance writer. Legacy recovery refuses other existing native installations rather than restamping their unproven history. Continue with the original product installer to build remaining dependencies, record its normal provenance, and verify the native core before activation. A failed recovery never claims a completed installation. Keep healthy runtime generations serving jobs throughout this process. A small, frequently evicted compiler cache is not a substitute for retaining compatible build objects.