diff --git a/.clang-tidy b/.clang-tidy
index 6861882e3..59ad5a5e7 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -1,13 +1,12 @@
 Checks: "
         modernize-use-*, -modernize-use-trailing-return-type*,
         performance-*,
         mpi-*,
         openmp-*,
         bugprone-*,
         readability-*, -readability-magic-numbers, -readability-redundant-access-specifiers,
         -clang-analyzer-*
         "
 AnalyzeTemporaryDtors: false
 HeaderFilterRegex:     'src/.*'
 FormatStyle:           file
-UseColor:              true
diff --git a/.codeclimate.yml b/.codeclimate.yml
index 7878866d0..1247c1b95 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -1,38 +1,42 @@
 checks:
   duplicate:
     enabled: true
     exclude_patterns:
       - "test/"
       - "examples/"
   structure:
     enabled: true
     exclude_patterns:
       - "test/"
 
 plugins:
   editorconfig:
     enabled: false
     config:
       editorconfig: .editorconfig
     exclude_patterns:
       - ".clangd/"
       - ".cache/"
   pep8:
     enabled: true
     exclude_patterns:
       - "test/test_fe_engine/py_engine/py_engine.py"
   cppcheck:
     enabled: false
     project: compile_commands.json
     language: c++
     check: warning, style, performance
     stds: [c++14]
-
   fixme:
     enabled: true
     exclude_patterns:
       - "doc/"
