Skip to main content

Odoo Doctor 🩺

Unified health scoring for Odoo custom addons.

Combines confidence-aware static analysis with optional external linters (Ruff, Pylint-Odoo) to produce a single 0–100 score per addon — designed for CI pipelines and AI coding agents.

Python 3.10+ License: MIT


Quick Start

# 1. Chạy ngay (không cần install)
pipx run odoo-doctor scan .

# 2. Install global
pip install odoo-doctor
odoo-doctor scan .

# 3. JSON output cho CI / agents
odoo-doctor scan . --json

# 4. Fail nếu score < 80
odoo-doctor scan . --min-score 80

# 5. Chỉ scan file đã thay đổi (PR review)
odoo-doctor scan . --diff main --json

# 6. Báo cáo HTML một file (mở bằng trình duyệt, đính kèm CI)
odoo-doctor scan . --html report.html

# 7. Upgrade readiness: how far is each module from Odoo 19?
odoo-doctor scan . --odoo-version 19.0 --upgrade-report

# 8. Reuse the last scan when nothing changed (`--cache`; only on repositories you trust:
#    a cache file committed to the repo can forge a hit)
odoo-doctor scan . --cache

Upgrade readiness. --upgrade-report (with --odoo-version <target>) groups the Upgrade Safety findings by kind of change, counts them, labels each one mechanical (fix --upgrade-to repairs it) or needs review, and scores every module 0-100. With --json it adds the top-level key upgrade_report. With --odoo-version X every module is evaluated as targeting X regardless of its manifest version; without it, rules follow each module's manifest version (a 17.0.x module shows an empty report, with a warning). odoo-doctor fix . --upgrade-to 19.0 --fix-dry-run previews the mechanical repairs as a diff (drop --fix-dry-run and pass --fix to apply). See docs/upgrade-report.md.


What it checks

Rule Tier Category
raw-sql-string-interpolation P0 Security
eval-usage P0 Security
missing-access-csv P0 Security
unknown-model-in-access-csv P1 Correctness
duplicate-xml-id P1 Correctness
view-field-not-in-model P1 Correctness
button-method-not-found P1 Correctness
orphan-view P2 Maintainability
missing-xml-ref P1 Correctness
manifest-missing-dependency P1 Module Hygiene
manifest-license-incompatible P2 Module Hygiene
missing-external-dependency P2 Module Hygiene
manifest-missing-required-fields P2 Module Hygiene
search-in-loop P1 Performance
create-in-loop P1 Performance
write-in-loop P1 Performance
n-plus-one-read P1 Performance
public-controller-sudo-risk P1 Security
sudo-without-comment P1 Security
record-rule-without-domain P1 Security
unbounded-search P2 Performance
manifest-data-order-risk P2 Module Hygiene
override-missing-super P1 Correctness
compute-missing-depends P2 Correctness
field-no-string-on-required P2 Maintainability
missing-translation P2 Maintainability
vendored-python-code P3 Maintainability
missing-ondelete P1 Data Integrity
data-noupdate-risk P2 Data Integrity
deprecated-api-usage P1 Upgrade Safety
removed-model-still-referenced P1 Upgrade Safety
asset-bundle-missing P2 Frontend
expensive-nonstored-compute P2 Performance
monetary-missing-currency-field P1 Correctness
missing-multicompany-rule P1 Security
unsafe-template-render P1 Security
access-write-without-group P1 Security
dynamic-method-dispatch P1 Security
rpc-sudo-without-access-check P1 Security
sql-constraints-unsupported P1 Upgrade Safety
removed-orm-api-usage P1 Upgrade Safety
cron-removed-fields P1 Upgrade Safety
view-attrs-states-removed P1 Upgrade Safety
tls-verification-disabled P1 Security
csrf-disabled-authenticated-route P1 Security
manual-cr-commit P1 Data Integrity
deprecated-orm-api-usage P2 Upgrade Safety
http-request-without-timeout P2 Performance
view-tree-removed P1 Upgrade Safety
removed-core-field-usage P1 Upgrade Safety
search-view-group-attributes P1 Upgrade Safety
act-window-target-inline P1 Upgrade Safety
chatter-div-removed P1 Upgrade Safety
kanban-legacy-templates P1 Upgrade Safety
hardcoded-company-or-currency P2 Correctness

