#!/usr/bin/env dotnet-script #nullable enable using System; using System.IO; using System.Diagnostics; using System.Threading; using System.Text; using System.Text.RegularExpressions; using System.Linq; using System.Collections.Generic; // ── Ctrl+C: kill active process and exit cleanly ────────────────────────────── var cts = new CancellationTokenSource(); Process? activeProcess = null; Console.CancelKeyPress += (_, e) => { e.Cancel = true; Console.WriteLine("\n[runner] Interrupted — shutting down..."); cts.Cancel(); try { activeProcess?.Kill(entireProcessTree: true); } catch { } }; // ── Paths ───────────────────────────────────────────────────────────────────── var repoRoot = Directory.GetCurrentDirectory(); var tasksFile = Path.Combine(repoRoot, "Docs", "Tasks.md"); if (!File.Exists(tasksFile)) { Console.Error.WriteLine($"[runner] ERROR: Tasks.md not found at {tasksFile}"); Console.Error.WriteLine("[runner] Run this script from the repository root."); Environment.Exit(1); } // ── Read & split by "---" separator lines ──────────────────────────────────── var content = File.ReadAllText(tasksFile); var items = Regex .Split(content, @"\r?\n---\r?\n") .Select(s => s.Trim()) .Where(s => s.Length > 0) .ToList(); Console.WriteLine($"[runner] Found {items.Count} section(s) in Tasks.md"); // ── Helper: run copilot and stream output, return full output ───────────────── async Task RunCopilot(IEnumerable extraArgs, string prompt) { var output = new StringBuilder(); var argList = new List { "launch", "copilot", "--model", "minimax-m2.7:cloud", "--yes", "--", "--allow-all-tools" }; argList.AddRange(extraArgs); argList.Add("-p"); argList.Add(prompt); var psi = new ProcessStartInfo("ollama") { WorkingDirectory = repoRoot, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, }; foreach (var a in argList) psi.ArgumentList.Add(a); activeProcess = new Process { StartInfo = psi }; activeProcess.OutputDataReceived += (_, e) => { if (e.Data is null) return; Console.WriteLine(e.Data); output.AppendLine(e.Data); }; activeProcess.ErrorDataReceived += (_, e) => { if (e.Data is null) return; Console.Error.WriteLine(e.Data); output.AppendLine(e.Data); }; activeProcess.Start(); activeProcess.BeginOutputReadLine(); activeProcess.BeginErrorReadLine(); await activeProcess.WaitForExitAsync(cts.Token); activeProcess = null; return output.ToString(); } // ── Main loop ───────────────────────────────────────────────────────────────── for (int i = 0; i < items.Count; i++) { var item = items[i]; if (cts.IsCancellationRequested) break; Console.WriteLine(); Console.WriteLine("[runner] ══════════════════════════════════════════════"); Console.WriteLine($"[runner] Task:\n{item}"); Console.WriteLine("[runner] ══════════════════════════════════════════════"); Console.WriteLine(); // Step 1 — run the task prompt await RunCopilot(Enumerable.Empty(), $"/caveman full"); await RunCopilot(new[] { "--continue" }, $"read ./Docs/instructions.md. {item}"); if (cts.IsCancellationRequested) break; // Step 2 — confirm completion in the same chat session Console.WriteLine("\n[runner] Asking for task confirmation...\n"); var confirmation = await RunCopilot( new[] { "--continue" }, "are you sure tasks is done. reply with yes" ); if (cts.IsCancellationRequested) break; // Step 3 — check for "yes" in the reply, with retry logic for issue resolution int maxRetries = 3; int retryCount = 0; bool taskConfirmed = confirmation.Contains("yes", StringComparison.OrdinalIgnoreCase); while (!taskConfirmed && retryCount < maxRetries) { retryCount++; Console.WriteLine($"\n[runner] Attempt {retryCount}/{maxRetries}: Resolving remaining issues and running tests...\n"); confirmation = await RunCopilot( new[] { "--continue" }, "resolve any remaining issues, make sure all tests are running and pass. then confirm with yes if done" ); if (cts.IsCancellationRequested) break; taskConfirmed = confirmation.Contains("yes", StringComparison.OrdinalIgnoreCase); } if (!taskConfirmed) { Console.WriteLine($"\n[runner] Task not confirmed as done after {maxRetries} attempts. Stopping."); break; } // Step 4 — commit the work Console.WriteLine("\n[runner] Task confirmed. Making git commit...\n"); await RunCopilot(Enumerable.Empty(), $"/caveman full"); await RunCopilot(new[] { "--continue" }, "make git commit"); if (cts.IsCancellationRequested) break; // Step 5 — remove completed task from Tasks.md var remaining = items.Skip(i + 1).ToList(); File.WriteAllText(tasksFile, string.Join("\n\n---\n\n", remaining)); Console.WriteLine("[runner] Removed completed task from Tasks.md"); } Console.WriteLine("\n[runner] Finished.");