diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ed05b66d250cbd8df6840b1abd96d1f64093e09c..72b66385b8e2f4070a44db641323882fbccd5b27 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,6 +1,7 @@
 image: registry.gitlab.inria.fr/solverstack/docker/distrib
 
 stages:
+  - pre
   - build
   - test
   - analyze
@@ -21,6 +22,18 @@ before_script:
   rules:
     - if: ($CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME !~ /^notest-.*$/)
 
+preliminary_checks:
+  stage: pre
+  tags: ["docker", "large"]
+  interruptible: true
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+  parallel:
+    matrix:
+      - TEST: [rebase, draft, header]
+  script:
+    - .gitlab/check_ci.sh $TEST
+
 hqr_build_linux:
   stage: build
   tags: ["docker", "large"]
diff --git a/.gitlab/check_ci.sh b/.gitlab/check_ci.sh
new file mode 100755
index 0000000000000000000000000000000000000000..a623889fca93723538caf8c85e4c491601a9380a
--- /dev/null
+++ b/.gitlab/check_ci.sh
@@ -0,0 +1,99 @@
+#!/usr/bin/env bash
+
+success=1
+
+check_rebase()
+{
+    hash_master=$(git show-ref -s origin/master)
+    hash_common=$(git merge-base origin/master ${CI_COMMIT_SHA})
+    if [ "${hash_master}" = "${hash_common}" ]
+    then
+        echo "check_rebase: OK"
+        return 0
+    else
+        echo "check_rebase: Rebase is required"
+        success=0
+        return 1
+    fi
+}
+
+check_draft()
+{
+    if [ "${CI_PIPELINE_SOURCE}" = "merge_request_event" ]
+    then
+        draft=$( echo ${CI_MERGE_REQUEST_TITLE} | sed "s/^Draft.*$/Draft/" )
+        wip=$( echo ${CI_MERGE_REQUEST_TITLE} | sed "s/^WIP.*$/WIP/" )
+
+        if [ "$draft" = "Draft" ]
+        then
+            echo "check_draft: Merge request is in draft mode"
+            success=0
+            return 1
+        fi
+
+        if [ "$wip" = "WIP" ]
+        then
+            echo "check_draft: Merge request is in WIP"
+            success=0
+            return 1
+        fi
+
+        # if [ "${CI_MERGE_REQUEST_APPROVED}" != "true" ]
+        # then
+        #     echo "check_approval: Merge request not yet approved"
+        #     success=0
+        #     return 1
+        # fi
+
+        echo "check_draft: Merge request is ok"
+    fi
+
+    return 0
+}
+
+check_header()
+{
+    echo " Checking file headers: "
+    SCRIPTDIR=$(dirname $0)
+
+    $SCRIPTDIR/check_header.sh
+    rc=$?
+    if [ $rc -eq 0 ]
+    then
+        echo "Check header: SUCCESS"
+    else
+        echo "Check header: FAILED"
+        success=0
+    fi
+}
+
+if [ $# -lt 1 ]
+then
+    echo "Usage: $0 [rebase|draft|header]"
+    exit 1
+fi
+
+echo ""
+echo "----------------------------------------------------"
+case $1 in
+    rebase)
+	check_rebase
+	;;
+    draft)
+	check_draft
+	;;
+    header)
+	check_header
+	;;
+    *)
+        echo "Usage: $0 [rebase|draft|header]"
+	exit 1
+	;;
+esac
+
+if [ $success -eq 0 ]
+then
+    exit 1
+    # We could cancel the job, but then the log is not pushed in time to the web interface
+    #curl --request POST --header "PRIVATE-TOKEN: ${PIPELINE_TOKEN}" "https://gitlab.inria.fr/api/v4/projects/$CI_PROJECT_ID/pipelines/$CI_PIPELINE_ID/cancel"
+fi
diff --git a/.gitlab/check_header.sh b/.gitlab/check_header.sh
new file mode 100755
index 0000000000000000000000000000000000000000..53d686d308a563ce96b9ed69fbc82dfd8641fb83
--- /dev/null
+++ b/.gitlab/check_header.sh
@@ -0,0 +1,191 @@
+#!/usr/bin/env sh
+
+header=1
+nberr=0
+
+print_header()
+{
+    if [ $header -ne 0 ]
+    then
+        echo "------ $1 --------"
+        header=0
+    fi
+}
+
+check_header_file()
+{
+    filename=$1
+    basename=$( basename $filename .in )
+
+    if [ "$basename" != "CMakeLists.txt" ]
+    then
+        toto=$( grep " @file $basename" $filename )
+        if [ $? -ne 0 ]
+        then
+            toto=$( grep " @file .*/$basename" $filename )
+        fi
+
+        if [ $? -ne 0 ]
+        then
+            print_header $filename
+            echo -n "@file line missing or incorrect:"; grep "@file" $filename; echo ""
+            nberr=$(( nberr + 1 ))
+        fi
+    fi
+}
+
+check_header_copyright()
+{
+    filename=$1
+    basename=$( basename $filename )
+
+    year=$( date +%Y )
+    toto=$( grep -E " @copyright [0-9]{4}-$year Bordeaux INP" $filename )
+
+    if [ $? -ne 0 ]
+    then
+        print_header $filename
+        echo -n "@copyright line missing or incorrect:"; grep "@copyright" $filename; echo "";
+    fi
+}
+
+check_header_version()
+{
+    filename=$1
+    basename=$( basename $filename )
+
+    toto=$( grep -E " @version [0-9]\.[0-9]\.[0-9]" $filename )
+    if [ $? -ne 0 ]
+    then
+        print_header $filename
+        echo -n "@version line missing or incorrect:"; grep "@version" $filename; echo "";
+        nberr=$(( nberr + 1 ))
+    fi
+}
+
+check_header_author()
+{
+    filename=$1
+    basename=$( basename $filename )
+
+    toto=$( grep -E " @author " $filename )
+    if [ $? -ne 0 ]
+    then
+        print_header $filename
+        echo "@author line missing";
+        nberr=$(( nberr + 1 ))
+    fi
+}
+
+check_header_date()
+{
+    filename=$1
+    basename=$( basename $filename )
+
+    toto=$( grep -E " @date [0-9]{4}-[01][0-9]-[0-3][0-9]" $filename )
+    if [ $? -ne 0 ]
+    then
+        print_header $filename
+        echo -n "@date line missing or incorrect"; grep "@date" $filename; echo "";
+        nberr=$(( nberr + 1 ))
+    fi
+}
+
+check_header_define()
+{
+    filename=$1
+    basename=$( basename $filename )
+
+    case $basename in
+        *.h)
+            n=$( basename $basename .h | awk '{print tolower($0)}' )
+
+            macro="_${n}_h_"
+            err=0
+
+            toto=$( grep "#ifndef .*$macro" $filename )
+            ret=$?
+            err=$((err + ret))
+
+            if [ $ret -eq 0 ]
+            then
+                macro=$( grep "#ifndef" $filename | sed 's/#ifndef //' )
+            fi
+            toto=$( grep "#define $macro" $filename )
+            ret=$?
+            err=$((err + ret))
+
+            toto=$( grep "#endif /\* $macro \*/" $filename )
+            ret=$?
+            err=$((err + ret))
+
+            if [ $err -ne 0 ]
+            then
+                print_header $filename
+                grep "#ifndef" $filename
+                grep "#define" $filename
+                grep "#endif"  $filename
+                nberr=$(( nberr + 1 ))
+            fi
+            ;;
+        *)
+    esac
+
+}
+
+#
+# Check that the given source file contains
+#
+# @file filename
+# @copyright
+# @version
+# @date
+#
+check_header()
+{
+    header=1
+    check_header_file $1
+    check_header_copyright $1
+    check_header_version $1
+    check_header_author $1
+    check_header_date $1
+    check_header_define $1
+}
+
+#
+# Check headers
+#
+files=$( git ls-files                     |
+             grep -v "^\."                |
+             grep -v ".*\.md"             |
+             grep -v ChangeLog            |
+             grep -v LICENSE              |
+             grep -v ".*\.cmake"          |
+             grep -v doc/                 |
+             grep -v CTest                |
+             grep -v "\.xml"              |
+             grep -v "input/.*\.in"       |
+             grep -v "\.org"              )
+if [ $# -gt 0 ]
+then
+    files=$*
+fi
+
+for f in $files
+do
+    #echo $f
+    if [ -d $f ]
+    then
+        continue;
+    fi
+
+    check_header $f
+done
+
+if [ $nberr -gt 0 ]
+then
+    echo "${nberr} mistakes have been found in the header files."
+    exit 1
+else
+    exit 0
+fi
diff --git a/lib/pkgconfig/hqr.pc.in b/lib/pkgconfig/hqr.pc.in
index 4ae7cbbcab1516d0afaf80e1d7c9e5b383c499ed..8eb78c85fa28776c1b434fcbe640967a84bb1bdc 100644
--- a/lib/pkgconfig/hqr.pc.in
+++ b/lib/pkgconfig/hqr.pc.in
@@ -1,3 +1,13 @@
+#
+#  @file hqr.pc
+#
+#  @copyright 2017-2023 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
+#                       Univ. Bordeaux. All rights reserved.
+#
+#  @version 1.0.0
+#  @author Mathieu Faverge
+#  @date 2017-03-21
+#
 prefix=@CMAKE_INSTALL_PREFIX@
 exec_prefix=${prefix}
 libdir=${exec_prefix}/lib