diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..f7798202a28cec4378ab8138db9217306a5d518b
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,24 @@
+/.phive                 export-ignore
+/tests                  export-ignore
+/.gitattributes         export-ignore
+/.gitignore             export-ignore
+/.gitlab-ci.yml         export-ignore
+/captainhook.json       export-ignore
+/phpcs.xml.dist         export-ignore
+/phpstan-baseline.neon  export-ignore
+/phpstan.dist.neon      export-ignore
+/phpunit.dist.xml       export-ignore
+# Ignore developer files when exporting
+.gitattributes         export-ignore
+.gitignore             export-ignore
+.gitlab-ci.yml         export-ignore
+.phive                 export-ignore
+captainhook.json       export-ignore
+phpcs.xml.dist         export-ignore
+phpstan-baseline.neon  export-ignore
+phpstan.dist.neon      export-ignore
+phpunit.dist.xml       export-ignore
+tests                  export-ignore
+
+# Explicitly set file type and line endings for PHP files, improves git diff output
+*.php     text eol=lf diff=php
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 9488a5cded2b1d4c1fd06f4e7726df2eb4e06fbd..2075b45ffcc28e67cad003af0c2192d5f76f0d31 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,11 @@
 tools/
 phpstan.neon
 .phpunit.result.cache
+
+tools/
+
+phpstan.neon
+
+.phpunit.result.cache
+
+phpunit.xml
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d54510162b70674e0300622233701a1a8549833c..6122e2ad7b60b6f76f315e83769e9f19838ab602 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,2 +1,19 @@
 include:
