Référence Technique

Wiki Développeur

Architecture interne, patterns, services core et système autonome de Coclico. Référence pour les contributeurs et intégrateurs.

Vue d'ensemble

Coclico est architecturé autour d'une boucle autonome Observe → Analyze → Act → Validate :

  • Observe : DynamicTracerService collecte la télémétrie CPU/RAM/réseau
  • Analyze : OptimizationEngineService + SourceAnalyzerService (Roslyn AST)
  • Act : AutoPatcherService applique les patches validés
  • Validate : DigitalTwinService vérifie que la complexité cyclomatique ne croît pas

Le framework est .NET 10 / C# (LangVersion: preview), UI en WPF + WPF-UI 4.x, DI via Microsoft.Extensions.DependencyInjection.

Startup & DI

Le point d'entrée est App.xaml.cs. La séquence de démarrage est :

// App.xaml.cs — séquence de démarrage simplifiée
protected override async void OnStartup(StartupEventArgs e)
{
    // 1. Élévation UAC obligatoire
    if (!ElevationHelper.IsElevated())
        ElevationHelper.RestartAsAdmin();

    // 2. Construction du conteneur DI
    ServiceContainer.Build(services =>
    {
        services.AddSingleton<ICacheService, CacheService>();
        services.AddSingleton<IDynamicTracer, DynamicTracerService>();
        services.AddSingleton<IRollbackService, RollbackService>();
        services.AddSingleton<IOptimizationEngine, OptimizationEngineService>();
        services.AddSingleton<AiChatService>();
        services.AddTransient<CleaningService>();
        // ... tous les services
    }, validateOnBuild: true);

    // 3. SplashWindow pendant le warm-up des services
    var splash = new SplashWindow();
    splash.Show();
    await Task.Delay(2000).ConfigureAwait(false);

    // 4. MainWindow
    new MainWindow().Show();
    splash.Close();
}

ValidateOnBuild = true garantit qu'une dépendance manquante fait crasher l'appli au démarrage, jamais en runtime.

ServiceContainer

ServiceContainer est un wrapper statique thread-safe autour de IServiceProvider. Il expose deux méthodes principales :

// Services/ServiceContainer.cs
public static class ServiceContainer
{
    private static IServiceProvider? _provider;

    /// Résolution obligatoire — lève une exception si absent
    public static T GetRequired<T>() where T : notnull
        => _provider!.GetRequiredService<T>();

    /// Résolution optionnelle — retourne null si absent
    public static T? GetOptional<T>() where T : class
        => _provider!.GetService<T>();

    public static void Build(Action<IServiceCollection> configure,
                               bool validateOnBuild = true)
    {
        var services = new ServiceCollection();
        configure(services);
        _provider = services.BuildServiceProvider(
            new ServiceProviderOptions { ValidateOnBuild = validateOnBuild });
    }
}
Préférez l'injection constructeur dans le nouveau code. ServiceContainer.GetRequired<T>() est réservé aux cas où l'injection est impossible (ex: handlers statiques, convertisseurs WPF).

Pattern MVVM