+  clang-tidy:
+    enabled: true
+    exclude_patterns:
+      - "test/"
+      - "examples/"
 
 exclude_patterns:
 - "third-party/"
 - "build*/"
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 92c14fc08..780deecb5 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,326 +1,347 @@
 stages:
   - configure
   - build
   - check-warnings
   - test
   - deploy
 
 .docker_build:
   image: 'docker:19.03.11'
   stage: .pre
   services:
     - docker:19.03.11-dind
   variables:
     # Use TLS https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#tls-enabled
     DOCKER_HOST: tcp://docker:2376
     DOCKER_TLS_CERTDIR: "/certs"
   before_script:
     - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
   script:
     - cd test/ci/${IMAGE_NAME}/
     - docker build -t registry.gitlab.com/akantu/akantu/${IMAGE_NAME} .
     - docker push registry.gitlab.com/akantu/akantu/${IMAGE_NAME}
 
 docker build:debian-testing:
   variables:
     IMAGE_NAME: debian:testing
   extends: .docker_build
   rules:
     - changes:
         - test/ci/debian:testing/Dockerfile
 
 docker build:ubuntu-lts:
   variables:
     IMAGE_NAME: ubuntu:lts
   extends: .docker_build
   rules:
     - changes:
         - test/ci/ubuntu:lts/Dockerfile
 
 .configure:
   stage: configure
   except:
     - tags
   variables:
     BLA_VENDOR: 'Generic'
   script:
     - cmake -E make_directory build
     - cd build
     - cmake -DAKANTU_COHESIVE_ELEMENT:BOOL=TRUE
             -DAKANTU_IMPLICIT:BOOL=TRUE
             -DAKANTU_PARALLEL:BOOL=TRUE
             -DAKANTU_STRUCTURAL_MECHANICS:BOOL=TRUE
             -DAKANTU_HEAT_TRANSFER:BOOL=TRUE
             -DAKANTU_DAMAGE_NON_LOCAL:BOOL=TRUE
             -DAKANTU_PYTHON_INTERFACE:BOOL=TRUE
             -DAKANTU_EXAMPLES:BOOL=TRUE
             -DAKANTU_BUILD_ALL_EXAMPLES:BOOL=TRUE
             -DAKANTU_TEST_EXAMPLES:BOOL=FALSE
             -DAKANTU_TESTS:BOOL=TRUE
             -DAKANTU_RUN_IN_DOCKER:BOOL=TRUE
             -DCMAKE_BUILD_TYPE:STRING=Coverage ..
      - cp compile_commmands.json ..
   artifacts:
     when: on_success
     paths:
       - build
       - compile_commands.json
     expire_in: 10h
 
 .build:
   stage: build
   script:
     - cmake --build build/src > >(tee -a ${output}-out.log) 2> >(tee -a ${output}-err.log >&2)
     - cmake --build build/python > >(tee -a ${output}-out.log) 2> >(tee -a ${output}-err.log >&2)
     - cmake --build build/test/ > >(tee -a ${output}-out.log) 2> >(tee -a ${output}-err.log >&2)
     - cmake --build build/examples > >(tee -a ${output}-out.log) 2> >(tee -a ${output}-err.log >&2)
   artifacts:
     when: on_success
     paths:
       - build/
       #- ${output}-out.log
       - ${output}-err.log
       - compile_commands.json
     expire_in: 10h
 
 .tests:
   stage: test
   script:
     - cd build
     - ctest -T test --no-compress-output --timeout 1800
   after_script:
     - cd build
     - tag=$(head -n 1 < Testing/TAG)
     - if [ -e Testing/${tag}/Test.xml ]; then
     -   xsltproc -o ./juint.xml ${CI_PROJECT_DIR}/test/ci/ctest2junit.xsl Testing/${tag}/Test.xml;
     - fi
     - gcovr --xml
             --gcov-executable "${GCOV_EXECUTABLE}"
             --output coverage.xml
             --object-directory ${CI_PROJECT_DIR}/build
             --root ${CI_PROJECT_DIR}  -s || true
   artifacts:
     when: always
     paths:
       - build/juint.xml
       - build/coverage.xml
     reports:
       junit:
         - build/juint.xml
       cobertura:
         - build/coverage.xml
 
 .analyse_build:
   stage: check-warnings
   script:
     - if [[ $(cat ${output}-err.log | grep warning -i) ]]; then
     -    cat ${output}-err.log;
     -    exit 1;
     - fi
   allow_failure: true
   artifacts:
     when: on_failure
     paths:
       - "$output-err.log"
 
 # ------------------------------------------------------------------------------
 .cache_build:
   variables:
     CCACHE_BASEDIRE: ${CI_PROJECT_DIR}/build
     CCACHE_DIR: ${CI_PROJECT_DIR}/.ccache
     CCACHE_NOHASHDIR: 1
     CCACHE_COMPILERCHECK: content
   cache:
     key: ${output}
     policy: pull-push
     paths:
       - .ccache/
       - third-party/google-test
       - third-party/pybind11
   before_script:
     - ccache --zero-stats || true
   after_script:
     - ccache --show-stats || true
 
 # ------------------------------------------------------------------------------
 .image_debian_testing:
   image: registry.gitlab.com/akantu/akantu/debian:testing
 
 .image_ubuntu_lts:
   image: registry.gitlab.com/akantu/akantu/ubuntu:lts
 
 # ------------------------------------------------------------------------------
 .compiler_gcc:
   variables:
     CC: /usr/lib/ccache/gcc
     CXX: /usr/lib/ccache/g++
     FC: gfortran
     GCOV_EXECUTABLE: gcov
 
 .compiler_clang:
   variables:
     CC: /usr/lib/ccache/clang
     CXX: /usr/lib/ccache/clang++
     FC: gfortran
     GCOV_EXECUTABLE: llvm-cov gcov
 
 # ------------------------------------------------------------------------------
 .debian_testing_gcc:
   variables:
     output: debian_testing_gcc
   extends:
     - .compiler_gcc
     - .image_debian_testing
     - .cache_build
 
 .debian_testing_clang:
   variables:
     output: debian_testing_clang
   extends:
     - .compiler_clang
     - .image_debian_testing
     - .cache_build
 
 .ubuntu_lts_gcc:
   variables:
     output: ubuntu_lts_gcc
   extends:
     - .compiler_gcc
     - .image_ubuntu_lts
     - .cache_build
 
 # ------------------------------------------------------------------------------
 # ------------------------------------------------------------------------------
 configure:debian_testing_gcc:
   extends:
     - .debian_testing_gcc
     - .configure
   cache:
     policy: pull-push
 
 build:debian_testing_gcc:
   extends:
     - .debian_testing_gcc
     - .build
   dependencies:
     - configure:debian_testing_gcc
 
 test:debian_testing_gcc:
   extends:
     - .debian_testing_gcc
     - .tests
   dependencies:
     - build:debian_testing_gcc
 
 analyse_build:debian_testing_gcc:
   extends:
     - .debian_testing_gcc
     - .analyse_build
   dependencies:
     - build:debian_testing_gcc
 
 # ------------------------------------------------------------------------------
 configure:debian_testing_clang:
   extends:
     - .debian_testing_clang
     - .configure
   cache:
     policy: pull-push
 
 build:debian_testing_clang:
   extends:
     - .debian_testing_clang
     - .build
   dependencies:
     - configure:debian_testing_clang
 
 test:debian_testing_clang:
   extends:
     - .debian_testing_clang
     - .tests
   dependencies:
     - build:debian_testing_clang
 
 analyse_build:debian_testing_clang:
   extends:
     - .debian_testing_clang
     - .analyse_build
   dependencies:
     - build:debian_testing_clang
 
 # ------------------------------------------------------------------------------
 configure:ubuntu_lts_gcc:
   extends:
     - .ubuntu_lts_gcc
     - .configure
   cache:
     policy: pull-push
 
 build:ubuntu_lts_gcc:
   extends:
     - .ubuntu_lts_gcc
     - .build
   dependencies:
     - configure:ubuntu_lts_gcc
 
 analyse_build:ubuntu_lts_gcc:
   extends:
     - .ubuntu_lts_gcc
     - .analyse_build
   dependencies:
     - build:ubuntu_lts_gcc
 
 test:ubuntu_lts_gcc:
   extends:
     - .ubuntu_lts_gcc
     - .tests
   dependencies:
     - build:ubuntu_lts_gcc
 
 # ------------------------------------------------------------------------------
