Skip to content

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.

  1. Never modify Netflix golden-score assertions (§8).
  2. Never git push --force to master. Branch protection also rejects force-push and deletion on the host; see ADR-0037 and the release guide.
  3. Never commit directly to master — PR with squash or fast-forward only. The host also enforces required_linear_history: true and the required status checks.
  4. Never merge without make lint + make test green locally.
  5. Every commit message is Conventional Commits (type(scope): subject) — enforced by the commit-msg git hook.
  6. Every new .c / .h / .cpp / .cu file starts with the applicable license header (wholly-new fork files: Copyright 2026 Lusoris; files touching Netflix code: Netflix header preserved).
  7. 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 under core/include/, feature extractors, GPU backends / SIMD paths, meson_options.txt build 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.
  8. Every non-trivial architectural, policy, or scope decision ships as its own ADR file docs/adr/NNNN-kebab-case.md following docs/adr/0000-template.md before the commit that implements it lands. The ADR's index row lives in docs/adr/_index_fragments/<NNNN-slug>.md (one fragment file per ADR; the slug is appended to docs/adr/_index_fragments/_order.txt). docs/adr/README.md is regenerated by scripts/docs/concat-adr-index.sh --write and 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. Cite req (direct user quote) or Q<round>.<q> (popup answer) in the ADR's ## References section. Invariant (ADR-0532): always run scripts/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 .md when committing. See ADR-0386 and ADR-0535.
  9. 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.md invariant note in the relevant package (or "no rebase-sensitive invariants"; where the package has an AGENTS.d/, the note is a page there, see agents index); (d) reproducer / smoke-test command in the PR description; (e) CHANGELOG fragment file under changelog.d/<section>/<topic>.md — CHANGELOG.md itself is rendered by scripts/release/concat-changelog-fragments.sh per ADR-0221; (f) entry in docs/rebase-notes.md (or no rebase impact: REASON). Fork-local means anything not a verbatim port of upstream Netflix/vmaf code; pure upstream syncs and port-upstream-commit PRs are exempt. The PR template (.github/PULL_REQUEST_TEMPLATE.md) carries the checklist.
  10. 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.py vs scripts/ci/tidy-baseline-<lane>.json, required CI context Tidy 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; // NOLINT is 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 18 readability-function-size NOLINTs 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.
  11. Every PR that touches a libvmaf public surface (C-API entry points, public headers, CLI flags, meson_options.txt entries, or any symbol probed by the enabled libvmaf* check_pkg_config lines) updates the relevant numbered patch file in the same PR when integration behavior changes. The root build-config.env owns FFMPEG_TAG and FFMPEG_REMOTE. Run python3 scripts/ci/ffmpeg_patch_stack.py --refresh and then --check: these replay every entry in ffmpeg-patches/series.txt cumulatively in disposable storage. Per-patch git apply --check is 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.
  12. Default to the vmaf-dev-mcp container for vmaf / vmaf-tune / ai / MCP-probing work. The container at dev/Containerfile bakes 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-side meson setup build chases 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 master sync that touched anything under core/, mcp-server/, ai/, tools/vmaf-tune/, or dev/. 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/ and python/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.json with noise; stash it unless the run is formal. 14. Every session re-reads the ADR index at the start and writes the missing docs/adr/NNNN-*.md files 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 updates docs/state.md in 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.