diff --git a/Models.cs b/Models.cs index 45fe13b..578431d 100644 --- a/Models.cs +++ b/Models.cs @@ -49,6 +49,28 @@ class EvalData enum PagerAction { Quit, Browse, Resume } +enum SessionDbType { CopilotCli, SkillValidator, Unknown } + +record BrowserSession( + string Id, + string Summary, + string Cwd, + DateTime UpdatedAt, + string EventsPath, + long FileSize, + string Branch, + string Repository, + SessionDbType DbType = SessionDbType.CopilotCli, + string? SkillName = null, + string? ScenarioName = null, + string? Role = null, + string? Model = null, + string? Status = null, + string? Prompt = null, + string? MetricsJson = null, + string? JudgeJson = null, + string? PairwiseJson = null); + // ========== JSON output records ========== record TurnOutput( int turn, diff --git a/SessionBrowser.cs b/SessionBrowser.cs index 071b497..a11b85d 100644 --- a/SessionBrowser.cs +++ b/SessionBrowser.cs @@ -8,22 +8,68 @@ class SessionBrowser(ContentRenderer cr, DataParsers dataParsers, string? sessionStateDir) { - public List<(string id, string summary, string cwd, DateTime updatedAt, string eventsPath, long fileSize, string branch, string repository)>? LoadSessionsFromDb(string sessionStateDir, string? dbPathOverride = null) - { - var dbPath = dbPathOverride ?? Path.Combine(Path.GetDirectoryName(sessionStateDir)!, "session-store.db"); - if (!File.Exists(dbPath)) return null; + SessionDbType _currentDbType = SessionDbType.CopilotCli; + /// Detect DB type by inspecting schema tables. + public static SessionDbType DetectDbType(string dbPath) + { + if (!File.Exists(dbPath)) return SessionDbType.Unknown; try { using var conn = new SqliteConnection($"Data Source={dbPath};Mode=ReadOnly"); conn.Open(); - // Validate schema version + // Check for skill-validator schema_info table + using (var cmd = conn.CreateCommand()) + { + cmd.CommandText = "SELECT value FROM schema_info WHERE key='type' LIMIT 1"; + try + { + var val = cmd.ExecuteScalar(); + if (val is string s && s == "skill-validator") + return SessionDbType.SkillValidator; + } + catch { /* table doesn't exist */ } + } + + // Check for Copilot CLI schema_version table using (var cmd = conn.CreateCommand()) { cmd.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name='schema_version'"; - if (cmd.ExecuteScalar() == null) return null; + if (cmd.ExecuteScalar() != null) return SessionDbType.CopilotCli; } + + return SessionDbType.Unknown; + } + catch { return SessionDbType.Unknown; } + } + + public List? LoadSessionsFromDb(string sessionStateDir, string? dbPathOverride = null) + { + var dbPath = dbPathOverride ?? Path.Combine(Path.GetDirectoryName(sessionStateDir)!, "session-store.db"); + var dbType = DetectDbType(dbPath); + + if (dbType == SessionDbType.SkillValidator) + { + _currentDbType = SessionDbType.SkillValidator; + return LoadSkillValidatorSessions(dbPath); + } + + if (dbType != SessionDbType.CopilotCli) return null; + _currentDbType = SessionDbType.CopilotCli; + return LoadCopilotCliSessions(sessionStateDir, dbPath); + } + + List? LoadCopilotCliSessions(string sessionStateDir, string dbPath) + { + if (!File.Exists(dbPath)) return null; + + try + { + using var conn = new SqliteConnection($"Data Source={dbPath};Mode=ReadOnly"); + conn.Open(); + + // Validate schema version using (var cmd = conn.CreateCommand()) { cmd.CommandText = "SELECT version FROM schema_version LIMIT 1"; @@ -43,7 +89,7 @@ class SessionBrowser(ContentRenderer cr, DataParsers dataParsers, string? sessio } // Load sessions - var results = new List<(string id, string summary, string cwd, DateTime updatedAt, string eventsPath, long fileSize, string branch, string repository)>(); + var results = new List(); using (var cmd = conn.CreateCommand()) { cmd.CommandText = "SELECT id, cwd, summary, updated_at, branch, repository FROM sessions ORDER BY updated_at DESC"; @@ -66,14 +112,117 @@ class SessionBrowser(ContentRenderer cr, DataParsers dataParsers, string? sessio else continue; // skip DB entries without local transcript files - results.Add((id, summary, cwd, updatedAt, eventsPath, fileSize, branch, repository)); + results.Add(new BrowserSession(id, summary, cwd, updatedAt, eventsPath, fileSize, branch, repository)); } } return results; } catch { - return null; // any error โ†’ fall back to file scan + return null; + } + } + + List? LoadSkillValidatorSessions(string dbPath) + { + if (!File.Exists(dbPath)) return null; + var dbDir = Path.GetDirectoryName(Path.GetFullPath(dbPath))!; + + try + { + using var conn = new SqliteConnection($"Data Source={dbPath};Mode=ReadOnly"); + conn.Open(); + + var results = new List(); + using var cmd = conn.CreateCommand(); + cmd.CommandText = """ + SELECT s.id, s.skill_name, s.skill_path, s.scenario_name, s.run_index, s.role, s.model, + s.config_dir, s.work_dir, s.prompt, s.status, s.started_at, s.completed_at, + r.metrics_json, r.judge_json, r.pairwise_json + FROM sessions s + LEFT JOIN run_results r ON s.id = r.session_id + ORDER BY s.skill_name, s.scenario_name, s.run_index, s.role + """; + using var reader = cmd.ExecuteReader(); + while (reader.Read()) + { + var id = reader.GetString(0); + var skillName = reader.GetString(1); + var skillPath = reader.GetString(2); + var scenarioName = reader.GetString(3); + var runIndex = reader.GetInt32(4); + var role = reader.GetString(5); + var model = reader.GetString(6); + var configDir = reader.IsDBNull(7) ? null : reader.GetString(7); + var workDir = reader.IsDBNull(8) ? null : reader.GetString(8); + var prompt = reader.IsDBNull(9) ? null : reader.GetString(9); + var status = reader.GetString(10); + var startedAtStr = reader.GetString(11); + var completedAtStr = reader.IsDBNull(12) ? null : reader.GetString(12); + var metricsJson = reader.IsDBNull(13) ? null : reader.GetString(13); + var judgeJson = reader.IsDBNull(14) ? null : reader.GetString(14); + var pairwiseJson = reader.IsDBNull(15) ? null : reader.GetString(15); + + var dateStr = completedAtStr ?? startedAtStr; + DateTime.TryParse(dateStr, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var updatedAt); + + // Resolve events.jsonl: config_dir is relative to DB directory + // Normalize path separators for cross-platform (Windows backslashes โ†’ forward slashes) + var eventsPath = ""; + long fileSize = 0; + if (configDir is not null) + { + var normalizedConfigDir = configDir.Replace('\\', '/'); + var configFullPath = Path.Combine(dbDir, normalizedConfigDir); + + // Try direct: config_dir/events.jsonl + eventsPath = Path.Combine(configFullPath, "events.jsonl"); + if (!File.Exists(eventsPath)) + { + // Try nested: config_dir/session-state/*/events.jsonl + var sessionStateDir2 = Path.Combine(configFullPath, "session-state"); + if (Directory.Exists(sessionStateDir2)) + { + foreach (var subDir in Directory.GetDirectories(sessionStateDir2)) + { + var candidate = Path.Combine(subDir, "events.jsonl"); + if (File.Exists(candidate)) { eventsPath = candidate; break; } + } + } + } + if (File.Exists(eventsPath)) + try { fileSize = new FileInfo(eventsPath).Length; } catch { } + } + + var summary = $"{scenarioName} ({role})"; + var runTag = runIndex > 0 ? $" #{runIndex}" : ""; + var cwd = workDir ?? skillPath; + + results.Add(new BrowserSession( + Id: id, + Summary: summary + runTag, + Cwd: cwd, + UpdatedAt: updatedAt, + EventsPath: eventsPath, + FileSize: fileSize, + Branch: model, + Repository: skillName, + DbType: SessionDbType.SkillValidator, + SkillName: skillName, + ScenarioName: scenarioName, + Role: role, + Model: model, + Status: status, + Prompt: prompt, + MetricsJson: metricsJson, + JudgeJson: judgeJson, + PairwiseJson: pairwiseJson)); + } + return results; + } + catch + { + return null; } } @@ -87,15 +236,16 @@ class SessionBrowser(ContentRenderer cr, DataParsers dataParsers, string? sessio return null; } - List<(string id, string summary, string cwd, DateTime updatedAt, string eventsPath, long fileSize, string branch, string repository)> allSessions = []; + List allSessions = []; var sessionsLock = new System.Threading.Lock(); bool scanComplete = false; int lastRenderedCount = -1; + bool isSkillDb = false; // Background scan thread โ€” try DB first, fall back to file scan var scanThread = new Thread(() => { - // Try loading Copilot sessions from SQLite DB (fast path) + // Try loading sessions from SQLite DB (fast path) var dbSessions = LoadSessionsFromDb(sessionStateDir!, dbPathOverride); string? dbPath = null; HashSet knownSessionIds = []; @@ -105,15 +255,16 @@ class SessionBrowser(ContentRenderer cr, DataParsers dataParsers, string? sessio { // Record the DB path for polling dbPath = dbPathOverride ?? Path.Combine(Path.GetDirectoryName(sessionStateDir!)!, "session-store.db"); + isSkillDb = _currentDbType == SessionDbType.SkillValidator; lock (sessionsLock) { allSessions.AddRange(dbSessions); - allSessions.Sort((a, b) => b.updatedAt.CompareTo(a.updatedAt)); + allSessions.Sort((a, b) => b.UpdatedAt.CompareTo(a.UpdatedAt)); foreach (var s in dbSessions) { - knownSessionIds.Add(s.id); - if (s.updatedAt > lastUpdatedAt) lastUpdatedAt = s.updatedAt; + knownSessionIds.Add(s.Id); + if (s.UpdatedAt > lastUpdatedAt) lastUpdatedAt = s.UpdatedAt; } } } @@ -153,15 +304,15 @@ class SessionBrowser(ContentRenderer cr, DataParsers dataParsers, string? sessio lock (sessionsLock) { - allSessions.Add((id, summary, cwd, updatedAt, eventsPath, fileSize, "", "")); + allSessions.Add(new BrowserSession(id, summary, cwd, updatedAt, eventsPath, fileSize, "", "")); knownSessionIds.Add(id); if (allSessions.Count % 50 == 0) - allSessions.Sort((a, b) => b.updatedAt.CompareTo(a.updatedAt)); + allSessions.Sort((a, b) => b.UpdatedAt.CompareTo(a.UpdatedAt)); } } lock (sessionsLock) { - allSessions.Sort((a, b) => b.updatedAt.CompareTo(a.updatedAt)); + allSessions.Sort((a, b) => b.UpdatedAt.CompareTo(a.UpdatedAt)); } } @@ -202,9 +353,9 @@ class SessionBrowser(ContentRenderer cr, DataParsers dataParsers, string? sessio long fileSize = new FileInfo(jsonlFile).Length; lock (sessionsLock) { - allSessions.Add((claudeId, claudeSummary, claudeCwd, claudeUpdatedAt, jsonlFile, fileSize, "", "")); + allSessions.Add(new BrowserSession(claudeId, claudeSummary, claudeCwd, claudeUpdatedAt, jsonlFile, fileSize, "", "")); if (allSessions.Count % 50 == 0) - allSessions.Sort((a, b) => b.updatedAt.CompareTo(a.updatedAt)); + allSessions.Sort((a, b) => b.UpdatedAt.CompareTo(a.UpdatedAt)); } } catch { continue; } @@ -213,7 +364,7 @@ class SessionBrowser(ContentRenderer cr, DataParsers dataParsers, string? sessio } lock (sessionsLock) { - allSessions.Sort((a, b) => b.updatedAt.CompareTo(a.updatedAt)); + allSessions.Sort((a, b) => b.UpdatedAt.CompareTo(a.UpdatedAt)); } } scanComplete = true; @@ -234,7 +385,7 @@ class SessionBrowser(ContentRenderer cr, DataParsers dataParsers, string? sessio cmd.Parameters.AddWithValue("@lastUpdated", lastUpdatedAt.ToString("o")); using var reader = cmd.ExecuteReader(); - var newSessions = new List<(string id, string summary, string cwd, DateTime updatedAt, string eventsPath, long fileSize, string branch, string repository)>(); + var newSessions = new List(); while (reader.Read()) { @@ -256,7 +407,7 @@ class SessionBrowser(ContentRenderer cr, DataParsers dataParsers, string? sessio else continue; - newSessions.Add((id, summary, cwd, updatedAt, eventsPath, fileSize, branch, repository)); + newSessions.Add(new BrowserSession(id, summary, cwd, updatedAt, eventsPath, fileSize, branch, repository)); knownSessionIds.Add(id); if (updatedAt > lastUpdatedAt) lastUpdatedAt = updatedAt; } @@ -266,7 +417,7 @@ class SessionBrowser(ContentRenderer cr, DataParsers dataParsers, string? sessio lock (sessionsLock) { allSessions.AddRange(newSessions); - allSessions.Sort((a, b) => b.updatedAt.CompareTo(a.updatedAt)); + allSessions.Sort((a, b) => b.UpdatedAt.CompareTo(a.UpdatedAt)); } } } @@ -299,7 +450,7 @@ void RebuildFiltered() if (searchFilter.Length > 0) { var s = allSessions[i]; - var text = $"{s.summary} {s.cwd} {s.id} {s.branch} {s.repository}"; + var text = $"{s.Summary} {s.Cwd} {s.Id} {s.Branch} {s.Repository} {s.SkillName} {s.ScenarioName} {s.Role} {s.Model}"; if (!text.Contains(searchFilter, StringComparison.OrdinalIgnoreCase)) continue; } @@ -327,19 +478,22 @@ void ClampCursor() void LoadPreview() { if (filtered.Count == 0 || cursorIdx >= filtered.Count) { previewLines.Clear(); return; } - string eventsPath; - string id; - lock (sessionsLock) - { - var s = allSessions[filtered[cursorIdx]]; - eventsPath = s.eventsPath; - id = s.id; - } - if (id == previewSessionId) return; - previewSessionId = id; + BrowserSession sess; + lock (sessionsLock) { sess = allSessions[filtered[cursorIdx]]; } + if (sess.Id == previewSessionId) return; + previewSessionId = sess.Id; previewScroll = 0; try { + if (sess.DbType == SessionDbType.SkillValidator) + { + // Show eval metadata as preview for skill-validator sessions + previewLines = RenderSkillPreview(sess); + previewScroll = 0; + return; + } + + var eventsPath = sess.EventsPath; var data = IsClaudeFormat(eventsPath) ? dataParsers.ParseClaudeData(eventsPath) : dataParsers.ParseJsonlData(eventsPath); if (data == null) { previewLines = ["", " (unable to load preview)"]; return; } if (data.Turns.Count > 50) @@ -354,6 +508,82 @@ void LoadPreview() } } + List RenderSkillPreview(BrowserSession s) + { + var lines = new List + { + "", + $" [bold]Skill:[/] {Markup.Escape(s.SkillName ?? "")}", + $" [bold]Scenario:[/] {Markup.Escape(s.ScenarioName ?? "")}", + $" [bold]Role:[/] {Markup.Escape(s.Role ?? "")}", + $" [bold]Model:[/] {Markup.Escape(s.Model ?? "")}", + $" [bold]Status:[/] {Markup.Escape(s.Status ?? "")}", + "" + }; + + if (!string.IsNullOrEmpty(s.Prompt)) + { + lines.Add(" [bold]Prompt:[/]"); + var promptLines = s.Prompt.Split('\n'); + foreach (var pl in promptLines.Take(10)) + lines.Add($" {Markup.Escape(pl.TrimEnd())}"); + if (promptLines.Length > 10) + lines.Add($" [dim]... ({promptLines.Length - 10} more lines)[/]"); + lines.Add(""); + } + + if (!string.IsNullOrEmpty(s.MetricsJson)) + { + lines.Add(" [bold]Metrics:[/]"); + try + { + var doc = JsonDocument.Parse(s.MetricsJson); + foreach (var prop in doc.RootElement.EnumerateObject().Take(10)) + lines.Add($" {Markup.Escape(prop.Name)}: {Markup.Escape(prop.Value.ToString())}"); + } + catch { lines.Add($" {Markup.Escape(s.MetricsJson[..Math.Min(200, s.MetricsJson.Length)])}"); } + lines.Add(""); + } + + if (!string.IsNullOrEmpty(s.JudgeJson)) + { + lines.Add(" [bold]Judge Result:[/]"); + try + { + var doc = JsonDocument.Parse(s.JudgeJson); + foreach (var prop in doc.RootElement.EnumerateObject().Take(10)) + lines.Add($" {Markup.Escape(prop.Name)}: {Markup.Escape(prop.Value.ToString())}"); + } + catch { lines.Add($" {Markup.Escape(s.JudgeJson[..Math.Min(200, s.JudgeJson.Length)])}"); } + lines.Add(""); + } + + if (!string.IsNullOrEmpty(s.PairwiseJson)) + { + lines.Add(" [bold]Pairwise:[/]"); + try + { + var doc = JsonDocument.Parse(s.PairwiseJson); + foreach (var prop in doc.RootElement.EnumerateObject().Take(10)) + lines.Add($" {Markup.Escape(prop.Name)}: {Markup.Escape(prop.Value.ToString())}"); + } + catch { lines.Add($" {Markup.Escape(s.PairwiseJson[..Math.Min(200, s.PairwiseJson.Length)])}"); } + lines.Add(""); + } + + if (!string.IsNullOrEmpty(s.EventsPath) && File.Exists(s.EventsPath)) + { + lines.Add($" [dim]Events: {Markup.Escape(s.EventsPath)}[/]"); + lines.Add($" [dim]Press Enter to view transcript[/]"); + } + else + { + lines.Add(" [dim]No events.jsonl available[/]"); + } + + return lines; + } + void Render() { int w; @@ -378,11 +608,12 @@ void Render() lock (sessionsLock) { var cs = allSessions[filtered[cursorIdx]]; - var updated = cs.updatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm"); - cursorInfo = $" | {cs.id} {updated}"; + var updated = cs.UpdatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm"); + cursorInfo = $" | {cs.Id} {updated}"; } } - var headerBase = $" ๐Ÿ“‹ Sessions โ€” {count} sessions{loadingStatus}{filterStatus}"; + var headerLabel = isSkillDb ? "๐Ÿงช Skill Eval" : "๐Ÿ“‹ Sessions"; + var headerBase = $" {headerLabel} โ€” {count} sessions{loadingStatus}{filterStatus}"; var headerText = headerBase + cursorInfo; int headerVis = VisibleWidth(headerText); var hdrPad = headerVis < w ? new string(' ', w - headerVis) : ""; @@ -402,19 +633,34 @@ void Render() // Left side: session list if (vi < filtered.Count) { - string id, summary, cwd, eventsPath, branch, repository; - DateTime updatedAt; - long fileSize; + BrowserSession sess; lock (sessionsLock) { var si = filtered[vi]; - (id, summary, cwd, updatedAt, eventsPath, fileSize, branch, repository) = allSessions[si]; + sess = allSessions[si]; + } + var age = FormatAge(DateTime.UtcNow - sess.UpdatedAt); + var size = FormatFileSize(sess.FileSize); + string icon; + if (sess.DbType == SessionDbType.SkillValidator) + { + var statusIcon = sess.Status switch + { + "completed" => "โœ…", + "timed_out" => "โฑ๏ธ", + "running" => "๐Ÿ”„", + _ => "๐Ÿงช" + }; + icon = statusIcon; } - var age = FormatAge(DateTime.UtcNow - updatedAt); - var size = FormatFileSize(fileSize); - var icon = eventsPath.Contains(".claude") ? "๐Ÿ”ด" : "๐Ÿค–"; - var branchTag = !string.IsNullOrEmpty(branch) ? $" [{branch}]" : ""; - var display = !string.IsNullOrEmpty(summary) ? summary.ReplaceLineEndings(" ") : cwd; + else + icon = sess.EventsPath.Contains(".claude") ? "๐Ÿ”ด" : "๐Ÿค–"; + var branchTag = !string.IsNullOrEmpty(sess.Branch) ? $" [{sess.Branch}]" : ""; + string display; + if (sess.DbType == SessionDbType.SkillValidator) + display = !string.IsNullOrEmpty(sess.Summary) ? $"{sess.Repository}: {sess.Summary}" : sess.Cwd; + else + display = !string.IsNullOrEmpty(sess.Summary) ? sess.Summary.ReplaceLineEndings(" ") : sess.Cwd; int maxDisplay = Math.Max(10, listWidth - 19 - VisibleWidth(branchTag)); if (VisibleWidth(display) > maxDisplay) display = TruncateToWidth(display, maxDisplay - 3) + "..."; display += branchTag; @@ -611,7 +857,15 @@ void Render() if (filtered.Count > 0 && cursorIdx < filtered.Count) { string path; - lock (sessionsLock) { path = allSessions[filtered[cursorIdx]].eventsPath; } + lock (sessionsLock) { path = allSessions[filtered[cursorIdx]].EventsPath; } + if (string.IsNullOrEmpty(path) || !File.Exists(path)) + { + // No events file โ€” toggle preview instead + if (!showPreview) { showPreview = true; previewSessionId = null; } + AnsiConsole.Clear(); + Render(); + break; + } Console.CursorVisible = true; AnsiConsole.Clear(); return path; @@ -694,7 +948,7 @@ void Render() if (filtered.Count > 0 && cursorIdx >= 0 && cursorIdx < filtered.Count) { string rPath; - lock (sessionsLock) { rPath = allSessions[filtered[cursorIdx]].eventsPath; } + lock (sessionsLock) { rPath = allSessions[filtered[cursorIdx]].EventsPath; } Console.CursorVisible = true; AnsiConsole.Clear(); LaunchResume(rPath); @@ -741,8 +995,17 @@ public void LaunchResume(string path) } else { - command = "copilot"; - args = $"--resume \"{sessionId}\""; + // Copilot CLI can be installed standalone ("copilot") or as a gh extension ("gh copilot") + if (CanRun("copilot")) + { + command = "copilot"; + args = $"--resume \"{sessionId}\""; + } + else + { + command = "gh"; + args = $"copilot --resume \"{sessionId}\""; + } } Console.WriteLine($"Resuming session with: {command} {args}"); @@ -762,7 +1025,27 @@ public void LaunchResume(string path) catch (Exception ex) { Console.Error.WriteLine($"Error launching {command}: {ex.Message}"); - Console.Error.WriteLine($"Make sure '{command}' is installed and available in your PATH."); + Console.Error.WriteLine("Make sure 'copilot' or 'gh copilot' is installed and available in your PATH."); + } + } + + static bool CanRun(string command) + { + try + { + var psi = new System.Diagnostics.ProcessStartInfo + { + FileName = command, + Arguments = "--version", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + using var proc = System.Diagnostics.Process.Start(psi); + proc?.WaitForExit(3000); + return proc?.ExitCode == 0; } + catch { return false; } } } diff --git a/TextUtils.cs b/TextUtils.cs index fe17a18..d5687bc 100644 --- a/TextUtils.cs +++ b/TextUtils.cs @@ -288,7 +288,7 @@ public static string ExtractContentString(JsonElement el) public static string SafeGetString(JsonElement el, string prop) { - if (el.TryGetProperty(prop, out var v) && v.ValueKind == JsonValueKind.String) + if (el.ValueKind == JsonValueKind.Object && el.TryGetProperty(prop, out var v) && v.ValueKind == JsonValueKind.String) return v.GetString() ?? ""; return ""; } diff --git a/replay.cs b/replay.cs index ad892b0..e7139f9 100644 --- a/replay.cs +++ b/replay.cs @@ -595,7 +595,7 @@ replay stats [options] (no args) Browse recent Copilot CLI sessions Options: - --db Browse sessions from an external session-store.db file + --db Browse sessions from a session-store.db or skill-validator sessions.db --tail Show only the last N conversation turns --expand-tools Show tool arguments, results, and thinking/reasoning --full Don't truncate tool output (use with --expand-tools) diff --git a/tests/DbPathTests.cs b/tests/DbPathTests.cs index d9d9e13..ca3addf 100644 --- a/tests/DbPathTests.cs +++ b/tests/DbPathTests.cs @@ -54,7 +54,7 @@ public void HelpText_IncludesDbFlag() var (stdout, stderr) = RunReplayWithArgs("--help"); Assert.Contains("--db", stdout); - Assert.Contains("Browse sessions from an external session-store.db file", stdout); + Assert.Contains("Browse sessions from a session-store.db or skill-validator sessions.db", stdout); } // Helper to create an empty DB file @@ -74,7 +74,7 @@ private string CreateEmptyDbFile() var startInfo = new System.Diagnostics.ProcessStartInfo { FileName = "dotnet", - Arguments = $"run --project {ReplayCs} -- {args}", + Arguments = $"run -v q --project {ReplayCs} -- {args}", RedirectStandardOutput = true, RedirectStandardError = true, RedirectStandardInput = true, // Need to redirect input too for proper TTY detection diff --git a/tests/EdgeCaseTests.cs b/tests/EdgeCaseTests.cs index 9e1f549..6196908 100644 --- a/tests/EdgeCaseTests.cs +++ b/tests/EdgeCaseTests.cs @@ -472,7 +472,7 @@ private string RunReplayWithArgs(string args) var startInfo = new System.Diagnostics.ProcessStartInfo { FileName = "dotnet", - Arguments = $"run --project {ReplayCs} -- {args}", + Arguments = $"run -v q --project {ReplayCs} -- {args}", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, diff --git a/tests/JsonOutputTests.cs b/tests/JsonOutputTests.cs index 46988c0..78e1f89 100644 --- a/tests/JsonOutputTests.cs +++ b/tests/JsonOutputTests.cs @@ -316,7 +316,7 @@ private string RunReplayWithArgs(string args) var startInfo = new System.Diagnostics.ProcessStartInfo { FileName = "dotnet", - Arguments = $"run --project {ReplayCs} -- {args}", + Arguments = $"run -v q --project {ReplayCs} -- {args}", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, diff --git a/tests/SummaryOutputTests.cs b/tests/SummaryOutputTests.cs index f088678..0a71b40 100644 --- a/tests/SummaryOutputTests.cs +++ b/tests/SummaryOutputTests.cs @@ -429,7 +429,7 @@ private string RunReplayWithArgs(string args) var startInfo = new System.Diagnostics.ProcessStartInfo { FileName = "dotnet", - Arguments = $"run --project {ReplayCs} -- {args}", + Arguments = $"run -v q --project {ReplayCs} -- {args}", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false,