-# include:
-#   - template: Code-Quality.gitlab-ci.yml
-
-# code_quality:
-#   dependencies:
-#     - build:debian_testing_clang
-#   artifacts:
-#     paths: [gl-code-quality-report.json]
-
-code_quality_clang_tidy:
-  extends:
-    - .debian_testing_clang
-  dependencies:
-    - build:debian_testing_clang
+code_quality:
   stage: test
+  image: docker:19.03.12
+  allow_failure: true
+  services:
+    - docker:19.03.12-dind
+  variables:
+    DOCKER_DRIVER: overlay2
+    DOCKER_HOST: tcp://docker:2376
+    DOCKER_TLS_CERTDIR: "/certs"
+    CODECLIMATE_DEV: ""
+    CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.22"
+  needs: []
   script:
-    - cmake --build build --target clang-tidy-all > >(tee -a clang-tidy-all-out.log) 2> >(tee -a clang-tidy-all-err.log >&2)
-    - python3 ./test/ci/scripts/clang-tidy2code-quality.py | tee code-quality-clang.json | jq
-  artifacts:
-    paths:
-      - clang-tidy-all-err.log
-      - clang-tidy-all-out.log
-      - code-quality-clang.json
-    expire_in: 10h
+    - export SOURCE_CODE=$PWD
+    - | # this is required to avoid undesirable reset of Docker image ENV variables being set on build stage
+      function propagate_env_vars() {
+        CURRENT_ENV=$(printenv)
+
+        for VAR_NAME; do
+          echo $CURRENT_ENV | grep "${VAR_NAME}=" > /dev/null && echo "--env $VAR_NAME "
+        done
+      }
+    - docker pull --quiet "$CODE_QUALITY_IMAGE"
+    - |
+    - docker build  -t codeclimate/codeclimate-clang-tidy test/ci/codeclimate/codeclimate-clang-tidy
+    - |
+      docker run \
+        $(propagate_env_vars \
+          SOURCE_CODE \
+          TIMEOUT_SECONDS \
+          CODECLIMATE_DEBUG \
+          CODECLIMATE_DEV \
+          REPORT_STDOUT \
+          REPORT_FORMAT \
+          ENGINE_MEMORY_LIMIT_BYTES \
+        ) \
+        --volume "$PWD":/code \
+        --volume /var/run/docker.sock:/var/run/docker.sock \
+        "$CODE_QUALITY_IMAGE" /code
   artifacts:
     reports:
-      codequality:
-        - code-quality-clang.json
+      codequality: gl-code-quality-report.json
+    expire_in: 1 week
+  dependencies: []
+  rules:
+    - if: '$CODE_QUALITY_DISABLED'
+      when: never
+    - if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH'
 
 # ------------------------------------------------------------------------------
 pages:
   stage: deploy
   extends:
     - .debian_testing_gcc
   script:
     - cd build
     - cmake -DAKANTU_DOCUMENTATION_DEVELOPER_MANUAL=ON ..
     - cmake --build . -t sphinx-doc
     - mv doc/dev-doc/html ../public
   dependencies:
     - build:debian_testing_gcc
   artifacts:
     paths:
       - public
   only:
     - features/doc