Plus Ruff and Pylint-Odoo findings when those tools are installed. Both run isolated from the scanned repository: Ruff runs with --isolated (its default rule set) and ignores the repository's ruff.toml / [tool.ruff], so a hostile repo cannot hide findings with it. Pylint's configuration is ignored too, because Pylint runs init-hook code from it. Filter their findings with [ignore] rules in odoo-doctor.toml. In terminal and PR-comment text, a :: that came from the repo (a file or module name) is shown with an invisible zero-width space between the colons, so copy-pasting such a name keeps that character.

The full, generated reference (55 rules, with before/after examples) is in docs/rules.md; every finding links to its entry. Disable a rule with odoo-doctor rules disable <rule-name>; write your own with the stable plugin API. What you can rely on across upgrades (CLI flags, exit codes, JSON keys, rule IDs) is in the stability contract. Supported Python and Odoo versions and the release support policy are in docs/support.md; scan times on Odoo 19 community are in docs/performance.md.


Score explained

Each category score starts at 100 and loses points per high-confidence finding, where each finding deducts tier_impact × category_weight (defaults: Security/Correctness 1.5, Performance/Data Integrity/Upgrade Safety 1.0, Module Hygiene 0.8, Maintainability/Frontend 0.5; override via [category_weights]). The overall score blends only in-scope categories (those with at least one active rule):

category_score = max(0, 100 − Σ(tier_impact × category_weight))
overall        = 0.4 × min(in_scope_category_scores)
               + 0.6 × avg(in_scope_category_scores)

Tier impacts: P0 = 25, P1 = 10, P2 = 4, P3 = 1.

Label Range
Excellent 90–100
Good 75–89
Needs work 50–74
Critical 0–49

Each finding deducts points by tier: P0 = −25, P1 = −10, P2 = −4, P3 = −1.
Only high confidence findings count toward the score.

Fix first

Every scan ranks the score-eligible findings of each module by marginal score gain per effort, so you (or an agent) fix the right thing first. The terminal prints the top 5 under Fix first; --json has the top 10 per module as modules.<name>.fix_priorities:

{"rank": 1, "rule": "duplicate-xml-id", "file_path": "...", "line": 79,
 "impact": 10.0, "effort": 1, "roi": 10.0, "projected_score": 52.1,
 "score_gain": 4.7, "fixable": false}
  • effort is a coarse 1-3 estimate per rule (1 mechanical, 2 local code change, 3 restructuring); auto-fixable rules count as 1.
  • projected_score is the module score after fixing this finding and every one ranked before it; score_gain is the change this step made. A category that is already at 0 shows +0.0 until enough of its findings are fixed — the weakest category carries the 0.4 × min term, so it is ranked first.

Configuration

odoo-doctor init   # creates odoo-doctor.toml
[odoo-doctor]
odoo_version = "17.0"
addons_paths = ["."]
odoo_source_path = "/path/to/odoo/source"
capabilities = ["enterprise", "owl"]
min_score = 75

[adapters]
ruff = true
pylint_odoo = false

[severity]
"search-in-loop" = "warning"

[ignore]
rules = []
files = ["**/migrations/**"]
modules = []

[category_weights]
Security = 1.5

[surfaces.pr_comment]
min_confidence = "all"
categories = []

[surfaces.ci_failure]
min_confidence = "high"
categories = []

[surfaces.terminal]
min_confidence = "medium"   # "low" shows everything

CI Integration

GitHub Actions

The easiest way to integrate Odoo Doctor into GitHub Actions is using our official composite action. See .github/workflows/odoo-doctor.example.yml for a full example.

- name: Odoo Doctor Scan
  uses: minhhq-a1/odoo-doctor@v0.12.0
  with:
    fail-on: warning
    min-score: 75
    diff-base: main
    pr-comment: true
    paths: "."

