diff --git a/ExerciceChecker/src/app.ts b/ExerciceChecker/src/app.ts
index afc0cb62d547d4edeef34c18e72cace834e7b359..636735a5ee387808e9cb3a6cdbed5a907f8b42f8 100644
--- a/ExerciceChecker/src/app.ts
+++ b/ExerciceChecker/src/app.ts
@@ -4,20 +4,23 @@ const path = require('node:path');
 require('dotenv').config({ path: path.join(__dirname, '../.env') });
 require('./shared/helpers/TypeScriptExtensions'); // ATTENTION : This line MUST be the second of this file
 
-import Styles               from './types/Styles';
-import Icon                 from './types/Icon';
-import boxen                from 'boxen';
-import RecursiveFilesStats  from './shared/helpers/recursiveFilesStats/RecursiveFilesStats';
-import Toolbox              from './shared/helpers/Toolbox';
-import ExerciceHelper       from './shared/helpers/ExerciceHelper';
-import ExerciceCheckerError from './types/ExerciceCheckerError';
-import { exec, spawn }      from 'child_process';
-import util                 from 'util';
-import fs                   from 'fs-extra';
-import HttpManager          from './managers/HttpManager';
-import DojoBackendManager   from './managers/DojoBackendManager';
-import Config               from './config/Config';
-import ArchiveHelper        from './shared/helpers/ArchiveHelper';
+import ClientsSharedConfig         from './sharedByClients/config/ClientsSharedConfig';
+import Styles                      from './types/Style';
+import Icon                        from './sharedByClients/types/Icon';
+import RecursiveFilesStats         from './shared/helpers/recursiveFilesStats/RecursiveFilesStats';
+import Toolbox                     from './shared/helpers/Toolbox';
+import ExerciceCheckerError        from './shared/types/Dojo/ExerciceCheckerError';
+import { exec }                    from 'child_process';
+import util                        from 'util';
+import fs                          from 'fs-extra';
+import HttpManager                 from './managers/HttpManager';
+import DojoBackendManager          from './managers/DojoBackendManager';
+import Config                      from './config/Config';
+import ArchiveHelper               from './shared/helpers/ArchiveHelper';
+import ExerciceDockerCompose       from './sharedByClients/helpers/Dojo/ExerciceDockerCompose';
+import ExerciceResultsValidation   from './sharedByClients/helpers/Dojo/ExerciceResultsValidation';
+import ExerciceEnonce              from './sharedByClients/models/ExerciceEnonce';
+import ClientsSharedExerciceHelper from './sharedByClients/helpers/Dojo/ClientsSharedExerciceHelper';
 
 
 (async () => {
@@ -27,24 +30,30 @@ import ArchiveHelper        from './shared/helpers/ArchiveHelper';
 
     console.log(Styles.APP_NAME(Config.appName));
 
+    let exerciceEnonce: ExerciceEnonce | undefined;
+    let exerciceDockerCompose: ExerciceDockerCompose;
+    let exerciceResultsValidation: ExerciceResultsValidation;
+
     /*
      //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 1 & 2:
      -   Read the dojo enonce file from the enonce repository
      -   Download immutables files (maybe throw or show an error if the files have been modified ?)
      */
-    console.log(Styles.INFO(`${ Icon.INFO }️ Checking the exercice's enonce and his immutable files`));
-    const exerciceEnonce = await DojoBackendManager.getExerciceEnonce();
-    if ( !exerciceEnonce ) {
-        console.error(Styles.ERROR(`${ Icon.ERROR } Error while getting the exercice's enonce`));
-        process.exit(ExerciceCheckerError.EXERCICE_ENONCE_GET_ERROR);
+    {
+        console.log(Styles.INFO(`${ Icon.INFO }️ Checking the exercice's enonce and his immutable files`));
+        exerciceEnonce = await DojoBackendManager.getExerciceEnonce();
+        if ( !exerciceEnonce ) {
+            console.error(Styles.ERROR(`${ Icon.ERROR } Error while getting the exercice's enonce`));
+            process.exit(ExerciceCheckerError.EXERCICE_ENONCE_GET_ERROR);
+        }
+
+        exerciceEnonce.immutable.forEach(immutableFile => {
+            const filePath = path.join(Config.folders.project, immutableFile.file_path);
+            fs.mkdirSync(path.dirname(filePath), { recursive: true });
+            fs.writeFileSync(filePath, immutableFile.content, { encoding: 'base64' });
+        });
     }
 
-    exerciceEnonce.immutable.forEach(immutableFile => {
-        const filePath = path.join(Config.folders.project, immutableFile.file_path);
-        fs.mkdirSync(path.dirname(filePath), { recursive: true });
-        fs.writeFileSync(filePath, immutableFile.content, { encoding: 'base64' });
-    });
-
 
     /*
      //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 3 & 4 & 5:
@@ -52,125 +61,101 @@ import ArchiveHelper        from './shared/helpers/ArchiveHelper';
      - Run docker-compose file
      - Get logs from linked services
      */
-    console.log(Styles.INFO(`${ Icon.INFO } Run docker compose file`));
-    const dockerComposeOverride = fs.readFileSync(path.join(__dirname, '../assets/docker-compose-override.yml'), 'utf8').replace('{{VOLUME_NAME}}', exerciceEnonce.enonceFile.result.volume).replace('{{MOUNT_PATH}}', Config.folders.resultsExercice);
-    fs.writeFileSync(`${ Config.folders.project }/docker-compose-override.yml`, dockerComposeOverride);
-
-    const changeDirectoryCommand = `cd "${ Config.folders.project }"`;
-    const dockerComposeCommand = `docker compose --project-name ${ Config.dockerCompose.projectName } --progress plain --file docker-compose.yml --file docker-compose-override.yml`;
-
-    const containerExitStatus = await new Promise<[ number, string ]>((resolve) => {
-        let logs = '####################################################### Docker Compose & Main Container Logs #######################################################\n';
-
-        const dockerCompose = spawn(`${ dockerComposeCommand } run --build ${ exerciceEnonce.enonceFile.result.container }`, {
-            cwd  : Config.folders.project,
-            shell: true,
-            env  : {
-                'DOCKER_BUILDKIT'  : '1',
-                'BUILDKIT_PROGRESS': 'plain', ...process.env
-            }
-        });
+    {
+        const composeOverridePath: string = path.join(Config.folders.project, 'docker-compose-override.yml');
 
-        dockerCompose.stdout.on('data', (data) => {
-            logs += data.toString();
-            console.log(data.toString());
-        });
+        const composeOverride = fs.readFileSync(path.join(__dirname, '../assets/docker-compose-override.yml'), 'utf8').replace('{{VOLUME_NAME}}', exerciceEnonce.enonceFile.result.volume).replace('{{MOUNT_PATH}}', Config.folders.resultsExercice);
+        fs.writeFileSync(composeOverridePath, composeOverride);
 
-        dockerCompose.stderr.on('data', (data) => {
-            logs += data.toString();
-            console.error(data.toString());
-        });
+        exerciceDockerCompose = new ExerciceDockerCompose(ClientsSharedConfig.dockerCompose.projectName, exerciceEnonce.enonceFile, Config.folders.project, [ composeOverridePath ]);
 
-        dockerCompose.on('exit', (code) => {
-            logs += '####################################################### Other Services Logs #######################################################\n';
-            resolve([ code ?? ExerciceCheckerError.DOCKER_COMPOSE_UP_ERROR, logs ]);
-        });
-    });
-    const containerExitCode = containerExitStatus[0];
-    if ( containerExitCode === ExerciceCheckerError.DOCKER_COMPOSE_UP_ERROR ) {
-        console.error(Styles.ERROR(`${ Icon.ERROR } Error while running the docker compose file`));
-        process.exit(containerExitCode);
-    }
-    fs.writeFileSync(`${ Config.folders.resultsDojo }/dockerComposeLogs.txt`, containerExitStatus[1]);
-
-    console.log(Styles.INFO(`${ Icon.INFO } Acquire logs of linked services`));
-    try {
-        await execAsync(`${ changeDirectoryCommand };${ dockerComposeCommand } logs --timestamps >> ${ Config.folders.resultsDojo }/dockerComposeLogs.txt`);
-    } catch ( error ) {
-        console.error(Styles.ERROR(`${ Icon.ERROR } Error while getting the linked services logs`));
-        process.exit(ExerciceCheckerError.DOCKER_COMPOSE_LOGS_ERROR);
-    }
+        try {
+            await new Promise<void>((resolve, reject) => {
+                exerciceDockerCompose.events.on('step', (name: string, message: string) => {
+                    console.log(Styles.INFO(`${ Icon.INFO } ${ message }`));
+                });
 
+                exerciceDockerCompose.events.on('endStep', (stepName: string, message: string, error: boolean) => {
+                    if ( error ) {
+                        console.error(Styles.ERROR(`${ Icon.ERROR } ${ message }`));
+                    }
+                });
 
-    //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 6: Check content requirements and content size
-    console.log(Styles.INFO(`${ Icon.INFO } Validating results folder size`));
-    const resultsFolderSize = await Toolbox.fs.getTotalSize(Config.folders.resultsExercice);
-    if ( resultsFolderSize > Config.resultsFolderMaxSizeInBytes ) {
-        console.error(Styles.ERROR(`${ Icon.ERROR } Results folder size is too big (bigger than ${ Config.resultsFolderMaxSizeInBytes / 1000000 })`));
-        process.exit(ExerciceCheckerError.EXERCICE_RESULTS_FOLDER_TOO_BIG);
-    }
+                exerciceDockerCompose.events.on('finished', (success: boolean, exitCode: number) => {
+                    success ? resolve() : reject();
+                });
 
-    console.log(Styles.INFO(`${ Icon.INFO } Checking results file`));
-    const resultsFileOriginPath = path.join(Config.folders.resultsExercice, Config.filenames.results);
-    const resultsFilePath = path.join(Config.folders.resultsDojo, Config.filenames.results);
+                exerciceDockerCompose.run();
+            });
+        } catch ( error ) { }
 
-    if ( !fs.existsSync(resultsFileOriginPath) ) {
-        console.error(Styles.ERROR(`${ Icon.ERROR } Results file not found.`));
-        process.exit(ExerciceCheckerError.EXERCICE_RESULTS_FILE_NOT_FOUND);
-    }
+        fs.rmSync(composeOverridePath);
+        fs.writeFileSync(path.join(Config.folders.resultsDojo, 'dockerComposeLogs.txt'), exerciceDockerCompose.allLogs);
 
-    fs.moveSync(resultsFileOriginPath, resultsFilePath, { overwrite: true });
+        if ( !exerciceDockerCompose.success ) {
+            process.exit(exerciceDockerCompose.exitCode);
+        }
+    }
 
-    const validationResults = ExerciceHelper.validateResultFile(resultsFilePath);
 
-    if ( !validationResults.isValid ) {
-        console.error(Styles.ERROR(`${ Icon.ERROR } Results file is not valid. Here are the errors :`));
-        console.error(Styles.ERROR(JSON.stringify(validationResults.errors)));
-        process.exit(ExerciceCheckerError.EXERCICE_RESULTS_FILE_SCHEMA_NOT_VALID);
+    //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 6: Check content requirements and content size
+    {
+        exerciceResultsValidation = new ExerciceResultsValidation(Config.folders.resultsDojo, Config.folders.resultsExercice);
+
+        try {
+            await new Promise<void>((resolve) => {
+                exerciceResultsValidation.events.on('step', (name: string, message: string) => {
+                    console.log(Styles.INFO(`${ Icon.INFO } ${ message }`));
+                });
+
+                exerciceResultsValidation.events.on('endStep', (stepName: string, message: string, error: boolean) => {
+                    if ( error ) {
+                        console.error(Styles.ERROR(`${ Icon.ERROR } ${ message }`));
+                    }
+                });
+
+                exerciceResultsValidation.events.on('finished', (success: boolean, exitCode: number) => {
+                    if ( !success ) {
+                        process.exit(exitCode);
+                    }
+
+                    resolve();
+                });
+
+                exerciceResultsValidation.run();
+            });
+        } catch ( error ) { }
     }
 
 
     //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 7: Upload and show the results
-    try {
-        console.log(Styles.INFO(`${ Icon.INFO } Uploading results to the dojo server`));
-        const commit: any = {};
-        Toolbox.getKeysWithPrefix(process.env, 'CI_COMMIT_').forEach(key => {
-            commit[Toolbox.snakeToCamel(key.replace('CI_COMMIT_', ''))] = process.env[key];
-        });
-
-        const files = await RecursiveFilesStats.explore(Config.folders.resultsVolume, {
-            replacePathByRelativeOne: true,
-            liteStats               : true
-        });
-
-        await DojoBackendManager.sendResults(containerExitCode, commit, validationResults.results!, files, await ArchiveHelper.getBase64(Config.folders.resultsVolume));
-    } catch ( error ) {
-        console.error(Styles.ERROR(`${ Icon.ERROR } Error while uploading the results`));
-        console.error(JSON.stringify(error));
-        process.exit(ExerciceCheckerError.UPLOAD);
+    {
+        try {
+            console.log(Styles.INFO(`${ Icon.INFO } Uploading results to the dojo server`));
+            const commit: any = {};
+            Toolbox.getKeysWithPrefix(process.env, 'CI_COMMIT_').forEach(key => {
+                commit[Toolbox.snakeToCamel(key.replace('CI_COMMIT_', ''))] = process.env[key];
+            });
+
+            const files = await RecursiveFilesStats.explore(Config.folders.resultsVolume, {
+                replacePathByRelativeOne: true,
+                liteStats               : true
+            });
+
+            await DojoBackendManager.sendResults(exerciceDockerCompose.exitCode, commit, exerciceResultsValidation.exerciceResults!, files, await ArchiveHelper.getBase64(Config.folders.resultsVolume));
+        } catch ( error ) {
+            console.error(Styles.ERROR(`${ Icon.ERROR } Error while uploading the results`));
+            console.error(JSON.stringify(error));
+            process.exit(ExerciceCheckerError.UPLOAD);
+        }
     }
 
 
     //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 8: Exit with container exit code
-    const finalLogGlobalResult = `${ Styles.INFO('Global result') } : ${ validationResults.results!.success ? Styles.SUCCESS(`${ Icon.SUCCESS } Success`) : Styles.FAILURE(`${ Icon.FAILURE } Failure`) }`;
-
-    const finalLogExecutionExitCode = `${ Styles.INFO('Execution exit code') } : ${ (containerExitCode == 0 ? Styles.SUCCESS : Styles.ERROR)(containerExitCode) }`;
-
-    const finalLogResultNumbers = validationResults.results!.successfulTests || validationResults.results!.failedTests ? `\n\n${ Styles.SUCCESS('Tests passed') } : ${ validationResults.results!.successfulTests ?? '--' }\n${ Styles.ERROR('Tests failed') } : ${ validationResults.results!.failedTests ?? '--' }` : '';
-
-    const finalLogSuccessResultDetails = (validationResults.results!.successfulTestsList ?? []).map(testName => `- ${ Icon.SUCCESS } ${ testName }`).join('\n');
-    const finalLogFailedResultDetails = (validationResults.results!.failedTestsList ?? []).map(testName => `- ${ Icon.FAILURE } ${ testName }`).join('\n');
-    const finalLogResultDetails = validationResults.results!.successfulTestsList || validationResults.results!.failedTestsList ? `\n\n${ Styles.INFO('Tests') } :${ finalLogSuccessResultDetails != '' ? '\n' + finalLogSuccessResultDetails : '' }${ finalLogFailedResultDetails != '' ? '\n' + finalLogFailedResultDetails : '' }` : '';
+    {
+        ClientsSharedExerciceHelper.displayExecutionResults(exerciceResultsValidation.exerciceResults!, exerciceDockerCompose.exitCode, Styles);
+    }
 
-    console.log(boxen(`${ finalLogGlobalResult }\n\n${ finalLogExecutionExitCode }${ finalLogResultNumbers }${ finalLogResultDetails }`, {
-        title         : 'Results',
-        titleAlignment: 'center',
-        borderColor   : 'yellow',
-        borderStyle   : 'bold',
-        margin        : 1,
-        padding       : 1,
-        textAlignment : 'left'
-    }));
 
-    process.exit(containerExitCode);
+    process.exit(exerciceDockerCompose.exitCode);
 })();
\ No newline at end of file