Coclico suit strictement le pattern MVVM avec CommunityToolkit.Mvvm 8.4 :

  • Views (Views/*.xaml) — XAML pur, aucune logique métier
  • ViewModels (ViewModels/*ViewModel.cs) — [ObservableProperty], [RelayCommand]
  • Services (Services/*.cs) — logique métier, accès système, IA
// ViewModels/DashboardViewModel.cs
public partial class DashboardViewModel : ObservableObject
{
    private readonly IDynamicTracer _tracer;
    private readonly ICacheService _cache;

    [ObservableProperty] private double _cpuUsage;
    [ObservableProperty] private long _ramUsedMb;
    [ObservableProperty] private double _healthScore;

    public DashboardViewModel(IDynamicTracer tracer, ICacheService cache)
    {
        _tracer = tracer;
        _cache  = cache;
    }

    [RelayCommand]
    private async Task RefreshAsync()
    {
        var snap = await _tracer.GetSnapshotAsync().ConfigureAwait(false);
        // Toujours Dispatcher.InvokeAsync pour les mises à jour UI
        await Application.Current.Dispatcher
            .InvokeAsync(() => { CpuUsage = snap.CpuPercent; });
    }
}

Dual LLM Executor

AiChatService maintient deux paires contexte/exécuteur LLamaSharp totalement isolées pour éviter la contention entre le chat utilisateur et l'engine d'optimisation :

// Services/AiChatService.cs — structure interne
public sealed class AiChatService
{
    // Contexte dédié au chat utilisateur (AiChatView)
    private LLamaContext _chatCtx;
    private readonly SemaphoreSlim _chatSem = new(1, 1);

    // Contexte dédié à l'OptimizationEngine (arrière-plan)
    private LLamaContext _engineCtx;
    private readonly SemaphoreSlim _engineSem = new(1, 1);

    // Immutable Context Swap — reset sans bloquer les consommateurs
    public async Task ResetChatContextAsync()
    {
        var old = Interlocked.Exchange(ref _chatCtx, await BuildContextAsync());
        old?.Dispose();
    }

    public async Task<string> SendChatAsync(string prompt)
    {
        await _chatSem.WaitAsync().ConfigureAwait(false);
        try { return await InferAsync(_chatCtx, prompt); }
        finally { _chatSem.Release(); }
    }
}
Règle absolue : ne jamais appeler WaitAsync() sur _chatSem ou _engineSem depuis le thread UI. Toujours utiliser ConfigureAwait(false).

FeatureExecutionEngine

Toute action longue doit passer par FeatureExecutionEngine.RunFeatureAsync qui ajoute automatiquement :

  • Circuit-breaker — après 3 échecs consécutifs, la feature est désactivée temporairement
  • Télémétrie — durée, succès/échec, exception loguée via Serilog
  • Annulation — support de CancellationToken propagé
// Utilisation dans un ViewModel ou Service
await _engine.RunFeatureAsync(
    featureId: "deep-clean",
    action: async ct =>
    {
        await _cleaningService.RunDeepCleanAsync(ct).ConfigureAwait(false);
    },
    cancellationToken: token
).ConfigureAwait(false);

CacheService

ICacheService / CacheService fournit un cache in-memory thread-safe avec TTL configurable par entrée :

public interface ICacheService
{
    T? Get<T>(string key);
    void Set<T>(string key, T value, TimeSpan? ttl = null);
    bool TryGet<T>(string key, out T? value);
    void Invalidate(string key);
    void InvalidateAll();
}

RollbackService

Toute opération d'écriture déclenchée par l'IA doit être précédée d'un snapshot rollback :

// Pattern obligatoire avant File.WriteAllText
var snapshotId = await _rollback
    .CreateSnapshotAsync(targetFilePath)
    .ConfigureAwait(false);
try
{
    File.WriteAllText(targetFilePath, newContent, Encoding.UTF8);
}
catch
{
    await _rollback.RestoreAsync(snapshotId).ConfigureAwait(false);
    throw;
}

Flow Chains système

Le système de Flow Chains est constitué de trois services :

  • WorkflowPipelineService — CRUD des définitions de pipeline (JSON dans %APPDATA%\Coclico\flow-chains\)
  • WorkflowPipelineExecutionService — exécution séquentielle avec circuit-breaker
  • WorkflowExecutionService — orchestration de haut niveau, planification

Toutes les opérations système (registre, WMI, fichiers) effectuées dans une Flow Chain passent obligatoirement par WorkflowPipelineExecutionService qui applique la ISecurityPolicy avant chaque nœud.

WorkflowPipeline — Modèle de données

Le modèle WorkflowPipeline définit la structure d'un pipeline :

// Models/WorkflowPipeline.cs
public record WorkflowPipeline
{
    public Guid Id { get; init; } = Guid.NewGuid();
    public string Name { get; init; } = string.Empty;
    public List<PipelineNode> Nodes { get; init; } = [];
}

public record PipelineNode
{
    public NodeType Type { get; init; }
    public Dictionary<string, string> Parameters { get; init; } = [];
    public NodeCondition? Condition { get; init; }
    public OnErrorAction OnError { get; init; } = OnErrorAction.ContinueNext;
}

public enum NodeType
{
    KillProcess, LaunchProcess, RestartService, StopService, StartService,
    SetRegistryValue, DeleteRegistryKey, CreateDirectory, DeleteFile,
    MoveFile, CopyFile, RunScript, CleanTemp, EmptyRecycleBin, FlushDns,
    ClearBrowserCache, ClearEventLogs, ClearPrefetch, ClearThumbnailCache,
    ClearWindowsErrorReports, FreeRam, SetWorkingSet, PingHost,
    DisableNetworkAdapter, EnableNetworkAdapter, Wait, Condition, Log, Notification
} // 28 types

public enum ConditionOperator
{
    Equals, NotEquals, GreaterThan, LessThan, GreaterOrEqual, LessOrEqual,
    Contains, NotContains, IsEmpty, IsNotEmpty
} // 10 opérateurs

public enum OnErrorAction
{ ContinueNext, StopChain, SkipToEnd }

SourceAnalyzer (Roslyn AST) — Phase 3.3

SourceAnalyzerService utilise Roslyn pour analyser le code C# source de Coclico lui-même. Il calcule par méthode :

  • Complexité Cyclomatique (CC) — nombre de chemins d'exécution indépendants
  • Métriques de Halstead — Volume (V), Difficulté (D), Effort (E)
  • Maintainability Index (MI) — score de maintenabilité 0-100
// Services/SourceAnalyzerService.cs — interface
public interface ISourceAnalyzer
{
    Task<AnalysisReport> AnalyzeFileAsync(string filePath, CancellationToken ct = default);
    Task<MethodMetrics> GetMethodMetricsAsync(string filePath, string methodName);
}

public record MethodMetrics(
    string MethodName,
    int CyclomaticComplexity,
    double HalsteadVolume,
    double HalsteadDifficulty,
    double HalsteadEffort,
    double MaintainabilityIndex
);

Digital Twin Gate — Phase 3.3

Avant d'appliquer tout patch généré par l'IA, DigitalTwinService valide que le patch ne dégrade pas la qualité du code :

public interface IDigitalTwin
{
    /// Retourne false si le CC du patch est supérieur à l'original
    Task<ValidationResult> ValidatePatchAsync(
        string originalSource,
        string patchedSource,
        CancellationToken ct = default);
}

public record ValidationResult(
    bool IsValid,
    string Reason,
    int OriginalCC,
    int PatchedCC
);

AutoPatcher — Phase 3.4

AutoPatcherService gère le cycle de vie des patches IA avec approbation humaine :

public interface IAutoPatcher
{
    /// Retourne les patches en attente d'approbation
    IReadOnlyList<PatchProposal> GetPendingProposals();

    /// Applique le patch (après validation DigitalTwin + Rollback snapshot)
    Task<ApplyResult> ApproveAndApplyAsync(Guid proposalId, CancellationToken ct = default);

    /// Rejette la proposition sans écrire sur le disque
    void RejectProposalAsync(Guid proposalId, string reason);
}

Par défaut, AutoPatcherAuditOnly = true dans les paramètres : les patches sont loggés mais jamais appliqués sans approbation explicite.

OptimizationEngine — Phase 2

OptimizationEngineService s'exécute toutes les 30 secondes en arrière-plan. Règles de performance critiques :

  • Utiliser ArrayPool<byte>.Shared pour les buffers de télémétrie (pas de new byte[])
  • Ne jamais allouer de closures dans la boucle principale
  • Utiliser _engineSem (jamais _chatSem) pour l'inférence LLM
  • Toujours ConfigureAwait(false) sur chaque await
// Boucle d'optimisation — pattern ArrayPool
var buffer = ArrayPool<byte>.Shared.Rent(4096);
try
{
    await _tracer.FillTelemetryBufferAsync(buffer, ct).ConfigureAwait(false);
    // Traitement du buffer...
}
finally
{
    ArrayPool<byte>.Shared.Return(buffer);
}

SecurityPolicy & AuditLog

SecurityPolicyService charge %APPDATA%\Coclico\security-policy.json et fusionne avec des valeurs par défaut non-suppressibles (hardcoded). Utilisé par WorkflowPipelineExecutionService pour autoriser/bloquer les nœuds.

AuditLogService écrit un journal NDJSON append-only incluant AiDecisionContext (prompt + réponse LLM) pour la traçabilité complète des décisions IA. La rétention est configurable via AuditRetentionDays.

Conventions de code

  • Nommage : PascalCase pour classes/interfaces, _camelCase pour champs privés, suffixe Async pour méthodes asynchrones
  • Injection : Constructeur uniquement — jamais de service-locator dans un constructeur
  • Background : ConfigureAwait(false) systématique dans les services
  • Logs : LoggingService.LogInfo / LoggingService.LogException — jamais swallower une exception silencieusement
  • DTOs : Utiliser record pour l'immutabilité des snapshots et résultats
  • Secrets : Jamais en dur dans le code — utiliser SettingsService ou variables d'environnement
  • Primary constructors C# 12 : Utiliser pour les services simples avec peu de champs
  • Collection expressions : Utiliser [] au lieu de new List<T>() / new T[]