Standards
Managed files
Every file odoo-nix's standards write, which module owns it, how each kind merges with what you wrote, the local regions, the retire rules, the checks sync reports, and each place odoo-nix changes OCA's rules.
On this page
odoo-nix sync --write writes the files below for the modules that are on, and sync --check reports any that differ. A module that turns off takes its files back out.
The files
| File | Module | Kind |
|---|---|---|
flake.nix | dev-shell | block inputs in the inputs set: odoo-nix.url, and standards-profile with an org profile |
.envrc | dev-shell | whole, local region envrc |
.gitignore | dev-shell | block dev-shell at the end: what the dev shell links or makes (/odoo.conf, /odools.toml, /.venv, /.devenv/, /.direnv/, /.odoo-nix/, /tools/.venv, /tools/.bin, /node_modules, /result, /result-*, __pycache__/, *.py[cod]) |
.editorconfig | editorconfig | whole, local region editorconfig; overridable |
.ruff.toml | python-lint | whole, local region ruff (it must open with a table header) |
.pylintrc, .pylintrc-mandatory | pylint-odoo | whole |
tools/pyproject.toml | every module with a tool | TOML merge: [project].dependencies (the locked tools of the modules that are on), [tool.uv] package = false, and [tool.coverage.run] and [tool.coverage.report] (tests) |
tools/uv.lock | every module with a tool | lock: uv lock --project tools |
package.json | js-xml | JSON merge: the devDependencies the hooks run |
package-lock.json | js-xml | lock: npm install --package-lock-only --ignore-scripts --no-audit --no-fund |
prettier.config.cjs, eslint.config.cjs | js-xml | whole |
.vscode/settings.json, .vscode/extensions.json | ide | JSONC merge (Editors) |
checklog-odoo.cfg | tests | seed: written once, then yours |
.pre-commit-config.yaml | every module with a hook | whole, local regions hooks and exclude |
.git-blame-ignore-revs | hygiene | seed |
committed.toml | commits | whole, local region committed |
.github/dependabot.yml | dependabot | whole, local region updates |
.github/zizmor.yml | workflow-lint | whole; overridable |
.github/workflows/ci.yml | ci | whole, when the lint or the test gate is on: a lint module, or tests with an installable addon that is not untested (CI) |
.github/workflows/pr-policy.yml | ci | whole, only with commits or releases on (the pr-policy gate) |
.github/workflows/deps.yml | ci | whole |
.github/workflows/release.yml | releases | whole, only where the checkout has a release branch |
README.md | readme with addons-table | block addons-table at the end; overridable |
"Overridable" means an org profile's templates/ may replace the template (Profiles).
The kinds
Whole: the file is odoo-nix's. It starts with a header comment. A file with a local region says
odoo-nix:managed — generated by odoo-nix (`odoo-init --sync`); edit only inside the odoo-nix:local region., and anything you add goes in that region, betweenodoo-nix:local-begin <name>andodoo-nix:local-end <name>, which sync carries over byte for byte. A file without one (.pylintrc,.pylintrc-mandatory,prettier.config.cjs,eslint.config.cjs,.github/zizmor.yml, the workflow callers) saysodoo-nix:managed — generated by odoo-nix (`odoo-init --sync`); do not edit.and takes no additions. Any edit outside a local region is drift.Block: odoo-nix owns the lines between
>>> odoo-nix: <name> (managed) >>>and<<< odoo-nix: <name> (managed) <<<(<!-- odoo-nix:begin <name> -->in Markdown) and nothing else in the file. A hand-written line the block now covers is absorbed into it on first opt-in when it is identical to the block's (a.gitignoreline, anodoo-nix.urlinflake.nixthat already names the same URL), and a comment paragraph left empty by that goes too. Aflake.nixinput defined otherwise is refused instead (see First opt-in).Merge (TOML, JSON, JSONC): odoo-nix owns the keys it writes and leaves every other one, its order and its comments alone. A pin you raised by hand in
tools/pyproject.tomlorpackage.jsonstays (a lower one is raised); a tool you added stays. JSONC files keep their indent, their inline arrays and a missing final newline.Seed: written when missing, never rewritten.
Lock:
tools/uv.lockandpackage-lock.jsonare checked against their manifests.--checkreports a stale lock (exit 1);--writereruns the lock command;--write --offlinenames it and runs nothing. Dependabot never moves them: the series data does (Versioning).
Every hook in .pre-commit-config.yaml is language: system and runs a locked tool, never one from PATH: tools/.venv/bin/<tool> (from tools/uv.lock), node_modules/.bin/<tool> (from package-lock.json) or tools/.bin/<tool> (actionlint, zizmor, shellcheck, nixfmt, statix, deadnix, committed: the Nix tools of the odoo-nix the checkout pins). The dev shell links all three into the checkout and runs prek install -c .pre-commit-config.yaml; -c keeps prek from running a nested config of an addon.
First opt-in
A checkout often has files of its own where odoo-nix writes. For these sync refuses to replace yours on first opt-in, exits 2 and prints the diff with where your lines go:
| File | Where your lines go |
|---|---|
flake.nix | an odoo-nix or standards-profile input defined otherwise than the managed inputs block; --force replaces it with the block's |
.pre-commit-config.yaml | the hooks local region; your exclude alternatives in the exclude region |
.envrc | the envrc local region |
.ruff.toml | python-lint's extra-select, ignore, per-file-ignores, or the ruff region |
.github/dependabot.yml | the updates local region |
Move them, then rerun with --force. A file odoo-nix's own scaffolding wrote (odoo-init's .envrc) is replaced without asking.
Retire rules
Files another tool's era left, deleted by sync while their module is on (retire-keep in the table keeps one):
| Files | Module | Why |
|---|---|---|
.flake8, .isort.cfg, ruff.toml; a setup.cfg holding only the old linters' sections | python-lint | ruff is configured in .ruff.toml |
.prettierrc*, .eslintrc*, eslint.config.{js,mjs,ts}, prettier.config.{js,mjs} | js-xml | prettier.config.cjs and eslint.config.cjs replace them |
.travis.yml, .t2d.yml; an unmanaged .github/workflows/*.yml (or .yaml) that runs OCA's checks or pre-commit/action (it contains oca_run_tests, ghcr.io/oca/oca-ci or pre-commit/action); list one you keep in retire-keep | ci | the gates replace OCA's CI and the pre-commit run |
oca_dependencies.txt, test-requirements.txt | dev-shell (addons mode with externalSources) | addons mode reads the external sources from flake.nix |
An org profile adds its own with [[retire]].
Checks
Beside the files, sync --check (and the end of --write) reports these as drift (exit 1):
readmewithrequired: an installable addon withoutREADME.mdorREADME.rst;ciin a project: a.gitmodulesentry that does not followbranch = <series>;packagingin addons mode: an addon without its whoolpyproject.toml, or anix/uv.lockthat predates one (odoo project relock);python-lint: a[tool.ruff]table in the config file, which ruff ignores once.ruff.tomlexists;the series sources disagreeing:
.odoo-series(orflake.nix), the manifests' versions,flake.lock'socbbranch, an integration branch named<n>.0that names another series.
Notices, which change no exit code: releases or packaging on in a project; ruff in the config file's [dependency-groups].dev (the hooks and the editor use the locked one); with ide on, python-envs.* in .vscode/settings.json (it overrides the interpreter); both shapes' signals without an explicit shape.
Refused before anything is written (exit 2): a series odoo-nix-tools has no data for; no tracked flake.nix; an addon repository without its addons-mode nix/pyproject.toml; the table in the other shape's file; addons.lockDir other than "nix"; releases with commits.convention = "odoo"; an [[tool.odoo-nix.untested]] entry naming no addon of the repository; releases under develop+series on a repository whose integration branch is itself a series branch (create develop and make it the default branch first).
Where odoo-nix changes OCA's rules
The lint and format rules are OCA's own, per series, from the series data each odoo-nix-tools release ships (odoo-nix data-path series/18.0.json). Every change odoo-nix makes to them is one of these deviations, applied in code and tested one by one:
| Id | Applies | Change |
|---|---|---|
odoo-nix-pins | always | manifestoo, checklog-odoo and manifestoo-core (no version in OCA's data), prek and coverage come from odoo-nix's own pins |
drop-gen-readme | always | no oca-gen-addon-readme: READMEs are Markdown, written by hand (OCA's own CI skips it too) |
drop-excluded-addons | always | no oca-update-pre-commit-excluded-addons: odoo-nix renders the NOT INSTALLABLE ADDONS region from discovery |
drop-external-deps | always (proposed, pending sign-off) | no oca-gen-external-dependencies: nothing in odoo-nix's uv model reads the requirements.txt it writes |
no-readme-fix | series 20.0 | oca-checks-odoo-module loses --fix (it can rename README.md to README.rst) |
stage-names | series 18.0 | commit/push/merge-commit become pre-commit/pre-push/pre-merge-commit |
forbid-contributing | hygiene.forbid-contributing = false | the obsolete-dotfiles hook stops refusing CONTRIBUTING.md |
addons-dir | projects | oca-fix-manifest-website gets --addons-dir=<addons root> |
github-per-hook | always | ^.github/ leaves the top-level exclude; each OCA hook excludes it itself, so actionlint, zizmor and check-yaml see .github/ |
prettier-vscode | always | prettier skips ^\.vscode/, so the editor's JSONC keeps its layout |
system-language | always | every hook is language: system and runs the locked executable; additional_dependencies, rev and default_language_version go |
locks-excluded | always | the top-level exclude gains uv.lock, flake.lock and package-lock.json |
esm-static | series 18.0 and later | eslint parses static/src/**/*.js and static/tests/**/*.js as ES modules |
pylint-dedupe | always | a message listed twice in a .pylintrc* enable list is written once |
required-author | addon-manifest.author = "" | no manifest-required-authors, and manifest-required-author is not enabled |
node-runtime | series 19.0 and 20.0 | node is the series preset's (nodejs_24), not OCA's default |
With addon-manifest.website = "" the oca-fix-manifest-website hook is not rendered at all.