Contributing to vibe-qc

Contributions and feedback both welcome. The decision tree below covers where to direct what.

Where to report what

What

Where

Security vulnerability — exploitable bug, memory corruption, anything you think shouldn’t be public-by-default

Email mpei@vibe-qc.com directly. PGP fingerprint CC6D 30BB DF96 F694 C615  FBDE 4CD5 65CF 26B1 E7E5 (key at https://vibe-qc.com/docs/_static/pgp/mpei.asc). See SECURITY.md for what to include and the disclosure timeline. Do not open a public issue.

Bug, install problem, missing feature, “this didn’t work the way the docs said”

GitLab issue tracker. Search for an existing issue first; comment on it if there is one. Filing requires a free account on gitlab.peintinger.com.

General feedback, question, “is this a bug or am I doing it wrong?”

Email mpei@vibe-qc.com. No GitLab account needed; replies go to your address directly.

Code-of-conduct violation

Email mpei@vibe-qc.com. See CODE_OF_CONDUCT.md.

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 GitLab 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. agentic-loop/asks/ contains historical files from before this rule and is closed to new ones; see the README there.

Maintainer-side automation (the bugctl broker used by the project’s own agents) reads and writes exactly these issue comments and labels, so a contributor commenting by hand and an agent using the tooling are participating in the same queue. Human comments are not fixer-lease actions and can be added after landing. An automated fixer instead posts the same brief after its push succeeds and before bugctl land, because land closes the lease that authorizes bugctl progress.

Before you open a merge request

  1. Confirm no existing issue already covers the change — if there is one, comment on it so we can coordinate.

  2. Run every test lane affected by the change and confirm it passes:

    .venv/bin/python scripts/test_gate/run_full_suite.py \
        --wt "$PWD" --py .venv/bin/python --lane <affected-lane>
    
  3. If your change touches documentation, build the Sphinx docs locally and skim the affected pages:

    sphinx-build -b html docs/ docs/_build/html
    
  4. For anything that adds a dependency or changes public API, open an issue first to check scope fit.

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 — refuses commits whose staged additions contain absolute paths into the author’s home directories or other personal-info patterns (CLAUDE.md § 12). It then runs .githooks/check_no_em_dashes.py, which refuses staged docs/*.md carrying em or en dashes in prose (code fences, inline code, and inline math are exempt).

  • commit-msg — refuses a release: vX.Y.Z commit unless pyproject.toml [project] version == X.Y.Z and 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-reporting 0.11.1. See docs/release_process.md § “Cutting a release”.

To bypass either hook for a reviewed exception, commit with git commit --no-verify and explain the reason in the commit message.

Worktrees need the setting applied per worktree

Parallel agent chats get one clone each, not a worktree — see CLAUDE.md § “One clone per chat”. 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

Code style

  • C++17. Four-space indent, brace on same line for control flow. Everything in cpp/ lives under namespace vibeqc { ... }. Match the patterns in cpp/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 (alongside Signed-off-by: / Co-Authored-By:) — values like v0.7.x, v0.8.x, v0.8.0, or comma-separated combinations. The release chat scans these when cutting patches. Do not tag vX.Y.Z, push to release, or open MRs against release directly — those are release-chat-owned operations and (since 2026-05-15) blocked by GitLab branch / tag protection. See CLAUDE.md § 13 and docs/release_process.md for 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:

  1. Your contribution is licensed under the Mozilla Public License 2.0 (the project license — see LICENSE).

  2. 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.