Contributing to vibe-qc¶
Contributions and feedback both welcome. The decision tree below covers where to direct what.
This repository owns the engine, vibe-basis/, and the shared marketing site.
Report viewer work in vibe-view,
scheduling work in vibe-queue,
and format/specification work in QVF.
Each companion owns its code, documentation and releases; a core patch does
not update a companion checkout. Public-site ownership and shared CSS are
covered in site publishing.
Where to report what¶
What |
Where |
|---|---|
Security vulnerability — exploitable bug, memory corruption, anything you think shouldn’t be public-by-default |
Email |
Bug, install problem, missing feature, “this didn’t work the way the docs said” |
GitHub issues. Search for an existing issue first; comment on it if there is one. Filing requires a GitHub account. |
General feedback, question, “is this a bug or am I doing it wrong?” |
Email |
Code-of-conduct violation |
Email |
You want to send a patch |
Read the rest of this file. |
You fixed something, or you want to check someone else’s fix |
The issue it belongs to. Verification requests and verdicts are comments on the corresponding issue, never files in the repository — see How a fix gets verified. |
You want to fund the project |
GitHub Sponsors (recurring monthly) or Ko-fi (one-time). Full pitch + author bio + funding goals on the support page. |
All contributors and maintainers are expected to follow the Code of Conduct.
How a fix gets verified¶
Every fix to a wrong answer, a numerical result, or anything release-blocking is checked by someone other than its author before the issue closes. That is a project rule, not a formality: the author of a fix is the person least able to see what it missed.
The request and the verdict are comments on the issue. When your fix lands, comment on the issue it fixes with what a second person needs in order to attack it:
the claim, in one paragraph — what was wrong and why the fix is correct;
the commit it landed as;
the exact commands to run, copy-pasteable;
the numbers you measured before the fix, so a verifier can reproduce the broken behaviour on the parent commit and confirm the diagnosis;
any test failure that is present but unrelated, named, with why you believe it is unrelated;
what you would attack if you were checking someone else’s version of this — the assumption you are least sure of.
To check someone else’s fix, say so in a comment on the issue, do the work, and comment the verdict with the evidence you measured. Disagreeing with the fix is a useful outcome, not a failed one.
Do not put any of this in a file in the repository. Contributors work from different machines and not all of them can push, so a request written as a file reaches its reader but their answer cannot come back. A comment needs only an account. Maintainers retain private verification and coordination records outside product repositories. Until automatic issue routing is active, maintainers explicitly link a public report to its GitLab issue. Public comments do not imply that the private agent queue has already received the report.
Human contributors may add verification comments after landing. An automated contributor posts the verification brief after its push succeeds and before completing the landing step, because that step closes the editing claim used to attach the brief.
Tests for optional dependencies must run in every environment¶
Force the branch you want to test; do not wait for an environment that happens to lack the dependency.
# Good: exercises the missing-dependency path whether or not trexio is
# installed, so it runs in every venv and in CI.
monkeypatch.setitem(sys.modules, "trexio", None)
with pytest.raises(ImportError) as excinfo:
trexio_format._require_trexio()
The shape to avoid is a test gated to run only when a dependency is
absent, typically @pytest.mark.skipif(HAVE_SOMETHING, ...). A broken
assertion inside such a gate is invisible to everyone who has the package
installed, and it can stay broken indefinitely because the people who could
see the failure are the ones not running it. A test that runs only in the
environment you do not have is a test nobody reads the failures of.
That is not hypothetical: vibe-view carried two assertions inside a
skipif(HAVE_RDKIT) gate that passed in CI only because the checkout
directory happened to be named vibe-view, so a substring check against a
path-derived install hint matched by coincidence. They were asserting where
the repository sat on disk, not that the message helped a user. Found and
fixed 2026-09-10.
This suite currently has no such gate, and it is worth keeping that way. To check:
grep -rnE 'skipif\(\s*(HAVE_|HAS_|_HAS_|has_)[A-Za-z_0-9]*\s*[,)]' tests/
Read the hits rather than counting them, and check the direction before believing either result. Only a condition that is true when the dependency is present hides anything:
Condition |
Test runs when |
Verdict |
|---|---|---|
|
RDKit absent |
the trap |
|
RDKit present |
fine, the ordinary needs-the-extra gate |
|
git present |
fine, the safe inverse |
A non-empty result is therefore not by itself a finding.
The narrow pattern above already excludes both safe forms, so running just it
usually needs no triage at all. Widen it when you want confidence that no
variant slipped past, and triage what the wider net drags in. Quote which
pattern produced a count, because the two do not agree: sweeping vibe-view’s
suite, the documented pattern returned 1 and a widened one returned
7 — six of them the safe which form, and the odd one out, present in
both, merely prose inside a docstring rather than a gate at all.
An empty result is also worth nothing until the pattern is shown capable of matching. Plant the shapes in a throwaway file first, confirm the grep finds them, then trust the sweep:
printf 'import pytest\n@pytest.mark.skipif(HAVE_RDKIT)\ndef test_x(): ...\n' \
> /tmp/gate_control.py
grep -rnE 'skipif\(\s*(HAVE_|HAS_|_HAS_|has_)[A-Za-z_0-9]*\s*[,)]' /tmp/gate_control.py
Related but distinct: it is fine for an extra leg of a test to skip when a companion is absent, as the writer-to-viewer integration above does, provided the core assertions still run unconditionally.
Branch and checkout policy¶
Clone the public vibe-qc/vibe-qc repository to prepare contributions.
Submit patches as pull requests, with the evidence described below; maintainers
route accepted changes into canonical GitLab development. Automatic routing is
not yet active; see publication status.
Maintainers working in an authorized upstream clone land changes on main
with local checks for the affected area. Rebase before pushing:
git pull --rebase origin main
git push origin main
Never force-push main, release, or release tags, and never amend a commit
that has already been pushed. When a rebase conflicts in a shared historical design record
or release record, preserve both contributors’ facts and review the merged result.
Keep ordinary release notes in separate fragments as described below.
The agentic loop alone cuts tags and fast-forwards release from a tagged
commit on main; the vibe-qc release chat prepares and proves each release.
Website changes publish from main after their CI gate; core docs publish
from release. See release process
and site publishing.
Use one clone per agent task so its branch, index, environment and generated files are isolated. Do not reset or delete another task’s changes. If an existing worktree must be used, check its hook configuration as described below.
Release notes¶
For each user-visible change, add one Markdown file under changelog.d/,
named after the issue or a short descriptive slug. Start it with a ###
heading or a - bullet. Tests-only and internal changes may omit a fragment.
Do not edit CHANGELOG.md directly for ordinary contributions. Separate
fragments let concurrent changes land without conflicting at [Unreleased].
Validate the fragments before committing:
python scripts/assemble_changelog.py --check
The release owner assembles them into [Unreleased] with
scripts/assemble_changelog.py when preparing a release; the command and
cleanup workflow are in changelog.d/README.md. Released sections remain
pinned. Deliberate amendments follow the pre-push guard procedure below.
Personal information¶
Do not commit personal home-directory paths or unrelated employer
information. Use ~/, /home/USER/, or <vibe-qc-checkout> placeholders in
examples. The pre-commit hook checks full staged files for private emails,
internal host suffixes, private IPs, personal paths and external private terms. Its narrow
runner, root, and user allowlist exists for CI accounts and generic
fixtures; it is not permission to add personal data under another name.
The hook and CI use the shared .githooks/source_privacy.py implementation.
Documentation prose¶
Use commas, parentheses or ordinary hyphens instead of em or en dashes in
Markdown prose under docs/. The hook checks each touched file’s full staged
content, including older prose. Fenced code blocks, inline code and inline
math are exempt because they may contain literal output or mathematical
notation. Provenance-marked QVF snapshots under docs/qvf/_vendored/ are
also exempt: change those in the QVF source repository, then deliberately
re-vendor and update the pin.
These public policies are the source of truth for the hooks. Do not bypass
hooks unless the maintainer explicitly authorizes the particular exception;
record that authorization and the reason in the commit message. Privacy failures
must be fixed in source or resolved with an exact public-content review; never
bypass them with --no-verify.
Before you open a merge request¶
Confirm no existing issue already covers the change — if there is one, comment on it so we can coordinate.
Run a bounded reproduction or focused tests for the change. A whole area lane is optional broad validation, not a default pre-push requirement:
.venv/bin/python scripts/test_gate/run_full_suite.py \ --wt "$PWD" --py .venv/bin/python --lane <affected-lane>
If your change touches documentation, build the Sphinx docs locally and skim the affected pages:
sphinx-build -b html docs/ docs/_build/html
For anything that adds a dependency or changes public API, open an issue first to check scope fit.
Build against the vendored dependencies before trusting a lane¶
A missing third_party/<dep>/install/ makes the top-level CMake configure
fall back to system discovery for that one dependency, and it does so
silently. Set VIBEQC_REQUIRE_VENDORED=ON on any build whose test numbers
you intend to report, so that fallback becomes a configure-time error
instead:
VIBEQC_REQUIRE_VENDORED=ON .venv/bin/pip install -e . \
--no-build-isolation --config-settings=build-dir=build/<your-build-dir>
What this catches is not a wrong number. A system libint built without the
one-body derivative kernels (overlap1, kinetic1, elecpot1) links
fine and passes every lane that never differentiates, then calls a null
kernel pointer the moment one does: compute_gradient takes SIGSEGV
inside an OpenMP worker, which kills the pytest process and takes the rest
of that lane’s results with it. Homebrew’s libint 2.13.1 is such a
build; the vendored one is not.
This bites hardest in a worktree, which starts with no third_party/ at
all, so an agent harness that builds in one gets the fallback by default.
See installation for what
scripts/setup_native_deps.sh fetches and where it installs.
Recommended pre-merge verification¶
Small focused checks are the local merge-readiness gate. See
testing policy and inventory; substantial numerical acceptance
is separate from the initial implementation merge. Use
scripts/test_gate/run_full_suite.py --list-lanes and
docs/developer_test_lanes.md to select them. The recommended layering is:
# 1) Non-mean-field solver tests — fast, no native rebuild needed.
.venv/bin/python -m pytest \
tests/test_solvers_active_space_api.py \
tests/test_solvers_dmrg_fixed_n.py \
tests/test_solvers_v2rdm_constraints.py \
tests/test_solvers_v2rdm_tc.py \
tests/test_solvers_integration.py \
tests/test_solvers_fci_parity.py
# 2) QVF writer + writer-to-viewer integration. The viewer leg is
# skipped automatically if vibe-view isn't installed; the strict
# writer-side schema asserts always run.
.venv/bin/python -m pytest \
tests/test_qvf_writer.py \
tests/test_qvf_writer_to_viewer.py
# 3) Packaging / extras metadata coherence — pure metadata, no install.
.venv/bin/python -m pytest tests/test_packaging_extras.py
# 4) Every affected lane. Add full-fast only for shared infrastructure.
.venv/bin/python scripts/test_gate/run_full_suite.py \
--wt "$PWD" --py .venv/bin/python --lane <affected-lane>
List the exact lanes and SHA tested in the merge-request description.
Do not claim full-inventory success unless full-fast and slow-nightly
actually ran on that SHA.
Pre-commit hook (one-time setup)¶
After cloning, point git at the tracked .githooks/ directory so the
repo’s commit guards run on every commit:
git config --local core.hooksPath .githooks
This single setting activates every hook in .githooks/ (git resolves
one hooks directory for all hook types):
pre-commit— first refuses a commit whose author or committer email is not a public address (mpei@vibe-qc.com,project@vibe-qc.com, or one you add withgit config --local --add privacy.commitEmail you@example.org); set the clone’s identity withgit config --local user.email. It then refuses commits whose staged file content contains absolute paths into the author’s home directories or other personal-info patterns (personal information). It then runs.githooks/check_no_em_dashes.py, which refuses stageddocs/*.mdcarrying em or en dashes in prose (code fences, inline code, and inline math are exempt).commit-msg— refuses arelease: vX.Y.Zcommit unlesspyproject.toml[project] version==X.Y.Zand a## [vX.Y.Z]CHANGELOG header exists. This is the backstop against tagging a release from a tree whose version bump was skipped — the defect that shipped v0.11.2 / v0.11.3 self-reporting0.11.1. See maintainer publication.pre-push— refuses a push tomainorrelease-candidate/*whoseCHANGELOG.mdchanges an already released section without re-pinning it inscripts/test_gate/changelog_pins.toml(#229). It runs at push time because misfiled entries arrive through rebases, which never runpre-commit. Ordinary unreleased notes belong inchangelog.d/until release assembly; a deliberate amendment to a released section is re-pinned withpython scripts/test_gate/changelog_guard.py pin vX.Y.Z --amended "why". The committed guard, pins and changelog must all be readable regular files; repairing only the working tree does not repair the pushed commit (#264). Historical commits are exempt only when neither policy file occurs in their complete reachable history. Deleting the guard or pins does not restore that exemption. If shallow history prevents proving it, fetch complete history before retrying. Deletions and pushes to unrelated destination refs remain exempt. The hook uses the committed guard as trusted project code; it is not a substitute for reviewing changes to the guard itself.
Fix hook failures before committing. A maintainer-authorized exception follows the documentation and hook policy above.
Worktrees need the setting applied per worktree¶
Parallel agent tasks get one clone each, following the
branch and checkout policy. Worktrees still turn up in practice
(agent harnesses create them under .claude/worktrees/), and a worktree
does not necessarily inherit the setting above, so check before trusting
it.
--local writes the shared .git/config, but once a repo has
extensions.worktreeConfig = true — git enables it the first time
anything writes a worktree-scoped setting — a linked worktree can carry
its own core.hooksPath in .git/worktrees/<name>/config.worktree that
overrides the shared value. Check what a worktree actually resolves,
and repair it with --worktree:
git -C <worktree> config --show-scope --get-all core.hooksPath
git -C <worktree> config --worktree core.hooksPath .githooks
Keep the path relative. An absolute path bakes the checkout
location into each worktree’s config, so renaming or moving the clone
leaves core.hooksPath pointing at a directory that no longer exists.
Git skips a missing hooks directory silently — no warning, no error,
no failed commit — so every guard above goes inert without anyone
noticing. A relative .githooks resolves against each worktree’s own
top level and survives the move.
To confirm a hook really runs, without making a commit:
git -C <worktree> hook run pre-commit
AI-assisted contributions¶
AI-assisted patches follow the same contributor, licensing and verification policies established for this project, including the policy work tracked in #195.
Disclose AI assistance in the pull request or merge request, including which parts it helped produce. The contributor remains responsible for understanding and reviewing the patch, its tests, citations and dependency choices. Check referenced sources directly and remove generated claims that cannot be verified.
Include tests that exercise the reported failure and the corrected behavior. For numerical changes, provide the relevant before/after values, reference settings, tolerances and validation comparisons. Identify the exact commit and build that were tested, and state missing checks or unrelated failures.
GitHub contributions are admitted only through a GitLab merge request when public contribution intake is enabled. A maintainer must not merge an imported patch without green GitLab CI for its current head. A changed head requires new validation; skipped or stale pipelines do not qualify. The independent review rules above still apply to numerical and release-blocking changes. The Licensing section below governs these contributions without changes to its terms.
Code style¶
C++17. Four-space indent, brace on same line for control flow. Everything in
cpp/lives undernamespace vibeqc { ... }. Match the patterns incpp/src/rhf.cpp/cpp/src/integrals.cpp.Python 3.11+. PEP 8 with four-space indent. Start every module with
from __future__ import annotations. Type hints on public API; local helpers can skip them.Prefer editing existing modules over introducing new ones. If a new file is the right choice, follow the neighbors’ layout.
Commit messages¶
Imperative mood, first line under 72 characters (e.g.
Fix SCF divergence on CH3 with SAD guess).Longer rationale in the body if the what doesn’t explain itself.
Co-author trailers are fine for pair work.
To flag a commit for inclusion in a patch release, add a
Patch-candidate:trailer to the commit body (alongsideSigned-off-by:/Co-Authored-By:) — values likev0.7.x,v0.8.x,v0.8.0, or comma-separated combinations. The release chat scans these when preparing patches. Do not tagvX.Y.Z, push torelease, or open MRs againstreleasedirectly — the agentic loop performs those, and (since 2026-05-15) GitLab branch / tag protection blocks them. See branch and checkout policy anddocs/release_process.mdfor the full convention.
What we won’t accept (for now)¶
API changes that break public signatures without a deprecation path.
New hard dependencies added without prior discussion — open an issue first.
Changes that regress the test suite without a stated rationale and a plan to restore.
Licensing¶
By submitting a patch, pull request, or any other contribution to vibe-qc, you agree that:
Your contribution is licensed under the Mozilla Public License 2.0 (the project license — see
LICENSE).You grant the project owner (Michael F. Peintinger) the right to relicense your contribution under alternative terms, including a future commercial license, alongside the MPL 2.0 public license. You retain copyright.
This is a lightweight alternative to a formal Contributor License Agreement. If you’re not comfortable with (2), please open an issue before contributing so we can discuss.
vibe-qc’s compiled core links libint (MPL 2.0), libxc (MPL 2.0), Eigen (MPL 2.0 / BSD-3), spglib (BSD-3), and pybind11 (BSD-3) — all MPL-compatible.
Private privacy policy¶
The portable guard checks privacy patterns without storing real private names. Operators
can add private literal terms through VIBE_PRIVACY_TERMS_FILE or the clone-local
privacy.termsFile Git setting. Use an absolute path to a UTF-8 file outside the
source checkout, with one literal term per line. Matching is case-insensitive;
a configured missing, empty or in-tree file blocks the check. Keep the private
terms file and resolved installation paths out of commits. The repository’s
privacy tests load the same external policy when configured.
Site wrappers and deployment configuration are maintained separately in private operations storage. Product changes must use portable examples and preserve the existing generic installation and configuration APIs.
Fast source privacy preflight¶
Enable git config --local core.hooksPath .githooks once in each clone.
Run python3 .githooks/source_privacy.py --staged before committing and
python3 .githooks/source_privacy.py --tree HEAD before requesting a release
candidate build. The same current-tree check runs in the first CI stage on
ordinary main pushes and release candidates, without building the numerical core.
All tracked text is covered, including instructions and hooks. Keep
operator logs, machine details and private configuration outside product checkouts.
Use VIBE_PRIVACY_TERMS_FILE or local Git privacy.termsFile for an external
UTF-8 file with one private literal per line. A configured empty, missing or
in-tree file blocks the check. Canonical CI supplies this file from private CI
configuration; the public repository contains no private term list.
The two public maintainer addresses and reserved documentation domains are
allowed. Public upstream attribution or syntax that resembles a private value
requires independent, exact-context review in .githooks/public_content_review.json.
Do not approve private values or generate a blanket baseline. Changing the text
invalidates its exception. Binary assets and historical public objects remain
covered by the automatic publication gate; this fast check does not replace it.