If you prefer pip install, you can run it directly:

- name: Odoo Doctor (pip)
  run: |
    pip install odoo-doctor
    odoo-doctor scan . --format github --min-score 75 --fail-on error

GitLab CI

ci-templates/gitlab-ci.yml defines an odoo-doctor job. Include it (pin a release tag instead of main once you depend on it) and override what you need:

include:
  - remote: "https://raw.githubusercontent.com/minhhq-a1/odoo-doctor/main/ci-templates/gitlab-ci.yml"

variables:
  ODOO_DOCTOR_ODOO_VERSION: "17.0"
  ODOO_DOCTOR_FAIL_ON: "warning"
  ODOO_DOCTOR_MIN_SCORE: "75"

Merge request pipelines scan only the files the MR changed (against the merge base, so a target branch that moved on does not leak into the scan); default-branch pipelines scan everything. The job keeps odoo-doctor-report.json as an artifact. Other variables: ODOO_DOCTOR_VERSION (pin a release), ODOO_DOCTOR_PATHS, ODOO_DOCTOR_ADVISORY=true (report only, never fail).

Bitbucket Pipelines

Copy ci-templates/bitbucket-pipelines.yml into your bitbucket-pipelines.yml (Bitbucket has no remote include). It runs on every pull request, scans only the changed files and keeps odoo-doctor-report.json as an artifact. The same ODOO_DOCTOR_* names as above are read from repository variables; the file's header lists them and shows how to also scan your main branch.

SARIF & Baseline Mode

For GitHub Code Scanning and IDE integration:

odoo-doctor scan . --format sarif > results.sarif
# Then upload via github/codeql-action/upload-sarif

To capture current debt and block only new findings in CI:

odoo-doctor scan . --write-baseline .odoo-doctor-baseline.json
# Commit the baseline, then in CI:
odoo-doctor scan . --baseline .odoo-doctor-baseline.json --fail-on warning

New baselines use format v3 and identify findings by paths relative to the scan root, so the same baseline works in another checkout or CI runner. Use the same scan root layout when writing and applying it. Existing v1/v2 baselines are still accepted at their original checkout path; regenerate them with --write-baseline for portability.

CI/PR Surfaces

  • --show-low: The terminal hides low-confidence findings by default ([surfaces.terminal]) and prints how many it hid; --show-low lists them all. Only the terminal output changes; JSON, SARIF, HTML, --fail-on and scores do not.
  • --format github: Emits GitHub Actions annotations inline.
  • --score-delta <base-ref>: Opt-in PR score delta. It does a worktree-isolated second scan and needs git history (fetch-depth: 0 in Actions).
  • Sticky PR comment: Posted/updated via gh when --format github runs in a PR with a valid GH_TOKEN. Idempotent via a hidden marker. Only --format github posts it; --no-pr-comment (or pr-comment: false on the Action) turns it off.

CI failure policy

--fail-on <severity> only counts findings admitted by [surfaces.ci_failure], which defaults to P0/P1 at high confidence: style/advisory (P2/P3) and low-confidence findings are reported but never fail a build. Adjust it:

[surfaces.ci_failure]
tiers = ["P0", "P1", "P2"]   # [] = every tier
min_confidence = "high"

Score history & badge

Track the score over time and publish a badge without any server (see docs/score-history.md):

odoo-doctor scan . --history .odoo-doctor/history.jsonl --badge badge.svg
odoo-doctor history show .odoo-doctor/history.jsonl --max-drop 3   # exit 2 on regression
odoo-doctor history import history.jsonl old-report.json           # pre-0.4.0 reports

HTML report

Write one self-contained HTML file (no server, no network request) to open in a browser or keep as a CI artifact (see docs/html-report.md):

odoo-doctor scan . --html report.html

pre-commit

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: odoo-doctor
        name: Odoo Doctor
        language: system
        entry: odoo-doctor scan --diff HEAD --fail-on error
        pass_filenames: false
        types: [python]

