using System.Text.Json; namespace Sdt.Core; public sealed record RunEventLogFile( string Path, string Name, DateTimeOffset LastWriteTime, long SizeBytes); public sealed class RunEventLogReader { public IReadOnlyList ListEventFiles(string projectRoot) { var eventsRoot = Path.Combine(projectRoot, ".sdt", "events"); if (!Directory.Exists(eventsRoot)) return []; return Directory.EnumerateFiles(eventsRoot, "*.jsonl", SearchOption.TopDirectoryOnly) .Select(path => { var info = new FileInfo(path); return new RunEventLogFile( Path: path, Name: info.Name, LastWriteTime: info.LastWriteTime, SizeBytes: info.Length); }) .OrderByDescending(f => f.LastWriteTime) .ToList(); } public IReadOnlyList ReadEvents(string filePath) { var results = new List(); if (!File.Exists(filePath)) return results; foreach (var line in File.ReadLines(filePath)) { if (string.IsNullOrWhiteSpace(line)) continue; if (TryParseLine(line, out var evt)) results.Add(evt!); } return results; } internal static bool TryParseLine(string jsonLine, out RunEvent? evt) { evt = null; try { using var doc = JsonDocument.Parse(jsonLine); var root = doc.RootElement; var category = root.TryGetProperty("category", out var c) ? c.GetString() : null; var typeRaw = root.TryGetProperty("type", out var t) ? t.GetString() : null; var message = root.TryGetProperty("message", out var m) ? m.GetString() : null; var workflowId = root.TryGetProperty("workflowId", out var wf) ? wf.GetString() : null; var stepId = root.TryGetProperty("stepId", out var st) ? st.GetString() : null; var tool = root.TryGetProperty("tool", out var tl) ? tl.GetString() : null; var success = root.TryGetProperty("success", out var s) && s.ValueKind != JsonValueKind.Null ? s.GetBoolean() : (bool?)null; var exitCode = root.TryGetProperty("exitCode", out var ec) && ec.ValueKind != JsonValueKind.Null ? ec.GetInt32() : (int?)null; DateTimeOffset? occurred = null; if (root.TryGetProperty("occurredAt", out var ts) && ts.ValueKind == JsonValueKind.String && DateTimeOffset.TryParse(ts.GetString(), out var parsed)) { occurred = parsed; } if (string.IsNullOrWhiteSpace(category) || string.IsNullOrWhiteSpace(typeRaw) || string.IsNullOrWhiteSpace(message) || !Enum.TryParse(typeRaw, ignoreCase: true, out var type)) { return false; } evt = new RunEvent( Category: category!, Type: type, Message: message!, WorkflowId: workflowId, StepId: stepId, Tool: tool, Success: success, ExitCode: exitCode, Timestamp: occurred); return true; } catch { return false; } } }