Agent hard rules¶
The binding rules for any agent or contributor working on this fork. They are referenced from the canonical AGENTS.md harness, which imports this page rather than inlining it so every vendor context file stays inside its line budget. Changing a rule here changes it for every agent.
- Never modify Netflix golden-score assertions (§8).
- Never
git push --forcetomaster. Branch protection also rejects force-push and deletion on the host; see ADR-0037 and the release guide. - Never commit directly to
master— PR with squash or fast-forward only. The host also enforcesrequired_linear_history: trueand the required status checks. - Never merge without
make lint+make testgreen locally. - Every commit message is Conventional Commits (
type(scope): subject) — enforced by thecommit-msggit hook. - Every new
.c/.h/.cpp/.cufile starts with the applicable license header (wholly-new fork files:Copyright 2026 Lusoris; files touching Netflix code: Netflix header preserved). - Every PR that adds or changes a user-discoverable surface ships human-readable documentation under
docs/in the same PR as the code. User-discoverable means: CLI flags or binaries, public C API undercore/include/, feature extractors, GPU backends / SIMD paths,meson_options.txtbuild flags, ffmpeg filter options, MCP tools, tiny-AI surfaces, and user-visible log / error / output-schema changes. Docs land in the existing topic tree (CLI →docs/usage/, C API →docs/api/, extractors →docs/metrics/, backends →docs/backends/, build/release →docs/development/, tiny-AI →docs/ai/, MCP →docs/mcp/, architecture →docs/architecture/). The minimum bar is per-surface (see ADR-0100 §Per-surface minimum bars). Tiny-AI keeps the tighter 5-point bar in ADR-0042 as the specialisation. Code comments and ADRs are not substitutes — they explain decisions to maintainers, not usage to humans. Internal refactors, bug fixes with no user-visible delta, and test-only changes are excluded. - Every non-trivial architectural, policy, or scope decision ships as its own ADR file
docs/adr/NNNN-kebab-case.mdfollowing docs/adr/0000-template.md before the commit that implements it lands. The ADR's index row lives indocs/adr/_index_fragments/<NNNN-slug>.md(one fragment file per ADR; the slug is appended todocs/adr/_index_fragments/_order.txt).docs/adr/README.mdis regenerated byscripts/docs/concat-adr-index.sh --writeand must not be edited by hand — see ADR-0221. Non-trivial = another engineer could reasonably have chosen differently. Bug fixes and implementation details do not need an ADR. Citereq(direct user quote) orQ<round>.<q>(popup answer) in the ADR's## Referencessection. Invariant (ADR-0532): always runscripts/adr/next-free.sh --claim <slug>to atomically reserve the next ADR number before creating the file. Do not hand-pick a number or use the read-only form without--claim. The command creates a stub file (docs/adr/NNNN-<slug>.md.stub) that all concurrent agents treat as taken; rename the stub to.mdwhen committing. See ADR-0386 and ADR-0535. - Every fork-local PR ships the six deep-dive deliverables in the same PR (per ADR-0108): (a) research digest under
docs/research/(or "no digest needed: trivial"); (b) decision matrix in the accompanying ADR's## Alternatives considered(or "no alternatives: only-one-way fix"); (c)AGENTS.mdinvariant note in the relevant package (or "no rebase-sensitive invariants"; where the package has anAGENTS.d/, the note is a page there, see agents index); (d) reproducer / smoke-test command in the PR description; (e) CHANGELOG fragment file underchangelog.d/<section>/<topic>.md—CHANGELOG.mditself is rendered byscripts/release/concat-changelog-fragments.shper ADR-0221; (f) entry indocs/rebase-notes.md(orno rebase impact: REASON). Fork-local means anything not a verbatim port of upstream Netflix/vmaf code; pure upstream syncs andport-upstream-commitPRs are exempt. The PR template (.github/PULL_REQUEST_TEMPLATE.md) carries the checklist. - Every PR leaves every file it touches lint-clean to the fork's strictest profile (clang-tidy + cppcheck +
make lint), whether the file is fork-local or upstream-mirror. Since ADR-1142 the standards apply to the whole tree — upstream-mirror, vendored, GPU kernels (CUDA/HIP/SYCL/Metal), SIMD, tests, tools — and the whole tree is bounded by the clang-tidy ratchet (scripts/ci/tidy-ratchet.pyvsscripts/ci/tidy-baseline-<lane>.json, required CI contextTidy Ratchet): no file may exceed its baseline count, a cleaned file must tighten the baseline in the same PR (make tidy-ratchet-write), and baselines are never hand-edited. "Touches" = any hunk in the PR's diff against its merge base. Refactor first;// NOLINTis reserved for cases where refactoring would break a load-bearing invariant (ADR-0138 / ADR-0139 bit-exactness pattern, upstream-parity identifier the rebase story depends on). Every NOLINT cites inline the ADR / research digest / rebase invariant that forces it; a NOLINT without a justification comment is itself a lint violation. The debt from before this rule (pre-2026-04-21, about 18readability-function-sizeNOLINTs plus upstream_iqa_*suppressions) was discharged by PR #327 (refactor pass) and PR #388 (citation closeout), so every NOLINT now in the tree carries an inline citation. See ADR-0141 and ADR-0278. - Every PR that touches a libvmaf public surface (C-API entry points, public headers, CLI flags,
meson_options.txtentries, or any symbol probed by theenabled libvmaf*check_pkg_configlines) updates the relevant numbered patch file in the same PR when integration behavior changes. The rootbuild-config.envownsFFMPEG_TAGandFFMPEG_REMOTE. Runpython3 scripts/ci/ffmpeg_patch_stack.py --refreshand then--check: these replay every entry inffmpeg-patches/series.txtcumulatively in disposable storage. Per-patchgit apply --checkis not a series gate. Local hooks refresh the configured release; daily CI discovers the latest stable released tag, excluding development and prerelease refs. See FFmpeg patch automation and ADR-1240. Pure internals, documentation and test changes need no semantic patch edit. -
Default to the
vmaf-dev-mcpcontainer for vmaf / vmaf-tune / ai / MCP-probing work. The container atdev/Containerfilebakes in every backend (CUDA + SYCL + HIP + Metal scaffolds), oneAPI, NVIDIA Container Toolkit runtime, ffmpeg with libvmaf, MCP server, and workspace mount. (The Vulkan backend was removed in ADR-0726.) Host-sidemeson setup buildchases moving toolchain targets (icpx missing, libsvm wheel drift, locale leaks); the container eliminates that whole class.- Before any non-trivial vmaf / vmaf-tune / ai / MCP run: rebuild the container if its image predates the last
mastersync that touched anything undercore/,mcp-server/,ai/,tools/vmaf-tune/, ordev/. One-liner:docker compose --project-directory $(git rev-parse --show-toplevel) -f dev/docker-compose.yml build dev-mcp && docker compose -f dev/docker-compose.yml up -d. - Then exec into it for the actual work:
docker exec vmaf-dev-mcp <command>. Workspace at/workspace/, vmaf binary at/usr/local/bin/vmaf(every backend live),.corpus/andpython/test/resource/mounted, MCP socket at/sockets/vmaf-mcp.sock. - Skip the container when: editing only Python harness files that don't touch the C surface, editing only docs / changelog / ADR, or running pure host-side git / gh operations.
- Don't reinvent host builds when a backend isn't reproducing in the container — diagnose the container first; fix the Containerfile rather than the host build-flag soup. Host-side builds remain available (
build/,core/build-cuda,core/build-all) but are no longer the default mental model. - The container is canonical for published artifacts (ADR-1102). Release binaries, published container images (
ghcr.io/vmafx/vmafx:*) and CI benchmark or snapshot artifacts used downstream must be produced inside the container. Host-side builds are diagnostic-only (IDE/clangd, debugger, sanitizer sweeps); never publish a host-built binary as a release artifact. See publishing for the rebuild triggers and the approved host-side uses. - Don't multiplex the same device across parallel jobs. When a long-running job (CHUG re-extract, BVI-DVC sweep) is pinned to one device (e.g. CUDA), schedule sibling parallel work on a different device — Intel Arc via SYCL, AMD via HIP, Metal on Apple Silicon, or CPU. Use
--backend $name(exclusive) or--no_<backend>(negative) to pin each parallel run to its own silicon. (The Vulkan backend was removed in ADR-0726.)
See docs/development/dev-mcp.md for the operator guide. 13. Never commit benchmark output files. An ad-hoc run rewrites
testdata/netflix_benchmark_results.jsonwith noise; stash it unless the run is formal. 14. Every session re-reads the ADR index at the start and writes the missingdocs/adr/NNNN-*.mdfiles and index rows for any decision inherited from context before the next commit. 15. Every PR that closes a bug, opens a bug, or rules a Netflix upstream report not-affecting-the-fork updatesdocs/state.mdin the same PR. The update lands a row in the right section (Open / Recently closed / Confirmed not-affected / Deferred) and cross-links the ADR, the PR and commit, and the Netflix issue if there is one. State drift compounds across sessions; the rule trades a 30-second edit for hours of re-investigation after a context reset. The pull request template carries a checkbox and reviewers verify it. See ADR-0165. - Before any non-trivial vmaf / vmaf-tune / ai / MCP run: rebuild the container if its image predates the last