diff --git a/test/ci/codeclimate/codeclimate-clang-tidy/Dockerfile b/test/ci/codeclimate/codeclimate-clang-tidy/Dockerfile
new file mode 100644
index 000000000..7d291f8b3
--- /dev/null
+++ b/test/ci/codeclimate/codeclimate-clang-tidy/Dockerfile
@@ -0,0 +1,23 @@
+FROM alpine:edge
+LABEL maintainer "Nicolas Richart <nicolas.richart@epfl.ch>"
+
+WORKDIR /usr/src/app
+
+RUN apk --update add --no-cache --upgrade \
+    clang\
+    clang-extra-tools \
+    musl-dev \
+    python3 \
+    py3-lxml && \
+    rm -rf /usr/share/ri && \
+    adduser -u 9000 -D -s /bin/false app
+
+#COPY engine.json /
+COPY . ./
+RUN chown -R app:app ./
+USER app
+
+VOLUME /code
+WORKDIR /code
+
+CMD ["/usr/src/app/bin/codeclimate-clang-tidy"]
diff --git a/test/ci/codeclimate/codeclimate-clang-tidy/bin/codeclimate-clang-tidy b/test/ci/codeclimate/codeclimate-clang-tidy/bin/codeclimate-clang-tidy
new file mode 100755
index 000000000..598027114
--- /dev/null
+++ b/test/ci/codeclimate/codeclimate-clang-tidy/bin/codeclimate-clang-tidy
@@ -0,0 +1,13 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+
+lib_path = os.path.abspath(os.path.join(__file__, '..', '..', 'lib'))
+sys.path.append(lib_path)
+
+from runner import Runner  # noqa
+
+if __name__ == '__main__':
+    print(f"Arguments: {sys.argv}", file=sys.stderr)
+    Runner().run()
diff --git a/test/ci/codeclimate/codeclimate-clang-tidy/bin/run-clang-tidy b/test/ci/codeclimate/codeclimate-clang-tidy/bin/run-clang-tidy
new file mode 100755
index 000000000..0dbac0b25
--- /dev/null
+++ b/test/ci/codeclimate/codeclimate-clang-tidy/bin/run-clang-tidy
@@ -0,0 +1,337 @@
+#!/usr/bin/env python3
+#
+#===- run-clang-tidy.py - Parallel clang-tidy runner --------*- python -*--===#
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+#
+#===-----------------------------------------------------------------------===#
+# FIXME: Integrate with clang-tidy-diff.py
+
+
+"""
+Parallel clang-tidy runner
+==========================
+
+Runs clang-tidy over all files in a compilation database. Requires clang-tidy
+and clang-apply-replacements in $PATH.
+
+Example invocations.
+- Run clang-tidy on all files in the current working directory with a default
+  set of checks and show warnings in the cpp files and all project headers.
+    run-clang-tidy.py $PWD
+
+- Fix all header guards.
+    run-clang-tidy.py -fix -checks=-*,llvm-header-guard
+
+- Fix all header guards included from clang-tidy and header guards
+  for clang-tidy headers.
+    run-clang-tidy.py -fix -checks=-*,llvm-header-guard extra/clang-tidy \
+                      -header-filter=extra/clang-tidy
+
+Compilation database setup:
+http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
+"""
+
+from __future__ import print_function
+
+import argparse
+import glob
+import json
+import multiprocessing
+import os
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+import threading
+import traceback
+
+try:
+  import yaml
+except ImportError:
+  yaml = None
+
+is_py2 = sys.version[0] == '2'
+
+if is_py2:
+    import Queue as queue
+else:
+    import queue as queue
+
+
+def find_compilation_database(path):
+  """Adjusts the directory until a compilation database is found."""
+  result = './'
+  while not os.path.isfile(os.path.join(result, path)):
+    if os.path.realpath(result) == '/':
+      print('Error: could not find compilation database.')
+      sys.exit(1)
+    result += '../'
+  return os.path.realpath(result)
+
+
+def make_absolute(f, directory):
+  if os.path.isabs(f):
+    return f
+  return os.path.normpath(os.path.join(directory, f))
+
+
+def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path,
+                        header_filter, allow_enabling_alpha_checkers,
+                        extra_arg, extra_arg_before, quiet, config):
+  """Gets a command line for clang-tidy."""
+  start = [clang_tidy_binary]
+  if allow_enabling_alpha_checkers:
+    start.append('-allow-enabling-analyzer-alpha-checkers')
+  if header_filter is not None:
+    start.append('-header-filter=' + header_filter)
+  if checks:
+    start.append('-checks=' + checks)
+  if tmpdir is not None:
+    start.append('-export-fixes')
+    # Get a temporary file. We immediately close the handle so clang-tidy can
+    # overwrite it.
+    (handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir)
+    os.close(handle)
+    start.append(name)
+  for arg in extra_arg:
+      start.append('-extra-arg=%s' % arg)
+  for arg in extra_arg_before:
+      start.append('-extra-arg-before=%s' % arg)
+  start.append('-p=' + build_path)
+  if quiet:
+      start.append('-quiet')
+  if config:
+      start.append('-config=' + config)
+  start.append(f)
+  return start
+
+
+def merge_replacement_files(tmpdir, mergefile):
+  """Merge all replacement files in a directory into a single file"""
+  # The fixes suggested by clang-tidy >= 4.0.0 are given under
+  # the top level key 'Diagnostics' in the output yaml files
+  mergekey = "Diagnostics"
+  merged=[]
+  for replacefile in glob.iglob(os.path.join(tmpdir, '*.yaml')):
+    content = yaml.safe_load(open(replacefile, 'r'))
+    if not content:
+      continue # Skip empty files.
+    merged.extend(content.get(mergekey, []))
+
+  if merged:
+    # MainSourceFile: The key is required by the definition inside
+    # include/clang/Tooling/ReplacementsYaml.h, but the value
+    # is actually never used inside clang-apply-replacements,
+    # so we set it to '' here.
+    output = {'MainSourceFile': '', mergekey: merged}
+    with open(mergefile, 'w') as out:
+      yaml.safe_dump(output, out)
+  else:
+    # Empty the file:
+    open(mergefile, 'w').close()
+
+
+def check_clang_apply_replacements_binary(args):
+  """Checks if invoking supplied clang-apply-replacements binary works."""
+  try:
+    subprocess.check_call([args.clang_apply_replacements_binary, '--version'])
+  except:
+    print('Unable to run clang-apply-replacements. Is clang-apply-replacements '
+          'binary correctly specified?', file=sys.stderr)
+    traceback.print_exc()
+    sys.exit(1)
+
+
+def apply_fixes(args, tmpdir):
+  """Calls clang-apply-fixes on a given directory."""
+  invocation = [args.clang_apply_replacements_binary]
+  if args.format:
+    invocation.append('-format')
+  if args.style:
+    invocation.append('-style=' + args.style)
+  invocation.append(tmpdir)
+  subprocess.call(invocation)
+
+
+def run_tidy(args, tmpdir, build_path, queue, lock, failed_files):
+  """Takes filenames out of queue and runs clang-tidy on them."""
+  while True:
+    name = queue.get()
+    invocation = get_tidy_invocation(name, args.clang_tidy_binary, args.checks,
+                                     tmpdir, build_path, args.header_filter,
+                                     args.allow_enabling_alpha_checkers,
+                                     args.extra_arg, args.extra_arg_before,
+                                     args.quiet, args.config)
+
+    proc = subprocess.Popen(invocation, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    output, err = proc.communicate()
+    if proc.returncode != 0:
+      failed_files.append(name)
+    with lock:
+      sys.stdout.write(' '.join(invocation) + '\n' + output.decode('utf-8'))
+      if len(err) > 0:
+        sys.stdout.flush()
+        sys.stderr.write(err.decode('utf-8'))
+    queue.task_done()
+
+
+def main():
+  parser = argparse.ArgumentParser(description='Runs clang-tidy over all files '
+                                   'in a compilation database. Requires '
+                                   'clang-tidy and clang-apply-replacements in '
+                                   '$PATH.')
+  parser.add_argument('-allow-enabling-alpha-checkers',
+                      action='store_true', help='allow alpha checkers from '
+                                                'clang-analyzer.')
+  parser.add_argument('-clang-tidy-binary', metavar='PATH',
+                      default='clang-tidy-11',
+                      help='path to clang-tidy binary')
+  parser.add_argument('-clang-apply-replacements-binary', metavar='PATH',
+                      default='clang-apply-replacements-11',
+                      help='path to clang-apply-replacements binary')
+  parser.add_argument('-checks', default=None,
+                      help='checks filter, when not specified, use clang-tidy '
+                      'default')
+  parser.add_argument('-config', default=None,
+                      help='Specifies a configuration in YAML/JSON format: '
+                      '  -config="{Checks: \'*\', '
+                      '                       CheckOptions: [{key: x, '
+                      '                                       value: y}]}" '
+                      'When the value is empty, clang-tidy will '
+                      'attempt to find a file named .clang-tidy for '
+                      'each source file in its parent directories.')
+  parser.add_argument('-header-filter', default=None,
+                      help='regular expression matching the names of the '
+                      'headers to output diagnostics from. Diagnostics from '
+                      'the main file of each translation unit are always '
+                      'displayed.')
+  if yaml:
+    parser.add_argument('-export-fixes', metavar='filename', dest='export_fixes',
+                        help='Create a yaml file to store suggested fixes in, '
+                        'which can be applied with clang-apply-replacements.')
+  parser.add_argument('-j', type=int, default=0,
+                      help='number of tidy instances to be run in parallel.')
+  parser.add_argument('files', nargs='*', default=['.*'],
+                      help='files to be processed (regex on path)')
+  parser.add_argument('-fix', action='store_true', help='apply fix-its')
+  parser.add_argument('-format', action='store_true', help='Reformat code '
+                      'after applying fixes')
+  parser.add_argument('-style', default='file', help='The style of reformat '
+                      'code after applying fixes')
+  parser.add_argument('-p', dest='build_path',
+                      help='Path used to read a compile command database.')
+  parser.add_argument('-extra-arg', dest='extra_arg',
+                      action='append', default=[],
+                      help='Additional argument to append to the compiler '
+                      'command line.')
+  parser.add_argument('-extra-arg-before', dest='extra_arg_before',
+                      action='append', default=[],
+                      help='Additional argument to prepend to the compiler '
+                      'command line.')
+  parser.add_argument('-quiet', action='store_true',
+                      help='Run clang-tidy in quiet mode')
+  args = parser.parse_args()
+
+  db_path = 'compile_commands.json'
+
+  if args.build_path is not None:
+    build_path = args.build_path
+  else:
+    # Find our database
+    build_path = find_compilation_database(db_path)
+
+  try:
+    invocation = [args.clang_tidy_binary, '-list-checks']
+    if args.allow_enabling_alpha_checkers:
+      invocation.append('-allow-enabling-analyzer-alpha-checkers')
+    invocation.append('-p=' + build_path)
+    if args.checks:
+      invocation.append('-checks=' + args.checks)
+    invocation.append('-')
+    if args.quiet:
+      # Even with -quiet we still want to check if we can call clang-tidy.
+      with open(os.devnull, 'w') as dev_null:
+        subprocess.check_call(invocation, stdout=dev_null)
+    else:
+      subprocess.check_call(invocation)
+  except:
+    print("Unable to run clang-tidy.", file=sys.stderr)
+    sys.exit(1)
+
+  # Load the database and extract all files.
+  database = json.load(open(os.path.join(build_path, db_path)))
+  files = [make_absolute(entry['file'], entry['directory'])
+           for entry in database]
+
+  max_task = args.j
+  if max_task == 0:
+    max_task = multiprocessing.cpu_count()
+
+  tmpdir = None
+  if args.fix or (yaml and args.export_fixes):
+    check_clang_apply_replacements_binary(args)
+    tmpdir = tempfile.mkdtemp()
+
+  # Build up a big regexy filter from all command line arguments.
+  file_name_re = re.compile('|'.join(args.files))
+
+  return_code = 0
+  try:
+    # Spin up a bunch of tidy-launching threads.
+    task_queue = queue.Queue(max_task)
+    # List of files with a non-zero return code.
+    failed_files = []
+    lock = threading.Lock()
+    for _ in range(max_task):
+      t = threading.Thread(target=run_tidy,
+                           args=(args, tmpdir, build_path, task_queue, lock, failed_files))
+      t.daemon = True
+      t.start()
+
+    # Fill the queue with files.
+    for name in files:
+      if file_name_re.search(name):
+        task_queue.put(name)
+
+    # Wait for all threads to be done.
+    task_queue.join()
+    if len(failed_files):
+      return_code = 1
+
+  except KeyboardInterrupt:
+    # This is a sad hack. Unfortunately subprocess goes
+    # bonkers with ctrl-c and we start forking merrily.
+    print('\nCtrl-C detected, goodbye.')
+    if tmpdir:
+      shutil.rmtree(tmpdir)
+    os.kill(0, 9)
+
+  if yaml and args.export_fixes:
+    print('Writing fixes to ' + args.export_fixes + ' ...')
+    try:
+      merge_replacement_files(tmpdir, args.export_fixes)
+    except:
+      print('Error exporting fixes.\n', file=sys.stderr)
+      traceback.print_exc()
+      return_code=1
+
+  if args.fix:
+    print('Applying fixes ...')
+    try:
+      apply_fixes(args, tmpdir)
+    except:
+      print('Error applying fixes.\n', file=sys.stderr)
+      traceback.print_exc()
+      return_code = 1
+
+  if tmpdir:
+    shutil.rmtree(tmpdir)
+  sys.exit(return_code)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/test/ci/codeclimate/codeclimate-clang-tidy/engine.json b/test/ci/codeclimate/codeclimate-clang-tidy/engine.json
new file mode 100644
index 000000000..0e520cc6f
--- /dev/null
+++ b/test/ci/codeclimate/codeclimate-clang-tidy/engine.json
@@ -0,0 +1,11 @@
+{
+  "name": "codeclimate-clang-tidy",
+  "description": "clang-tidy is a static analysis tool for C/C++ code.",
+  "maintainer": {
+    "name": "Nicolas Richart",
+    "email": "nicolas.richart@epfl.ch"
+  },
+  "languages" : ["C", "C++"],
+  "version": "0.0.1",
+  "spec_version": "0.2.0"
+}
diff --git a/test/ci/codeclimate/codeclimate-clang-tidy/lib/command.py b/test/ci/codeclimate/codeclimate-clang-tidy/lib/command.py
new file mode 100644
index 000000000..584f86e63
--- /dev/null
+++ b/test/ci/codeclimate/codeclimate-clang-tidy/lib/command.py
@@ -0,0 +1,39 @@
+import os
+
+
+class Command:
+    """Returns command line arguments by parsing codeclimate config file."""
+    def __init__(self, config, file_list):
+        self.config = config
+        self.file_list = file_list
+
+    def build(self):
+        command = ['/usr/src/app/bin/run-clang-tidy',
+                   '-clang-tidy-binary',
+                   '/usr/bin/clang-tidy']
+
+        if self.config.get('check'):
+            command.append(
+                '-checks {}'.format(self.config.get('check')))
+
+        if self.config.get('config'):
+            command.append(
+                '-config {}'.format(self.config.get('project')))
+
+        if self.config.get('header-filter'):
+            command.append(
+                '-header-filter {}'.format(self.config.get('language')))
+
+        if self.config.get('compilation_database_path'):
+            command.append(
+                '-p {}'.format(self.config.get('compilation_database_path')))
+
+        include_paths = []
+        for file_ in self.file_list:
+            include_paths.append(os.path.dirname(file_))
+        include_paths = [f'--extra-arg -I{path}' for path in set(include_paths)]
+
+        command.extend(include_paths)
+        # command.extend(self.file_list)
+
+        return command
diff --git a/test/ci/codeclimate/codeclimate-clang-tidy/lib/issue_formatter.py b/test/ci/codeclimate/codeclimate-clang-tidy/lib/issue_formatter.py
new file mode 100644
index 000000000..3e728fd3a
--- /dev/null
+++ b/test/ci/codeclimate/codeclimate-clang-tidy/lib/issue_formatter.py
@@ -0,0 +1,73 @@
+import hashlib
+import os
+
+
+class IssueFormatter:
+    CLASSIFICATIONS = {
+        'bugprone': {
+            'categories': ['Bug Risk'],
+            'severity': 'major',
+        },
+        'modernize': {
+            'categories': ['Clarity', 'Compatibility', 'Style'],
+            'severity': 'info'
+        },
+        'mpi': {
+            'categories': ['Bug Risk', 'Performance'],
+            'severity': 'critical',
+        },
+        'openmp': {
+            'categories': ['Bug Risk', 'Performance'],
+            'severity': 'critical',
+        },
+        'performance': {
+            'categories': ['Performance'],
+            'severity': 'minor',
+        },
+        'readability': {
+            'categories': ['Clarity', 'Style'],
+            'severity': 'info'
+        },
+    }
+
+    def __init__(self, issue):
+        self.issue_dict = issue
+
+    def format(self):
+        self.issue_dict['file'] = os.path.relpath(self.issue_dict['file'])
+        issue = {
+            'type': 'issue',
+            'check_name': self.issue_dict['type'],
+            'description': self.issue_dict['detail'],
+            'location': {
+                "path": self.issue_dict['file'],
+                "positions": {
+                    "begin": {
+                        "line": int(self.issue_dict['line']),
+                        "column": int(self.issue_dict['column']),
+                    },
+                    "end": {
+                        "line": int(self.issue_dict['line']),
+                        "column": int(self.issue_dict['column']),
+                    },
+                },
+            },
+        }
+
+        if 'content' in self.issue_dict:
+            issue['content'] = {
+                'body': '```\n' +
+                '\n'.join(self.issue_dict['content']) +
+                '\n```'
+            }
+        
+        issue['fingerprint'] = hashlib.md5(
+            '{file}:{line}:{column}:{type}'.format(**self.issue_dict).encode()
+        ).hexdigest()
+
+        type_ = self.issue_dict['type'].split('-')[0]
+        if type_ in self.CLASSIFICATIONS:
+            issue['categories'] = self.CLASSIFICATIONS[type_]['categories']
+            issue['severity'] = self.CLASSIFICATIONS[type_]['severity']
+
+        return issue
diff --git a/test/ci/codeclimate/codeclimate-clang-tidy/lib/runner.py b/test/ci/codeclimate/codeclimate-clang-tidy/lib/runner.py
new file mode 100644
index 000000000..aa0b7ca47
--- /dev/null
+++ b/test/ci/codeclimate/codeclimate-clang-tidy/lib/runner.py
@@ -0,0 +1,106 @@
+import json
+import subprocess
+import sys
+import re
+import tempfile
+
+from command import Command
+from issue_formatter import IssueFormatter
+from workspace import Workspace
+
+
+class Runner:
+    CONFIG_FILE_PATH = '/config.json'
+
+    """Runs clang-tidy, collects and reports results."""
+    def __init__(self):
+        self._config_file_path = self.CONFIG_FILE_PATH
+        #self._workspace_path = workspace_path
+        pass
+
+    def run(self):
+        config = self._decode_config()
+        self._print_debug(f'[clang-tidy] config: {config}')
+
+        workspace = Workspace(config.get('include_paths'))
+        workspace_files = workspace.calculate()
+
+        if not len(workspace_files) > 0:
+            return
+
+        self._print_debug(f'[clang-tidy] analyzing {len(workspace_files)} files')
+
+        plugin_config = config.get('config', {})
+        command = Command(plugin_config, workspace_files).build()
+
+        command.append('src/')
+        self._print_debug(f'[clang-tidy] command: {command}')
+
+        results = self._run_command(command)
+        issues = self._parse_results(results)
+
+        for issue in issues:
+            print(f'{json.dumps(issue)}\0')
+
+    def _decode_config(self):
+        self._print_debug(f"Decoding config file {self._config_file_path}")
+
+        contents = ""
+        with open(self._config_file_path, "r") as config:
+            contents = config.read()
+
+        return json.loads(contents)
+
+    def _run_command(self, command):
+        process = subprocess.Popen(command,
+                                   stdout=subprocess.PIPE,
+                                   stderr=subprocess.PIPE)
+
+        stdout, stderr = process.communicate()
+
+        return_vals = stdout.decode('utf-8').split('\n')
+        return return_vals
+
+    def _parse_results(self, results):
+        ansi_escape = re.compile(r'''
+        \x1B  # ESC
+        (?:   # 7-bit C1 Fe (except CSI)
+            [@-Z\\-_]
+        |     # or [ for CSI, followed by a control sequence
+            \[
+            [0-?]*  # Parameter bytes
+            [ -/]*  # Intermediate bytes
+            [@-~]   # Final byte
+        )''', re.VERBOSE)
+
+        re_log_parse = re.compile(
+            r'(?P<file>.*\.(cc|hh)):(?P<line>[0-9]+):(?P<column>[0-9]+): warning: (?P<detail>.*) \[(?P<type>.*)\]'  # noqa
+        )
+        issues = {}
+        issue = None
+
+        for line in results:
+            clean_line = ansi_escape.sub('', line)
+            match = re_log_parse.match(clean_line)
+            if match:
+                if issue:
+                    issue_ = IssueFormatter(issue).format()
+                    issues[issue_['fingerprint']] = issue_
+                issue = match.groupdict()
+            elif issue:
+                if 'content' in issue:
+                    issue['content'].append(line)
+                else:
+                    issue['content'] = [line]
+            else:
+                issue = None
+
+        if issue:
+            issue_ = IssueFormatter(issue).format()
+            issues[issue_['fingerprint']] = issue_
+
+        issues = list(issues.values())
+        return issues
+
+    def _print_debug(self, message):
+        print(message, file=sys.stderr)
diff --git a/test/ci/codeclimate/codeclimate-clang-tidy/lib/workspace.py b/test/ci/codeclimate/codeclimate-clang-tidy/lib/workspace.py
new file mode 100644
index 000000000..2d3f0bc8d
--- /dev/null
+++ b/test/ci/codeclimate/codeclimate-clang-tidy/lib/workspace.py
@@ -0,0 +1,33 @@
+import os
+
+SRC_SUFFIX = ['.c', '.cpp', '.cc', '.cxx']
+
+
+class Workspace:
+    def __init__(self, include_paths):
+        self.include_paths = include_paths
+
+    def calculate(self):
+        paths = []
+
+        for path in self.include_paths:
+            if os.path.isdir(path):
+                paths.extend(self._walk(path))
+            elif self.should_include(path):
+                paths.append(path)
+
+        return paths
+
+    def should_include(self, name):
+        return name.lower().endswith(tuple(SRC_SUFFIX))
+
+    def _walk(self, path):
+        paths = []
+
+        for root, _directories, files in os.walk(path):
+            for name in files:
+                if self.should_include(name):
+                    path = os.path.join(root, name)
+                    paths.append(path)
+
+        return paths