diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index cbf4fb5cd348fd6aebb3ff648fa11a0d3bffbff7..5cb57c81758e3ea40477edd60a0d386474c5e910 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -29,6 +29,14 @@ cache:
   - make setup_headless
   - source .venv/bin/activate
 
+pre-commit:
+  tags: [shell, envinf]
+  rules:
+    - if: $CI_MERGE_REQUEST_IID
+  needs: []
+  script:
+    - pre-commit run --from-ref ${CI_MERGE_REQUEST_DIFF_BASE_SHA} --to-ref HEAD
+
 build:
   script:
     - pip install build
@@ -38,7 +46,6 @@ tests (arch):
   tags: [shell, envinf]
   needs: []
   script:
-    - pre-commit run --all-files
     - *setup-headless
     - make test
 
@@ -135,3 +142,26 @@ user container image:
     - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
     - docker build -t $DOCKER_USER_IMAGE .
     - docker push $DOCKER_USER_IMAGE
+
+code_quality_ruff:
+  image: python:3.10-slim
+  needs: []
+  script:
+    # Use same ruff as in .pre-commit-config.yaml
+    - pip install ruff==0.0.277
+    - ruff . --format gitlab > ruff-code-quality-report.json || true
+  artifacts:
+    reports:
+      codequality: ruff-code-quality-report.json
+
+code_quality_mypy:
+  image: python:3.10-slim
+  needs: []
+  script:
+    # Use same mypy as in .pre-commit-config.yaml
+    - pip install mypy==1.4.1 mypy-to-codeclimate
+    - mypy ogstools > mypy-output.txt || true
+    - mypy-to-codeclimate mypy-output.txt mypy-code-quality-report.json || true
+  artifacts:
+    reports:
+      codequality: mypy-code-quality-report.json
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index fc6045cd3486a7ac53c2b0e9a8832f9c3819b486..3e9c65864e82a39d9dd2d4f3bc54cdda56636601 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -47,6 +47,7 @@ repos:
     hooks:
       - id: mypy
         files: ogstools
+        exclude: ".*/examples/.*"
   - repo: https://github.com/codespell-project/codespell
     rev: "v2.2.5"
     hooks:
@@ -67,4 +68,4 @@ repos:
   - repo: https://github.com/executablebooks/mdformat
     rev: 0.7.16
     hooks:
-    - id: mdformat
+      - id: mdformat
diff --git a/pyproject.toml b/pyproject.toml
index fe3e8ca1277b8e295e996ad5fbd98592b4b77d1b..3ff6c2b35b5de17b25187130b44fa71cb0efe18e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -90,16 +90,19 @@ testpaths = ["tests"]
 line-length = 80
 
 [tool.mypy]
-files = "ogstools"
+files = "ogstools/**/*.py"
+# does not work when mypy invoked directly, works in pre-commit as explicitly
+# stated there again:
 exclude = ['.*/examples/.*']
+ignore_missing_imports = true
+scripts_are_modules = true
+# Set to true when current issues are fixed
 strict = false
-show_error_codes = true
-enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
+# Delete following lines when strict is true:
+check_untyped_defs = true
+disallow_untyped_defs = true
+disallow_incomplete_defs = true
 warn_unreachable = true
-# Uncomment and fix one by one:
-# check_untyped_defs = true
-# disallow_untyped_defs = true
-# disallow_incomplete_defs =  true
 
 [tool.ruff]
 select = [
@@ -144,7 +147,7 @@ unfixable = [
   "T20",  # Removes print statements
   "F841", # Removes unused variables
 ]
-exclude = []
+exclude = ["EXPERIMENTAL"]
 flake8-unused-arguments.ignore-variadic-names = true
 line-length = 80