Agent Usage

Odoo Doctor is designed for AI coding agents. Install the SKILL.md files:

odoo-doctor install   # installs to .odoo-doctor/skills/

Then in your agent workflow:

# After editing Odoo code
odoo-doctor scan . --diff main --json

# Fix P0/P1 findings with confidence: "high"
# Re-scan to verify fixes
odoo-doctor scan . --diff main --json

Use odoo-doctor rules explain <rule-name> to understand any finding (description, why, fix, examples and a docs link).

Use odoo-doctor rules stats to see which rules your team suppresses most (inline # odoo-doctor: disable, [ignore] rules, [severity] = "off"). Rules where most findings are suppressed are flagged as noisy, with a suggestion to lower their severity.

In your editor (experimental). uv tool install 'odoo-doctor[lsp]' (or pipx install; Python 3.10+) adds odoo-doctor lsp, a language server that shows the same findings as diagnostics and offers quick fixes (apply the auto-fix, or disable the rule on a line, in a file or in odoo-doctor.toml). The VS Code extension is on the Marketplace (MinhHong.odoo-doctor, preview; source in editors/vscode); setup for VS Code, Neovim and Helix is in docs/lsp.md.


Generating stubs for your Odoo version

Bundled stubs cover 17.0, 18.0, 19.0 (core models only).
For full accuracy, generate from source or a live instance:

# From Odoo source checkout
python -m odoo_doctor.graph.stubs.build_stubs source \
  --odoo-path /path/to/odoo \
  --version 17.0

# From a live Odoo instance (no source needed)
python -m odoo_doctor.graph.stubs.build_stubs rpc \
  --rpc-url http://localhost:8069 \
  --rpc-db mydb \
  --rpc-password admin \
  --version 17.0

The generated JSON is written to src/odoo_doctor/graph/stubs/data/<version>.json
(or --output <path> for a custom location).


Inline suppression

x = self.env.cr.execute(f"SELECT ...")  # odoo-doctor: disable=raw-sql-string-interpolation
<record id="my_record" model="ir.ui.view">  <!-- odoo-doctor: disable=duplicate-xml-id -->

Exit codes

Code Meaning
0 Clean — no triggered thresholds
1 Findings at or above --fail-on severity
2 One or more modules score below --min-score
3 Invalid argument, out-of-range --min-score, or git/ref failure

odoo-doctor history show --max-drop N also exits 2 when the score regressed.


Development

git clone https://github.com/minhhq-a1/odoo-doctor
cd odoo-doctor
pip install -e ".[dev]"
pytest                    # the full suite
pytest --cov=odoo_doctor  # with coverage

Metadata

Release files for odoo-doctor 0.12.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for odoo-doctor 0.12.0
File Size Uploaded Provenance
odoo_doctor-0.12.0.tar.gz 281.4 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for odoo-doctor 0.12.0
File Interpreter ABI Platform
odoo_doctor-0.12.0-py3-none-any.whl Python 3 none any Details

Total release size: 602.6 kB

Release files / odoo_doctor-0.12.0.tar.gz

Download URL odoo_doctor-0.12.0.tar.gz
Size 281.4 kB
Tags Source
SHA-256 checksum
How to use checksums
9b255238d9966d1e3d7aa0ccebd4092cc9bdc099fc24912a221b5a8081668f66
BLAKE2b-256 checksum
How to use checksums
4bd3843b155b6c397e8bae6562aa1fbf044e7525fa63c0d7404dd73ac663b19c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Release files / odoo_doctor-0.12.0-py3-none-any.whl

Download URL odoo_doctor-0.12.0-py3-none-any.whl
Size 321.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3a2952e4d028bba5fe5b6ea9ee6f1ef784e46fc0a72c73d79810883bd886ed87
BLAKE2b-256 checksum
How to use checksums
f6585c76452304e1fd706453322ef4316166dd77cea855fb4294abfabe437466
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Security for odoo-doctor 0.12.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for odoo_doctor-0.12.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page