Vue d'ensemble
Coclico est architecturé autour d'une boucle autonome Observe → Analyze → Act → Validate :
- Observe :
DynamicTracerServicecollecte la télémétrie CPU/RAM/réseau - Analyze :
OptimizationEngineService+SourceAnalyzerService(Roslyn AST) - Act :
AutoPatcherServiceapplique les patches validés - Validate :
DigitalTwinServicevé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 appelerWaitAsync()sur_chatSemou_engineSemdepuis le thread UI. Toujours utiliserConfigureAwait(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
CancellationTokenpropagé
// 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-breakerWorkflowExecutionService— 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>.Sharedpour les buffers de télémétrie (pas denew byte[]) - Ne jamais allouer de closures dans la boucle principale
- Utiliser
_engineSem(jamais_chatSem) pour l'inférence LLM - Toujours
ConfigureAwait(false)sur chaqueawait
// 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 :
PascalCasepour classes/interfaces,_camelCasepour champs privés, suffixeAsyncpour 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
recordpour l'immutabilité des snapshots et résultats - Secrets : Jamais en dur dans le code — utiliser
SettingsServiceou variables d'environnement - Primary constructors C# 12 : Utiliser pour les services simples avec peu de champs
- Collection expressions : Utiliser
[]au lieu denew List<T>()/new T[]