-  - component: dev.quiqqer.com/quiqqer/stabilization/ci-cd-components/quiqqer-package-bundle/quiqqer-package-bundle@2
+  - component: dev.quiqqer.com/quiqqer/stabilization/ci-cd-components/quiqqer-package-bundle/quiqqer-package-bundle@next-2.x
+    inputs:
+      quiqqer-major-version: 1
+
+# Remove the entire phpunit-php8.1 block, to allow PHPUnit to run on PHP 8.1 in your pipeline
+phpunit-php8.1:
+  rules:
+    - when: never
+
+# Remove the entire phpunit-php8.2 block, to allow PHPUnit to run on PHP 8.2 in your pipeline
+phpunit-php8.2:
+  rules:
+    - when: never
+
+# Remove the entire phpunit-php8.3 block, to allow PHPUnit to run on PHP 8.3 in your pipeline
+phpunit-php8.3:
+  rules:
+    - when: never
diff --git a/.phive/phars.xml b/.phive/phars.xml
index 2abf1f932017c0556ecb1f28c8b830c01af01543..63a030f34ebaa167a4ac0c278a66d42d6f1d11e6 100644
--- a/.phive/phars.xml
+++ b/.phive/phars.xml
@@ -1,5 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <phive xmlns="https://phar.io/phive">
-  <phar name="phpstan" version="1.10.60" installed="1.10.60" location="./tools/phpstan" copy="false"/>
-  <phar name="phpunit" version="^10.5.16" installed="10.5.16" location="./tools/phpunit" copy="false"/>
+  <phar name="phpstan" version="1.11.1" installed="1.11.1" location="./tools/phpstan" copy="false"/>
+  <phar name="phpunit" version="^10.5.20" installed="10.5.20" location="./tools/phpunit" copy="false"/>
+  <phar name="phpcs" version="^3.10.1" installed="3.10.1" location="./tools/phpcs" copy="false"/>
+  <phar name="phpcbf" version="^3.10.1" installed="3.10.1" location="./tools/phpcbf" copy="false"/>
+  <phar name="captainhook" version="^5.23.0" installed="5.23.0" location="./tools/captainhook" copy="false"/>
 </phive>
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000000000000000000000000000000000..4a69a59b440e5beec561eca1e341509bd5a18688
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# Contributing
+
+This package follows the [QUIQQER contribution guidelines](https://dev.quiqqer.com/quiqqer/stabilization/documentation/-/wikis/home).
\ No newline at end of file
diff --git a/captainhook.json b/captainhook.json
new file mode 100644
index 0000000000000000000000000000000000000000..30f10640ec4ca996d2989dfc2c17c4fb7136ab3d
--- /dev/null
+++ b/captainhook.json
@@ -0,0 +1,16 @@
+{
+  "config": {
+    "bootstrap": "tests/captainhook-bootstrap.php"
+  },
+  "pre-commit": {
+    "enabled": true,
+    "actions": [
+      {
+        "action": "\\CaptainHook\\App\\Hook\\PHP\\Action\\Linting"
+      },
+      {
+        "action": "composer test"
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/composer.json b/composer.json
index 5b5c00b3fdc28c826ebf1dcc2be8a13b8392d37a..f26f11d24861bcb6aa29e65f417160b1ef7528a2 100644
--- a/composer.json
+++ b/composer.json
@@ -23,5 +23,47 @@
         "psr-4": {
             "QUI\\Test\\": "src/QUI/Test"
         }
+    },
+    "scripts": {
+        "test": [
+            "@dev:lint",
+            "@dev:phpunit"
+        ],
+        "dev:phpunit": "./tools/phpunit",
+        "dev:lint": [
+            "@dev:lint:phpstan",
+            "@dev:lint:style"
+        ],
+        "dev:lint:phpstan": "./tools/phpstan",
+        "dev:lint:style": "./tools/phpcs",
+        "dev:lint:style:fix": "./tools/phpcbf",
+        "dev:init": [
+            "@dev:init:check-requirements",
+            "@dev:init:tools",
+            "@dev:init:git-hooks"
+        ],
+        "dev:init:check-requirements": [
+            "which composer > /dev/null || (echo 'Error: composer has to be globally installed'; exit 1)",
+            "which phive > /dev/null || (echo 'Error: PHIVE has to be globally installed'; exit 1)"
+        ],
+        "dev:init:tools": "phive install --temporary",
+        "dev:init:git-hooks": "./tools/captainhook install --only-enabled --force"
+    },
+    "scripts-aliases": {
+        "test": [
+            "dev:test"
+        ]
+    },
+    "scripts-descriptions": {
+        "test": "Runs linting, static analysis, and unit tests.",
+        "dev:phpunit": "Run PHPUnit test suites",
+        "dev:lint": "Run PHPStan and code style check",
+        "dev:lint:phpstan": "Run PHPStan",
+        "dev:lint:style": "Run code style check (PHP_CodeSniffer)",
+        "dev:lint:style:fix": "Try to fix code style errors automatically",
+        "dev:init": "Initialize the developer tooling (tools and git hooks)",
+        "dev:init:check-requirements": "Check if the necessary requirements are met",
+        "dev:init:tools": "Install all developer tools (requires PHIVE)",
+        "dev:init:git-hooks": "Install all git hooks (may require tools to be installed)"
     }
-}
+}
\ No newline at end of file
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
new file mode 100644
index 0000000000000000000000000000000000000000..d48084fbdc86a2dbfb78dd89ab2e0b0ff2322ba7
--- /dev/null
+++ b/phpcs.xml.dist
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<ruleset>
+    <!-- Use PSR-12 ruleset -->
+    <rule ref="PSR12"/>
+
+    <!-- Only scan *.php files -->
+    <arg name="extensions" value="php"/>
+
+    <!-- Ignore warnings -->
+    <arg name="warning-severity" value="0"/>
+
+    <!-- Process 64 (or number of CPU cores) files in parallel -->
+    <arg name="parallel" value="64"/>
+
+    <!-- Output relative file paths, by setting the current folder as the basepath -->
+    <arg name="basepath" value="."/>
+
+    <!-- Show colored output -->
+    <arg name="colors"/>
+
+    <!-- Scan everything in the current folder -->
+    <file>.</file>
+</ruleset>
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/phpstan.dist.neon b/phpstan.dist.neon
index 5a936a80d1a2fe3d38f137fefea9801a3c121605..15e9210673a01ed98b39e12b6280cfa0aa90f038 100644
--- a/phpstan.dist.neon
+++ b/phpstan.dist.neon
@@ -1,6 +1,8 @@
+includes:
+	- phpstan-baseline.neon
+
 parameters:
-    level: 9
+    level: 8
     paths:
         - src
-    bootstrapFiles:
-        - tests/phpstan-bootstrap.php
+    tipsOfTheDay: false
diff --git a/phpunit.dist.xml b/phpunit.dist.xml
index 1f6dcbb555bf7bf696c9e3aa9ced5b35b4ab89f4..f6c7becf0c12757beb871a9333e2d81e02aa7cae 100644
--- a/phpunit.dist.xml
+++ b/phpunit.dist.xml
@@ -1,9 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <phpunit bootstrap="tests/phpunit-bootstrap.php">
-  <testsuites>
-    <testsuite name="Unit Tests">
-      <directory>tests/unit</directory>
-    </testsuite>
-  </testsuites> 
+    <testsuites>
+        <testsuite name="Tests">
+            <directory>tests/</directory>
+        </testsuite>
+    </testsuites>
 </phpunit>
-
diff --git a/tests/captainhook-bootstrap.php b/tests/captainhook-bootstrap.php
new file mode 100644
index 0000000000000000000000000000000000000000..1b61b73a71ef5fb804addf29a4266548612c8cf0
--- /dev/null
+++ b/tests/captainhook-bootstrap.php
@@ -0,0 +1,3 @@
+<?php
+
+// This file is supposed to be empty, see https://github.com/captainhookphp/captainhook/issues/248
diff --git a/tests/phpstan-bootstrap.php b/tests/phpstan-bootstrap.php
index eca92fd67bed8ae4ec424ed82d300119d792f042..b61ff4b8300a965e0f46025ba64745a22b118e74 100644
--- a/tests/phpstan-bootstrap.php
+++ b/tests/phpstan-bootstrap.php
@@ -8,4 +8,6 @@
     define('QUIQQER_AJAX', true);
 }
 
+putenv("QUIQQER_OTHER_AUTOLOADERS=KEEP");
+
 require_once __DIR__ . '/../../../../bootstrap.php';