From 29286e77538651618ab9e3042d035d949ee29c51 Mon Sep 17 00:00:00 2001 From: SHARUKH SHAIKH Date: Tue, 7 Jul 2026 20:05:26 +0800 Subject: [PATCH 1/6] feat(dotnet11): add process-api-net11 skill for new process APIs --- plugins/dotnet11/README.md | 1 + .../skills/process-api-net11/SKILL.md | 172 ++++++++++++++++++ tests/dotnet11/process-api-net11/eval.yaml | 57 ++++++ 3 files changed, 230 insertions(+) create mode 100644 plugins/dotnet11/skills/process-api-net11/SKILL.md create mode 100644 tests/dotnet11/process-api-net11/eval.yaml diff --git a/plugins/dotnet11/README.md b/plugins/dotnet11/README.md index 7a85a71559..b706f171e1 100644 --- a/plugins/dotnet11/README.md +++ b/plugins/dotnet11/README.md @@ -4,4 +4,5 @@ Skills focused on new APIs and language features introduced in .NET 11. ## Skills +- process-api-net11 - system-text-json-net11 diff --git a/plugins/dotnet11/skills/process-api-net11/SKILL.md b/plugins/dotnet11/skills/process-api-net11/SKILL.md new file mode 100644 index 0000000000..39d48dd247 --- /dev/null +++ b/plugins/dotnet11/skills/process-api-net11/SKILL.md @@ -0,0 +1,172 @@ +--- +name: process-api-net11 +description: > + Provides guidance on the new System.Diagnostics.Process APIs introduced in .NET 11. + It covers high-level convenience methods (Process.Run, Process.RunAndCaptureText, Process.StartAndForget), + reliable deadlock-free output reading (Process.ReadAllText/Bytes/Lines), and lifecycle/handle management + (KillOnParentExit, InheritedHandles, StartDetached). + Use when starting, orchestrating, or capturing output from external processes in .NET 11 applications. +license: MIT +--- + +# Process API Improvements — .NET 11 + +New APIs added to `System.Diagnostics.Process` in .NET 11 simplify process management, eliminate boilerplate, and prevent common deadlock patterns when capturing output. + +## When to Use + +- Running or orchestrating external processes in a .NET 11 (or later) project. +- Needing to start a process, wait for it to exit, and capture its output/error streams without risking deadlocks (`Process.RunAndCaptureTextAsync`). +- Wanting to ensure child processes are automatically terminated when the parent process exits (`KillOnParentExit`). +- Requiring trimmer-friendly and NativeAOT-compatible process creation via `SafeProcessHandle`. +- Requiring fine-grained control over handle inheritance (`InheritedHandles`) or starting detached processes (`StartDetached`). + +## When Not to Use + +- The project targets .NET 10 or earlier — these APIs are not available before .NET 11. +- Running simple shells where custom execution code is unnecessary. +- The default `Process.Start()` is sufficient and does not require output capturing or advanced lifecycle rules. + +## Target Framework + +```xml +net11.0 +``` + +## New APIs & Convenience Methods + +### High-Level Convenience APIs + +#### `Process.Run[Async]` +Starts a process and waits for it to exit, returning the exit status. Does not capture standard output or error. +```csharp +public static ProcessExitStatus Run(string fileName, string arguments = "", ProcessStartInfo? startInfo = null) +public static Task RunAsync(string fileName, string arguments = "", ProcessStartInfo? startInfo = null, CancellationToken cancellationToken = default) +``` + +#### `Process.RunAndCaptureText[Async]` +Starts a process, captures both standard output and error, and waits for it to exit. Extremely useful for avoiding deadlocks on stream redirection. +```csharp +public static ProcessTextOutput RunAndCaptureText(string fileName, string arguments = "", ProcessStartInfo? startInfo = null) +public static Task RunAndCaptureTextAsync(string fileName, string arguments = "", ProcessStartInfo? startInfo = null, CancellationToken cancellationToken = default) +``` + +#### `Process.StartAndForget` +Launches a process and immediately releases the system handle resources, returning only the process ID (PID). +```csharp +public static int StartAndForget(string fileName, string arguments = "", ProcessStartInfo? startInfo = null) +``` + +### Reliable Output Reading APIs +These methods allow direct reading from stdout and stderr without manually handling events or async streams, ensuring no OS pipe buffer overflow deadlocks. + +```csharp +public string ReadAllText() +public Task ReadAllTextAsync(CancellationToken cancellationToken = default) +public byte[] ReadAllBytes() +public Task ReadAllBytesAsync(CancellationToken cancellationToken = default) +public string[] ReadAllLines() +public Task ReadAllLinesAsync(CancellationToken cancellationToken = default) +``` + +### ProcessStartInfo Properties + +#### `KillOnParentExit` +Ensures that the spawned child process is terminated when the current (parent) process exits. Works across both Windows and Unix platforms. +```csharp +public bool KillOnParentExit { get; set; } +``` + +#### `InheritedHandles` +Provides precise control over which file/kernel handles are inherited by the child process, preventing accidental resource leaks. +```csharp +public ProcessHandleInheritance InheritedHandles { get; set; } +``` + +#### `StartDetached` +Starts the process detached from the parent's terminal or job session, ensuring it survives the parent's exit. +```csharp +public bool StartDetached { get; set; } +``` + +--- + +## Examples + +### 1. One-Line Run and Capture Output + +Run a process and safely read all output text without stream deadlock risks: + +```csharp +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +// Run 'git --version' and capture output +ProcessTextOutput result = await Process.RunAndCaptureTextAsync("git", "--version"); + +if (result.ExitStatus.ExitCode == 0) +{ + Console.WriteLine($"Git Version: {result.StandardOutput.Trim()}"); +} +else +{ + Console.WriteLine($"Failed with exit code: {result.ExitStatus.ExitCode}"); + Console.WriteLine($"Error: {result.StandardError}"); +} +``` + +### 2. Auto-Killing Child Processes on Parent Exit + +Ensure a long-running background worker process is killed when the main application terminates: + +```csharp +using System.Diagnostics; + +var startInfo = new ProcessStartInfo("dotnet", "run --project BackgroundWorker.csproj") +{ + KillOnParentExit = true // Auto-teardown when this parent process exits +}; + +using var process = Process.Start(startInfo); +// The background worker is now tied to this process's lifecycle +``` + +### 3. Read All Lines From Output + +Start a process and read its output lines safely: + +```csharp +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +var startInfo = new ProcessStartInfo("ping", "127.0.0.1") +{ + RedirectStandardOutput = true +}; + +using var process = Process.Start(startInfo); +if (process != null) +{ + // Read all output lines safely and asynchronously + string[] lines = await process.ReadAllLinesAsync(); + foreach (var line in lines) + { + Console.WriteLine($"> {line}"); + } +} +``` + +### 4. Start and Forget (Fire & Forget) + +Launch a helper tool or browser without holding onto system handle structures: + +```csharp +using System; +using System.Diagnostics; + +// Fire and forget, getting back only the process ID +int pid = Process.StartAndForget("notepad.exe"); +Console.WriteLine($"Notepad started with PID: {pid}"); +``` diff --git a/tests/dotnet11/process-api-net11/eval.yaml b/tests/dotnet11/process-api-net11/eval.yaml new file mode 100644 index 0000000000..793de2fff2 --- /dev/null +++ b/tests/dotnet11/process-api-net11/eval.yaml @@ -0,0 +1,57 @@ +scenarios: + # --- Scenario 1: Run and capture output in .NET 11 --- + - name: "Run and capture process output in .NET 11" + prompt: | + I'm writing a .NET 11 command-line tool that needs to run an external CLI (e.g. 'git status') and capture both stdout and stderr. + I want to make sure I don't run into any OS pipe deadlock issues, and I want to write it in the cleanest way possible using new .NET 11 features. + Show me the C# program and the `.csproj` XML targeting `net11.0` for this tool. Print both in your response. + assertions: + - type: "exit_success" + - type: "output_matches" + pattern: "Process\\.RunAndCaptureText(Async)?" + - type: "output_matches" + pattern: "net11\\.0" + - type: "output_not_matches" + pattern: "RedirectStandardOutput\\s*=\\s*true" + rubric: + - "Uses the new built-in Process.RunAndCaptureText or Process.RunAndCaptureTextAsync static method" + - "Targets net11.0 in the project file" + - "Avoids manual redirection setup boilerplate (like RedirectStandardOutput = true, BeginOutputReadLine, etc.)" + timeout: 180 + + # --- Scenario 2: Auto-teardown child processes on parent exit --- + - name: "Auto-teardown child processes on parent exit in .NET 11" + prompt: | + In a .NET 11 application, I need to spawn a background daemon process. + To prevent orphan processes, I want to ensure that this daemon process is automatically killed by the operating system if my main parent process crashes or exits. + How do I configure this in .NET 11 using ProcessStartInfo? Show me a minimal example, and show the `.csproj` XML targeting `net11.0` in your response. + assertions: + - type: "exit_success" + - type: "output_matches" + pattern: "KillOnParentExit\\s*=\\s*true" + - type: "output_matches" + pattern: "net11\\.0" + rubric: + - "Uses the new KillOnParentExit property on ProcessStartInfo set to true" + - "Targets net11.0" + timeout: 180 + + # --- Scenario 3: Negative — skill should NOT activate --- + - name: "Non-activation: Running a process on .NET 8" + prompt: | + I have a .NET 8 console app and I need to start a process 'notepad.exe'. + Show me a minimal C# program and the `.csproj` XML targeting `net8.0` that starts this process using the traditional Process.Start. Print both in your response. + expect_activation: false + assertions: + - type: "exit_success" + - type: "output_matches" + pattern: "net8\\.0" + - type: "output_matches" + pattern: "Process\\.Start\\(" + - type: "output_not_matches" + pattern: "Process\\.RunAndCaptureText" + rubric: + - "Solves the task using standard pre-.NET 11 APIs (Process.Start)" + - "Does NOT load or reference the process-api-net11 skill" + - "Targets net8.0" + timeout: 180 From 2ae80518911e44768bacc6585ef969c5c612680e Mon Sep 17 00:00:00 2001 From: SHARUKH SHAIKH Date: Sat, 11 Jul 2026 08:26:52 +0800 Subject: [PATCH 2/6] docs(dotnet11): align process-api-net11 signatures and examples with ref assemblies --- .../skills/process-api-net11/SKILL.md | 59 +++++++++++-------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/plugins/dotnet11/skills/process-api-net11/SKILL.md b/plugins/dotnet11/skills/process-api-net11/SKILL.md index 39d48dd247..891285a6ec 100644 --- a/plugins/dotnet11/skills/process-api-net11/SKILL.md +++ b/plugins/dotnet11/skills/process-api-net11/SKILL.md @@ -35,39 +35,45 @@ New APIs added to `System.Diagnostics.Process` in .NET 11 simplify process manag ## New APIs & Convenience Methods -### High-Level Convenience APIs +### High-Level Convenience APIs (Static Methods) -#### `Process.Run[Async]` +#### `Process.Run` / `Process.RunAsync` Starts a process and waits for it to exit, returning the exit status. Does not capture standard output or error. ```csharp -public static ProcessExitStatus Run(string fileName, string arguments = "", ProcessStartInfo? startInfo = null) -public static Task RunAsync(string fileName, string arguments = "", ProcessStartInfo? startInfo = null, CancellationToken cancellationToken = default) +public static ProcessExitStatus Run(string fileName, IList? arguments = null, bool silent = false, TimeSpan? timeout = null) +public static Task RunAsync(string fileName, IList? arguments = null, bool silent = false, CancellationToken cancellationToken = default) +public static ProcessExitStatus Run(ProcessStartInfo startInfo, TimeSpan? timeout = null) +public static Task RunAsync(ProcessStartInfo startInfo, CancellationToken cancellationToken = default) ``` -#### `Process.RunAndCaptureText[Async]` +#### `Process.RunAndCaptureText` / `Process.RunAndCaptureTextAsync` Starts a process, captures both standard output and error, and waits for it to exit. Extremely useful for avoiding deadlocks on stream redirection. ```csharp -public static ProcessTextOutput RunAndCaptureText(string fileName, string arguments = "", ProcessStartInfo? startInfo = null) -public static Task RunAndCaptureTextAsync(string fileName, string arguments = "", ProcessStartInfo? startInfo = null, CancellationToken cancellationToken = default) +public static ProcessTextOutput RunAndCaptureText(string fileName, IList? arguments = null, TimeSpan? timeout = null) +public static Task RunAndCaptureTextAsync(string fileName, IList? arguments = null, CancellationToken cancellationToken = default) +public static ProcessTextOutput RunAndCaptureText(ProcessStartInfo startInfo, TimeSpan? timeout = null) +public static Task RunAndCaptureTextAsync(ProcessStartInfo startInfo, CancellationToken cancellationToken = default) ``` #### `Process.StartAndForget` Launches a process and immediately releases the system handle resources, returning only the process ID (PID). ```csharp -public static int StartAndForget(string fileName, string arguments = "", ProcessStartInfo? startInfo = null) +public static int StartAndForget(string fileName, IList? arguments = null) +public static int StartAndForget(ProcessStartInfo startInfo) ``` -### Reliable Output Reading APIs -These methods allow direct reading from stdout and stderr without manually handling events or async streams, ensuring no OS pipe buffer overflow deadlocks. +### Reliable Output Reading APIs (Instance Methods) +These methods are called on a `Process` instance to directly read stdout and stderr, guaranteeing no OS pipe buffer overflow deadlocks. ```csharp -public string ReadAllText() -public Task ReadAllTextAsync(CancellationToken cancellationToken = default) -public byte[] ReadAllBytes() -public Task ReadAllBytesAsync(CancellationToken cancellationToken = default) -public string[] ReadAllLines() -public Task ReadAllLinesAsync(CancellationToken cancellationToken = default) +public (string StandardOutput, string StandardError) ReadAllText(TimeSpan? timeout = null) +public Task<(string StandardOutput, string StandardError)> ReadAllTextAsync(CancellationToken cancellationToken = default) +public (byte[] StandardOutput, byte[] StandardError) ReadAllBytes(TimeSpan? timeout = null) +public Task<(byte[] StandardOutput, byte[] StandardError)> ReadAllBytesAsync(CancellationToken cancellationToken = default) +public IEnumerable ReadAllLines(TimeSpan? timeout = null) +public IAsyncEnumerable ReadAllLinesAsync(CancellationToken cancellationToken = default) ``` +*Note: `ProcessOutputLine` is a readonly struct containing `string Content` and `bool StandardError` properties.* ### ProcessStartInfo Properties @@ -80,7 +86,7 @@ public bool KillOnParentExit { get; set; } #### `InheritedHandles` Provides precise control over which file/kernel handles are inherited by the child process, preventing accidental resource leaks. ```csharp -public ProcessHandleInheritance InheritedHandles { get; set; } +public IList? InheritedHandles { get; set; } ``` #### `StartDetached` @@ -102,12 +108,12 @@ using System; using System.Diagnostics; using System.Threading.Tasks; -// Run 'git --version' and capture output -ProcessTextOutput result = await Process.RunAndCaptureTextAsync("git", "--version"); +// Run 'git status' and capture output (arguments passed as list) +ProcessTextOutput result = await Process.RunAndCaptureTextAsync("git", ["status"]); if (result.ExitStatus.ExitCode == 0) { - Console.WriteLine($"Git Version: {result.StandardOutput.Trim()}"); + Console.WriteLine($"Git Output: {result.StandardOutput.Trim()}"); } else { @@ -123,7 +129,7 @@ Ensure a long-running background worker process is killed when the main applicat ```csharp using System.Diagnostics; -var startInfo = new ProcessStartInfo("dotnet", "run --project BackgroundWorker.csproj") +var startInfo = new ProcessStartInfo("dotnet", ["run", "--project", "BackgroundWorker.csproj"]) { KillOnParentExit = true // Auto-teardown when this parent process exits }; @@ -141,19 +147,20 @@ using System; using System.Diagnostics; using System.Threading.Tasks; -var startInfo = new ProcessStartInfo("ping", "127.0.0.1") +var startInfo = new ProcessStartInfo("ping", ["127.0.0.1"]) { - RedirectStandardOutput = true + RedirectStandardOutput = true, + RedirectStandardError = true }; using var process = Process.Start(startInfo); if (process != null) { // Read all output lines safely and asynchronously - string[] lines = await process.ReadAllLinesAsync(); - foreach (var line in lines) + await foreach (ProcessOutputLine line in process.ReadAllLinesAsync()) { - Console.WriteLine($"> {line}"); + string prefix = line.StandardError ? "[Err]" : "[Out]"; + Console.WriteLine($"{prefix} > {line.Content}"); } } ``` From 0eaf5fd5855dd5572342122ad056a7e1e10abfe5 Mon Sep 17 00:00:00 2001 From: SHARUKH SHAIKH Date: Sun, 19 Jul 2026 08:48:46 +0800 Subject: [PATCH 3/6] test(dotnet11): harden eval scenarios and add negative non-activation tests per reviewer suggestions --- tests/dotnet11/process-api-net11/eval.yaml | 87 +++++++++++++++++++++- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/tests/dotnet11/process-api-net11/eval.yaml b/tests/dotnet11/process-api-net11/eval.yaml index 793de2fff2..1791924c10 100644 --- a/tests/dotnet11/process-api-net11/eval.yaml +++ b/tests/dotnet11/process-api-net11/eval.yaml @@ -11,8 +11,15 @@ scenarios: pattern: "Process\\.RunAndCaptureText(Async)?" - type: "output_matches" pattern: "net11\\.0" + # The prompt asks for BOTH stdout AND stderr — require both members. + - type: "output_matches" + pattern: "\\.StandardOutput\\b" + - type: "output_matches" + pattern: "\\.StandardError\\b" - type: "output_not_matches" pattern: "RedirectStandardOutput\\s*=\\s*true" + - type: "output_not_matches" + pattern: "BeginOutputReadLine|OutputDataReceived" rubric: - "Uses the new built-in Process.RunAndCaptureText or Process.RunAndCaptureTextAsync static method" - "Targets net11.0 in the project file" @@ -36,7 +43,62 @@ scenarios: - "Targets net11.0" timeout: 180 - # --- Scenario 3: Negative — skill should NOT activate --- + # --- Scenario 3: deadlock-free full read via instance ReadAllText (tuple return) --- + - name: "Deadlock-free full-output read via instance ReadAllText in .NET 11" + prompt: | + In a .NET 11 app I start a child process myself with `Process.Start(...)` because I + need to configure `ProcessStartInfo` first. After it starts I want to read its ENTIRE + standard output AND standard error to completion, without the classic deadlock where + one buffer fills while I'm draining the other. Give me the simplest first-party .NET 11 + way to get both back at once. Show a minimal program and the `net11.0` `.csproj`. + assertions: + - type: "exit_success" + - type: "output_matches" + pattern: "net11\\.0" + - type: "output_matches" + pattern: "ReadAllText(Async)?\\s*\\(" + # Correct: destructure the (StandardOutput, StandardError) tuple (sync or async form). + - type: "output_matches" + pattern: "\\)\\s*=\\s*(await\\s+)?\\w+\\.ReadAllText(Async)?\\s*\\(" + # Wrong: assigning the tuple-returning call to a single string. + - type: "output_not_matches" + pattern: "string\\s+\\w+\\s*=\\s*(await\\s+)?\\w+\\.ReadAllText(Async)?\\s*\\(" + rubric: + - "Uses the new instance method Process.ReadAllText/ReadAllTextAsync" + - "Destructures the (string StandardOutput, string StandardError) tuple — not a single string" + - "Does not fall back to manual StandardOutput.ReadToEndAsync() as the primary mechanism" + - "Targets net11.0" + timeout: 180 + + # --- Scenario 4: stream tagged output lines via ReadAllLinesAsync --- + - name: "Stream tagged process output lines in .NET 11" + prompt: | + I have a long-running child process in a .NET 11 app. As it runs I want to handle each + output line the moment it is produced — not buffer everything to the end — and for each + line I need to know whether it came from stdout or stderr. I want the cleanest first-party + .NET 11 approach, with no event-callback plumbing and no deadlock risk. Show a minimal + program and the `net11.0` `.csproj`. + assertions: + - type: "exit_success" + - type: "output_matches" + pattern: "net11\\.0" + - type: "output_matches" + pattern: "ReadAllLines(Async)?\\s*\\(" + - type: "output_matches" + pattern: "await\\s+foreach" + - type: "output_matches" + pattern: "\\.Content\\b|ProcessOutputLine" + - type: "output_matches" + pattern: "\\.StandardError\\b" + - type: "output_not_matches" + pattern: "OutputDataReceived|BeginOutputReadLine" + rubric: + - "Uses Process.ReadAllLinesAsync consumed with `await foreach` over IAsyncEnumerable" + - "Reads ProcessOutputLine.Content and distinguishes stdout vs stderr via .StandardError" + - "Does NOT use the old OutputDataReceived/BeginOutputReadLine event model; targets net11.0" + timeout: 180 + + # --- Scenario 5: Negative — skill should NOT activate on .NET 8 --- - name: "Non-activation: Running a process on .NET 8" prompt: | I have a .NET 8 console app and I need to start a process 'notepad.exe'. @@ -49,9 +111,30 @@ scenarios: - type: "output_matches" pattern: "Process\\.Start\\(" - type: "output_not_matches" - pattern: "Process\\.RunAndCaptureText" + pattern: "RunAndCaptureText|ReadAllTextAsync|ReadAllLinesAsync|KillOnParentExit" rubric: - "Solves the task using standard pre-.NET 11 APIs (Process.Start)" - "Does NOT load or reference the process-api-net11 skill" - "Targets net8.0" timeout: 180 + + # --- Scenario 6: subtle non-activation — capture output on .NET 10 (APIs are net11-only) --- + - name: "Non-activation: capture process output on .NET 10" + prompt: | + I'm on a `.NET 10` project (`net10.0`). I need to run `git` with the argument `status` + and capture its standard output into a string. Show a minimal C# program and the + `.csproj` XML targeting `net10.0`, and print both. + expect_activation: false + assertions: + - type: "exit_success" + - type: "output_matches" + pattern: "net10\\.0" + - type: "output_not_matches" + pattern: "RunAndCaptureText|ReadAllTextAsync|ReadAllLinesAsync" + - type: "output_matches" + pattern: "RedirectStandardOutput|ReadToEnd" + rubric: + - "Solves the task with pre-.NET 11 APIs (RedirectStandardOutput + ReadToEnd/ReadToEndAsync)" + - "Recognizes the new .NET 11 Process convenience APIs are unavailable on net10.0 and does not use them" + - "Does NOT load the process-api-net11 skill; targets net10.0" + timeout: 180 From d821f787a261a0dc5b815525fb6a2adec2256314 Mon Sep 17 00:00:00 2001 From: shaikhsharukh Date: Sat, 29 Aug 2026 14:15:38 +0800 Subject: [PATCH 4/6] Address PR review comments on process-api-net11 skill --- .../skills/process-api-net11/SKILL.md | 96 +++++++++++++------ 1 file changed, 66 insertions(+), 30 deletions(-) diff --git a/plugins/dotnet11/skills/process-api-net11/SKILL.md b/plugins/dotnet11/skills/process-api-net11/SKILL.md index 891285a6ec..cab89d4beb 100644 --- a/plugins/dotnet11/skills/process-api-net11/SKILL.md +++ b/plugins/dotnet11/skills/process-api-net11/SKILL.md @@ -16,15 +16,14 @@ New APIs added to `System.Diagnostics.Process` in .NET 11 simplify process manag ## When to Use - Running or orchestrating external processes in a .NET 11 (or later) project. -- Needing to start a process, wait for it to exit, and capture its output/error streams without risking deadlocks (`Process.RunAndCaptureTextAsync`). +- Needing to start a process, wait for it to exit, and capture its output/error streams without risking deadlocks (`Process.RunAndCaptureText[Async]`). - Wanting to ensure child processes are automatically terminated when the parent process exits (`KillOnParentExit`). -- Requiring trimmer-friendly and NativeAOT-compatible process creation via `SafeProcessHandle`. +- Requiring trimmer-friendly and NativeAOT-optimized process creation via `SafeProcessHandle` for the smallest possible disk footprint. - Requiring fine-grained control over handle inheritance (`InheritedHandles`) or starting detached processes (`StartDetached`). ## When Not to Use - The project targets .NET 10 or earlier — these APIs are not available before .NET 11. -- Running simple shells where custom execution code is unnecessary. - The default `Process.Start()` is sufficient and does not require output capturing or advanced lifecycle rules. ## Target Framework @@ -33,36 +32,64 @@ New APIs added to `System.Diagnostics.Process` in .NET 11 simplify process manag net11.0 ``` -## New APIs & Convenience Methods - -### High-Level Convenience APIs (Static Methods) - -#### `Process.Run` / `Process.RunAsync` -Starts a process and waits for it to exit, returning the exit status. Does not capture standard output or error. +## New APIs + +### Types + +Before using the new convenience methods, note the following return and record structures: + +- **`ProcessExitStatus`**: Represents the outcome of a completed process. + ```csharp + public readonly record struct ProcessExitStatus(int ExitCode) + { + public bool Success => ExitCode == 0; + } + ``` +- **`ProcessTextOutput`**: Contains the exit status along with all captured standard output and standard error text. + ```csharp + public readonly record struct ProcessTextOutput(ProcessExitStatus ExitStatus, string StandardOutput, string StandardError); + ``` +- **`ProcessOutputLine`**: Represents a single output line tagged with its stream source. + ```csharp + public readonly struct ProcessOutputLine + { + public string Content { get; } + public bool StandardError { get; } + } + ``` + +### High-Level Convenience APIs + +#### Static Methods + +##### `Process.Run` / `Process.RunAsync` +Starts a process and waits for it to exit, returning the exit status. Does not capture standard output or error. Passing `silent: true` discards standard output and error by internally redirecting standard handles to the `NUL` device. ```csharp -public static ProcessExitStatus Run(string fileName, IList? arguments = null, bool silent = false, TimeSpan? timeout = null) -public static Task RunAsync(string fileName, IList? arguments = null, bool silent = false, CancellationToken cancellationToken = default) +public static ProcessExitStatus Run(string fileName, IEnumerable? arguments = null, bool silent = false, TimeSpan? timeout = null) +public static Task RunAsync(string fileName, IEnumerable? arguments = null, bool silent = false, CancellationToken cancellationToken = default) public static ProcessExitStatus Run(ProcessStartInfo startInfo, TimeSpan? timeout = null) public static Task RunAsync(ProcessStartInfo startInfo, CancellationToken cancellationToken = default) ``` -#### `Process.RunAndCaptureText` / `Process.RunAndCaptureTextAsync` +##### `Process.RunAndCaptureText` / `Process.RunAndCaptureTextAsync` Starts a process, captures both standard output and error, and waits for it to exit. Extremely useful for avoiding deadlocks on stream redirection. ```csharp -public static ProcessTextOutput RunAndCaptureText(string fileName, IList? arguments = null, TimeSpan? timeout = null) -public static Task RunAndCaptureTextAsync(string fileName, IList? arguments = null, CancellationToken cancellationToken = default) +public static ProcessTextOutput RunAndCaptureText(string fileName, IEnumerable? arguments = null, TimeSpan? timeout = null) +public static Task RunAndCaptureTextAsync(string fileName, IEnumerable? arguments = null, CancellationToken cancellationToken = default) public static ProcessTextOutput RunAndCaptureText(ProcessStartInfo startInfo, TimeSpan? timeout = null) public static Task RunAndCaptureTextAsync(ProcessStartInfo startInfo, CancellationToken cancellationToken = default) ``` -#### `Process.StartAndForget` -Launches a process and immediately releases the system handle resources, returning only the process ID (PID). +##### `Process.StartAndForget` +There is a common misconception that when a process is disposed, it's also being killed. This is not the case, as `Process.Dispose` only releases the resources associated with the process, but does not kill it. + +To make it easier to start a process without the need to worry about disposing it, `Process.StartAndForget` was introduced. The method starts a process, returns its ID, and immediately releases all handle resources associated with it. ```csharp -public static int StartAndForget(string fileName, IList? arguments = null) +public static int StartAndForget(string fileName, IEnumerable? arguments = null) public static int StartAndForget(ProcessStartInfo startInfo) ``` -### Reliable Output Reading APIs (Instance Methods) +#### Instance Methods These methods are called on a `Process` instance to directly read stdout and stderr, guaranteeing no OS pipe buffer overflow deadlocks. ```csharp @@ -73,22 +100,31 @@ public Task<(byte[] StandardOutput, byte[] StandardError)> ReadAllBytesAsync(Can public IEnumerable ReadAllLines(TimeSpan? timeout = null) public IAsyncEnumerable ReadAllLinesAsync(CancellationToken cancellationToken = default) ``` -*Note: `ProcessOutputLine` is a readonly struct containing `string Content` and `bool StandardError` properties.* ### ProcessStartInfo Properties #### `KillOnParentExit` -Ensures that the spawned child process is terminated when the current (parent) process exits. Works across both Windows and Unix platforms. +Ensures that the spawned child process is terminated when the current (parent) process exits. Works across Windows, Linux, and Android. ```csharp public bool KillOnParentExit { get; set; } ``` #### `InheritedHandles` Provides precise control over which file/kernel handles are inherited by the child process, preventing accidental resource leaks. +- Standard handles (`stdin`, `stdout`, `stderr`) are always included (no need to add them to the list). +- Setting the list to an empty list means only standard handles get inherited. +- Only `SafeFileHandle` and `SafePipeHandle` instances are allowed as of today. +- No global lock is used when spawning new processes on Windows (important for tuning projects that spawn multiple processes in parallel). ```csharp public IList? InheritedHandles { get; set; } ``` +#### `Silent` +When set to `true`, the standard handles are by default redirected to the `NUL` device, ensuring the child process does not keep parent console or terminal resources alive. +```csharp +public bool Silent { get; set; } +``` + #### `StartDetached` Starts the process detached from the parent's terminal or job session, ensuring it survives the parent's exit. ```csharp @@ -101,19 +137,18 @@ public bool StartDetached { get; set; } ### 1. One-Line Run and Capture Output -Run a process and safely read all output text without stream deadlock risks: +Run a process and safely read all output text without stream deadlock risks. For simple CLI operations that don't need cancellation or async scalability, prefer the synchronous overload: ```csharp using System; using System.Diagnostics; -using System.Threading.Tasks; -// Run 'git status' and capture output (arguments passed as list) -ProcessTextOutput result = await Process.RunAndCaptureTextAsync("git", ["status"]); +// Run 'git status' and capture output +ProcessTextOutput result = Process.RunAndCaptureText("git", ["status"]); if (result.ExitStatus.ExitCode == 0) { - Console.WriteLine($"Git Output: {result.StandardOutput.Trim()}"); + Console.WriteLine($"Git Output: {result.StandardOutput}"); } else { @@ -127,14 +162,15 @@ else Ensure a long-running background worker process is killed when the main application terminates: ```csharp +using System; using System.Diagnostics; -var startInfo = new ProcessStartInfo("dotnet", ["run", "--project", "BackgroundWorker.csproj"]) +ProcessStartInfo startInfo = new("dotnet", ["run", "--project", "BackgroundWorker.csproj"]) { - KillOnParentExit = true // Auto-teardown when this parent process exits + KillOnParentExit = OperatingSystem.IsWindows() || OperatingSystem.IsLinux() // Auto-teardown when this parent process exits }; -using var process = Process.Start(startInfo); +using Process? process = Process.Start(startInfo); // The background worker is now tied to this process's lifecycle ``` @@ -147,13 +183,13 @@ using System; using System.Diagnostics; using System.Threading.Tasks; -var startInfo = new ProcessStartInfo("ping", ["127.0.0.1"]) +ProcessStartInfo startInfo = new("ping", ["127.0.0.1"]) { RedirectStandardOutput = true, RedirectStandardError = true }; -using var process = Process.Start(startInfo); +using Process? process = Process.Start(startInfo); if (process != null) { // Read all output lines safely and asynchronously From 5c789a627d183e8fe84a0eb36e6c164df1dec30d Mon Sep 17 00:00:00 2001 From: shaikhsharukh Date: Thu, 3 Sep 2026 19:50:25 +0800 Subject: [PATCH 5/6] Clarify SafeProcessHandle performance and form-factor characteristics --- plugins/dotnet11/skills/process-api-net11/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dotnet11/skills/process-api-net11/SKILL.md b/plugins/dotnet11/skills/process-api-net11/SKILL.md index cab89d4beb..d19963b0d2 100644 --- a/plugins/dotnet11/skills/process-api-net11/SKILL.md +++ b/plugins/dotnet11/skills/process-api-net11/SKILL.md @@ -18,7 +18,7 @@ New APIs added to `System.Diagnostics.Process` in .NET 11 simplify process manag - Running or orchestrating external processes in a .NET 11 (or later) project. - Needing to start a process, wait for it to exit, and capture its output/error streams without risking deadlocks (`Process.RunAndCaptureText[Async]`). - Wanting to ensure child processes are automatically terminated when the parent process exits (`KillOnParentExit`). -- Requiring trimmer-friendly and NativeAOT-optimized process creation via `SafeProcessHandle` for the smallest possible disk footprint. +- Requiring lightweight, low-overhead process creation via `SafeProcessHandle` for better performance characteristics across all form-factors and runtimes. - Requiring fine-grained control over handle inheritance (`InheritedHandles`) or starting detached processes (`StartDetached`). ## When Not to Use From 9b35a5df85a7d6e49433b6f7a5a4dfa71f3f0744 Mon Sep 17 00:00:00 2001 From: shaikhsharukh Date: Thu, 8 Oct 2026 19:19:13 +0800 Subject: [PATCH 6/6] Address review comments from @adamsitnik and @pamod-madubashana: update struct definitions, API notes, examples, and migrate eval to Vally schema --- .../skills/process-api-net11/SKILL.md | 86 +++--- tests/dotnet11/process-api-net11/eval.yaml | 262 +++++++++++------- 2 files changed, 216 insertions(+), 132 deletions(-) diff --git a/plugins/dotnet11/skills/process-api-net11/SKILL.md b/plugins/dotnet11/skills/process-api-net11/SKILL.md index d19963b0d2..f0d5ec7914 100644 --- a/plugins/dotnet11/skills/process-api-net11/SKILL.md +++ b/plugins/dotnet11/skills/process-api-net11/SKILL.md @@ -1,11 +1,11 @@ --- name: process-api-net11 description: > - Provides guidance on the new System.Diagnostics.Process APIs introduced in .NET 11. - It covers high-level convenience methods (Process.Run, Process.RunAndCaptureText, Process.StartAndForget), - reliable deadlock-free output reading (Process.ReadAllText/Bytes/Lines), and lifecycle/handle management - (KillOnParentExit, InheritedHandles, StartDetached). - Use when starting, orchestrating, or capturing output from external processes in .NET 11 applications. + Provides guidance on the new System.Diagnostics.Process APIs introduced in .NET 11: + Process.Run, Process.RunAndCaptureText, Process.StartAndForget, Process.ReadAllText/Bytes/Lines, + KillOnParentExit, InheritedHandles, and StartDetached. + USE FOR: starting, orchestrating, or capturing output from external processes in .NET 11 applications (net11.0). + DO NOT USE FOR: applications targeting .NET 10 or earlier, or basic pre-.NET 11 Process.Start usage without new APIs. license: MIT --- @@ -36,18 +36,15 @@ New APIs added to `System.Diagnostics.Process` in .NET 11 simplify process manag ### Types -Before using the new convenience methods, note the following return and record structures: +Before using the new convenience methods, note the following return structures: - **`ProcessExitStatus`**: Represents the outcome of a completed process. ```csharp - public readonly record struct ProcessExitStatus(int ExitCode) - { - public bool Success => ExitCode == 0; - } + public readonly record struct ProcessExitStatus(int ExitCode, bool Canceled, PosixSignal? Signal = null); ``` -- **`ProcessTextOutput`**: Contains the exit status along with all captured standard output and standard error text. +- **`ProcessTextOutput`**: Contains the exit status along with all captured standard output, standard error text, and the process ID. ```csharp - public readonly record struct ProcessTextOutput(ProcessExitStatus ExitStatus, string StandardOutput, string StandardError); + public readonly record struct ProcessTextOutput(ProcessExitStatus ExitStatus, string StandardOutput, string StandardError, int ProcessId); ``` - **`ProcessOutputLine`**: Represents a single output line tagged with its stream source. ```csharp @@ -63,7 +60,7 @@ Before using the new convenience methods, note the following return and record s #### Static Methods ##### `Process.Run` / `Process.RunAsync` -Starts a process and waits for it to exit, returning the exit status. Does not capture standard output or error. Passing `silent: true` discards standard output and error by internally redirecting standard handles to the `NUL` device. +Starts a process and waits for it to exit, returning the exit status. Does not capture standard output or error. Passing `silent: true` discards standard output and error by internally redirecting standard handles to the `NUL` device. On timeout or cancellation, the process is killed. ```csharp public static ProcessExitStatus Run(string fileName, IEnumerable? arguments = null, bool silent = false, TimeSpan? timeout = null) public static Task RunAsync(string fileName, IEnumerable? arguments = null, bool silent = false, CancellationToken cancellationToken = default) @@ -72,7 +69,9 @@ public static Task RunAsync(ProcessStartInfo startInfo, Cance ``` ##### `Process.RunAndCaptureText` / `Process.RunAndCaptureTextAsync` -Starts a process, captures both standard output and error, and waits for it to exit. Extremely useful for avoiding deadlocks on stream redirection. +Starts a process, captures both standard output and error, and waits for it to exit. Extremely useful for avoiding deadlocks on stream redirection. On timeout or cancellation, the process is killed. + +When using the `ProcessStartInfo` overloads, the caller is responsible for setting `RedirectStandardOutput = true` and `RedirectStandardError = true` on `ProcessStartInfo` (because BCL APIs cannot modify the input arguments they were given). ```csharp public static ProcessTextOutput RunAndCaptureText(string fileName, IEnumerable? arguments = null, TimeSpan? timeout = null) public static Task RunAndCaptureTextAsync(string fileName, IEnumerable? arguments = null, CancellationToken cancellationToken = default) @@ -83,7 +82,7 @@ public static Task RunAndCaptureTextAsync(ProcessStartInfo st ##### `Process.StartAndForget` There is a common misconception that when a process is disposed, it's also being killed. This is not the case, as `Process.Dispose` only releases the resources associated with the process, but does not kill it. -To make it easier to start a process without the need to worry about disposing it, `Process.StartAndForget` was introduced. The method starts a process, returns its ID, and immediately releases all handle resources associated with it. +To make it easier to start a process without the need to worry about disposing it, `Process.StartAndForget` was introduced. The method starts a process, returns its ID, and immediately releases all handle resources associated with it. By default, when output/error redirection was not specified, `Process.StartAndForget` redirects all standard handles to the `NUL` device. ```csharp public static int StartAndForget(string fileName, IEnumerable? arguments = null) public static int StartAndForget(ProcessStartInfo startInfo) @@ -92,6 +91,8 @@ public static int StartAndForget(ProcessStartInfo startInfo) #### Instance Methods These methods are called on a `Process` instance to directly read stdout and stderr, guaranteeing no OS pipe buffer overflow deadlocks. +*Note: Calling these methods requires `RedirectStandardOutput = true` and `RedirectStandardError = true` on the `ProcessStartInfo` passed to `Process.Start`.* + ```csharp public (string StandardOutput, string StandardError) ReadAllText(TimeSpan? timeout = null) public Task<(string StandardOutput, string StandardError)> ReadAllTextAsync(CancellationToken cancellationToken = default) @@ -101,16 +102,21 @@ public IEnumerable ReadAllLines(TimeSpan? timeout = null) public IAsyncEnumerable ReadAllLinesAsync(CancellationToken cancellationToken = default) ``` +Example of deconstructing the tuple return: +```csharp +var (stdout, stderr) = await process.ReadAllTextAsync(); +``` + ### ProcessStartInfo Properties #### `KillOnParentExit` -Ensures that the spawned child process is terminated when the current (parent) process exits. Works across Windows, Linux, and Android. +Ensures that the spawned child process is terminated when the current (parent) process exits (including fatal crash and being force killed). Works across Windows, Linux, and Android. ```csharp public bool KillOnParentExit { get; set; } ``` #### `InheritedHandles` -Provides precise control over which file/kernel handles are inherited by the child process, preventing accidental resource leaks. +Provides precise control over which handles (file descriptors) are inherited by the child process, preventing accidental resource leaks. - Standard handles (`stdin`, `stdout`, `stderr`) are always included (no need to add them to the list). - Setting the list to an empty list means only standard handles get inherited. - Only `SafeFileHandle` and `SafePipeHandle` instances are allowed as of today. @@ -119,12 +125,6 @@ Provides precise control over which file/kernel handles are inherited by the chi public IList? InheritedHandles { get; set; } ``` -#### `Silent` -When set to `true`, the standard handles are by default redirected to the `NUL` device, ensuring the child process does not keep parent console or terminal resources alive. -```csharp -public bool Silent { get; set; } -``` - #### `StartDetached` Starts the process detached from the parent's terminal or job session, ensuring it survives the parent's exit. ```csharp @@ -167,10 +167,10 @@ using System.Diagnostics; ProcessStartInfo startInfo = new("dotnet", ["run", "--project", "BackgroundWorker.csproj"]) { - KillOnParentExit = OperatingSystem.IsWindows() || OperatingSystem.IsLinux() // Auto-teardown when this parent process exits + KillOnParentExit = true // Auto-teardown when this parent process exits }; -using Process? process = Process.Start(startInfo); +using Process process = Process.Start(startInfo)!; // The background worker is now tied to this process's lifecycle ``` @@ -189,19 +189,37 @@ ProcessStartInfo startInfo = new("ping", ["127.0.0.1"]) RedirectStandardError = true }; -using Process? process = Process.Start(startInfo); -if (process != null) +using Process process = Process.Start(startInfo)!; +// Read all output lines safely and asynchronously +await foreach (ProcessOutputLine line in process.ReadAllLinesAsync()) { - // Read all output lines safely and asynchronously - await foreach (ProcessOutputLine line in process.ReadAllLinesAsync()) - { - string prefix = line.StandardError ? "[Err]" : "[Out]"; - Console.WriteLine($"{prefix} > {line.Content}"); - } + string prefix = line.StandardError ? "[Err]" : "[Out]"; + Console.WriteLine($"{prefix} > {line.Content}"); } ``` -### 4. Start and Forget (Fire & Forget) +### 4. Read Entire Output Deadlock-Free (Tuple Return) + +Start a process with custom `ProcessStartInfo` and read both stdout and stderr into a deconstructed tuple without buffer deadlocks: + +```csharp +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +ProcessStartInfo startInfo = new("git", ["diff"]) +{ + RedirectStandardOutput = true, + RedirectStandardError = true +}; + +using Process process = Process.Start(startInfo)!; +var (stdout, stderr) = await process.ReadAllTextAsync(); + +Console.WriteLine($"Diff output: {stdout}"); +``` + +### 5. Start and Forget (Fire & Forget) Launch a helper tool or browser without holding onto system handle structures: diff --git a/tests/dotnet11/process-api-net11/eval.yaml b/tests/dotnet11/process-api-net11/eval.yaml index 1791924c10..43473cb732 100644 --- a/tests/dotnet11/process-api-net11/eval.yaml +++ b/tests/dotnet11/process-api-net11/eval.yaml @@ -1,140 +1,206 @@ -scenarios: - # --- Scenario 1: Run and capture output in .NET 11 --- - - name: "Run and capture process output in .NET 11" +name: process-api-net11 +description: Evaluates the dotnet11/process-api-net11 skill +type: capability +defaults: + timeout: 5m +stimuli: + # --- Stimulus 1: Run and capture output in .NET 11 --- + - name: Run and capture process output in .NET 11 prompt: | I'm writing a .NET 11 command-line tool that needs to run an external CLI (e.g. 'git status') and capture both stdout and stderr. I want to make sure I don't run into any OS pipe deadlock issues, and I want to write it in the cleanest way possible using new .NET 11 features. Show me the C# program and the `.csproj` XML targeting `net11.0` for this tool. Print both in your response. - assertions: - - type: "exit_success" - - type: "output_matches" - pattern: "Process\\.RunAndCaptureText(Async)?" - - type: "output_matches" - pattern: "net11\\.0" - # The prompt asks for BOTH stdout AND stderr — require both members. - - type: "output_matches" - pattern: "\\.StandardOutput\\b" - - type: "output_matches" - pattern: "\\.StandardError\\b" - - type: "output_not_matches" - pattern: "RedirectStandardOutput\\s*=\\s*true" - - type: "output_not_matches" - pattern: "BeginOutputReadLine|OutputDataReceived" + graders: + - type: exit-success + - type: output-matches + config: + pattern: Process\.RunAndCaptureText(Async)? + - type: output-matches + config: + pattern: net11\.0 + - type: output-matches + config: + pattern: \.StandardOutput\b + - type: output-matches + config: + pattern: \.StandardError\b + - type: output-not-matches + config: + pattern: RedirectStandardOutput\s*=\s*true + - type: output-not-matches + config: + pattern: BeginOutputReadLine|OutputDataReceived + - type: prompt rubric: - - "Uses the new built-in Process.RunAndCaptureText or Process.RunAndCaptureTextAsync static method" - - "Targets net11.0 in the project file" - - "Avoids manual redirection setup boilerplate (like RedirectStandardOutput = true, BeginOutputReadLine, etc.)" - timeout: 180 + - Uses the new built-in Process.RunAndCaptureText or Process.RunAndCaptureTextAsync static method + - Targets net11.0 in the project file + - Avoids manual redirection setup boilerplate (like RedirectStandardOutput = true, BeginOutputReadLine, etc.) - # --- Scenario 2: Auto-teardown child processes on parent exit --- - - name: "Auto-teardown child processes on parent exit in .NET 11" + # --- Stimulus 2: Auto-teardown child processes on parent exit --- + - name: Auto-teardown child processes on parent exit in .NET 11 prompt: | In a .NET 11 application, I need to spawn a background daemon process. To prevent orphan processes, I want to ensure that this daemon process is automatically killed by the operating system if my main parent process crashes or exits. How do I configure this in .NET 11 using ProcessStartInfo? Show me a minimal example, and show the `.csproj` XML targeting `net11.0` in your response. - assertions: - - type: "exit_success" - - type: "output_matches" - pattern: "KillOnParentExit\\s*=\\s*true" - - type: "output_matches" - pattern: "net11\\.0" + graders: + - type: exit-success + - type: output-matches + config: + pattern: KillOnParentExit\s*=\s*true + - type: output-matches + config: + pattern: net11\.0 + - type: prompt rubric: - - "Uses the new KillOnParentExit property on ProcessStartInfo set to true" - - "Targets net11.0" - timeout: 180 + - Uses the new KillOnParentExit property on ProcessStartInfo set to true + - Targets net11.0 - # --- Scenario 3: deadlock-free full read via instance ReadAllText (tuple return) --- - - name: "Deadlock-free full-output read via instance ReadAllText in .NET 11" + # --- Stimulus 3: deadlock-free full read via instance ReadAllText (tuple return) --- + - name: Deadlock-free full-output read via instance ReadAllText in .NET 11 prompt: | In a .NET 11 app I start a child process myself with `Process.Start(...)` because I need to configure `ProcessStartInfo` first. After it starts I want to read its ENTIRE standard output AND standard error to completion, without the classic deadlock where one buffer fills while I'm draining the other. Give me the simplest first-party .NET 11 way to get both back at once. Show a minimal program and the `net11.0` `.csproj`. - assertions: - - type: "exit_success" - - type: "output_matches" - pattern: "net11\\.0" - - type: "output_matches" - pattern: "ReadAllText(Async)?\\s*\\(" - # Correct: destructure the (StandardOutput, StandardError) tuple (sync or async form). - - type: "output_matches" - pattern: "\\)\\s*=\\s*(await\\s+)?\\w+\\.ReadAllText(Async)?\\s*\\(" - # Wrong: assigning the tuple-returning call to a single string. - - type: "output_not_matches" - pattern: "string\\s+\\w+\\s*=\\s*(await\\s+)?\\w+\\.ReadAllText(Async)?\\s*\\(" + graders: + - type: exit-success + - type: output-matches + config: + pattern: net11\.0 + - type: output-matches + config: + pattern: ReadAllText(Async)?\s*\( + - type: output-matches + config: + pattern: \)\s*=\s*(await\s+)?\w+\.ReadAllText(Async)?\s*\( + - type: output-not-matches + config: + pattern: string\s+\w+\s*=\s*(await\s+)?\w+\.ReadAllText(Async)?\s*\( + - type: prompt rubric: - - "Uses the new instance method Process.ReadAllText/ReadAllTextAsync" - - "Destructures the (string StandardOutput, string StandardError) tuple — not a single string" - - "Does not fall back to manual StandardOutput.ReadToEndAsync() as the primary mechanism" - - "Targets net11.0" - timeout: 180 + - Uses the new instance method Process.ReadAllText or Process.ReadAllTextAsync + - Destructures the (string StandardOutput, string StandardError) tuple return + - Does not fall back to manual StandardOutput.ReadToEndAsync() as the primary mechanism + - Targets net11.0 - # --- Scenario 4: stream tagged output lines via ReadAllLinesAsync --- - - name: "Stream tagged process output lines in .NET 11" + # --- Stimulus 4: stream tagged output lines via ReadAllLinesAsync --- + - name: Stream tagged process output lines in .NET 11 prompt: | I have a long-running child process in a .NET 11 app. As it runs I want to handle each output line the moment it is produced — not buffer everything to the end — and for each line I need to know whether it came from stdout or stderr. I want the cleanest first-party .NET 11 approach, with no event-callback plumbing and no deadlock risk. Show a minimal program and the `net11.0` `.csproj`. - assertions: - - type: "exit_success" - - type: "output_matches" - pattern: "net11\\.0" - - type: "output_matches" - pattern: "ReadAllLines(Async)?\\s*\\(" - - type: "output_matches" - pattern: "await\\s+foreach" - - type: "output_matches" - pattern: "\\.Content\\b|ProcessOutputLine" - - type: "output_matches" - pattern: "\\.StandardError\\b" - - type: "output_not_matches" - pattern: "OutputDataReceived|BeginOutputReadLine" + graders: + - type: exit-success + - type: output-matches + config: + pattern: net11\.0 + - type: output-matches + config: + pattern: ReadAllLines(Async)?\s*\( + - type: output-matches + config: + pattern: await\s+foreach + - type: output-matches + config: + pattern: \.Content\b|ProcessOutputLine + - type: output-matches + config: + pattern: \.StandardError\b + - type: output-not-matches + config: + pattern: OutputDataReceived|BeginOutputReadLine + - type: prompt rubric: - - "Uses Process.ReadAllLinesAsync consumed with `await foreach` over IAsyncEnumerable" - - "Reads ProcessOutputLine.Content and distinguishes stdout vs stderr via .StandardError" - - "Does NOT use the old OutputDataReceived/BeginOutputReadLine event model; targets net11.0" - timeout: 180 + - Uses Process.ReadAllLinesAsync consumed with `await foreach` over IAsyncEnumerable + - Reads ProcessOutputLine.Content and distinguishes stdout vs stderr via .StandardError + - Does NOT use the old OutputDataReceived/BeginOutputReadLine event model; targets net11.0 - # --- Scenario 5: Negative — skill should NOT activate on .NET 8 --- + # --- Stimulus 5: Fire-and-forget process start --- + - name: Fire-and-forget process start in .NET 11 + prompt: | + In a .NET 11 tool, I need to launch an independent helper process (such as a local browser or background tool) + and immediately move on without holding onto process handles or having to manage its disposal. + I just want to launch it without keeping process handle structures alive and obtain its process ID. + Show me a minimal C# program and the `.csproj` XML targeting `net11.0` for this. + graders: + - type: exit-success + - type: output-matches + config: + pattern: net11\.0 + - type: output-matches + config: + pattern: Process\.StartAndForget\( + - type: prompt + rubric: + - Uses the new Process.StartAndForget method added in .NET 11 + - Targets net11.0 + + # --- Stimulus 6: Whitelist inherited handles for child process --- + - name: Whitelist inherited handles for child process in .NET 11 + prompt: | + In a high-throughput .NET 11 application, I need to spawn child processes while keeping open file handles in the parent. + To avoid leaking file descriptors to the child, I need to explicitly specify which handles get inherited + by the child process using the new ProcessStartInfo handle inheritance features in .NET 11. + Show me a minimal example configuring handle inheritance and the `.csproj` XML targeting `net11.0`. + graders: + - type: exit-success + - type: output-matches + config: + pattern: net11\.0 + - type: output-matches + config: + pattern: InheritedHandles\s*= + - type: prompt + rubric: + - Configures the InheritedHandles property on ProcessStartInfo + - Targets net11.0 + + # --- Stimulus 7: Negative — skill should NOT activate on .NET 8 --- - name: "Non-activation: Running a process on .NET 8" prompt: | I have a .NET 8 console app and I need to start a process 'notepad.exe'. Show me a minimal C# program and the `.csproj` XML targeting `net8.0` that starts this process using the traditional Process.Start. Print both in your response. expect_activation: false - assertions: - - type: "exit_success" - - type: "output_matches" - pattern: "net8\\.0" - - type: "output_matches" - pattern: "Process\\.Start\\(" - - type: "output_not_matches" - pattern: "RunAndCaptureText|ReadAllTextAsync|ReadAllLinesAsync|KillOnParentExit" + graders: + - type: exit-success + - type: output-matches + config: + pattern: net8\.0 + - type: output-matches + config: + pattern: Process\.Start\( + - type: output-not-matches + config: + pattern: RunAndCaptureText|ReadAll(Text|Bytes|Lines)(Async)?|KillOnParentExit|StartAndForget + - type: prompt rubric: - - "Solves the task using standard pre-.NET 11 APIs (Process.Start)" - - "Does NOT load or reference the process-api-net11 skill" - - "Targets net8.0" - timeout: 180 + - Solves the task using standard pre-.NET 11 APIs (Process.Start) + - Does NOT load or reference the process-api-net11 skill + - Targets net8.0 - # --- Scenario 6: subtle non-activation — capture output on .NET 10 (APIs are net11-only) --- + # --- Stimulus 8: Subtle negative — capture output on .NET 10 (APIs are net11-only) --- - name: "Non-activation: capture process output on .NET 10" prompt: | I'm on a `.NET 10` project (`net10.0`). I need to run `git` with the argument `status` and capture its standard output into a string. Show a minimal C# program and the `.csproj` XML targeting `net10.0`, and print both. expect_activation: false - assertions: - - type: "exit_success" - - type: "output_matches" - pattern: "net10\\.0" - - type: "output_not_matches" - pattern: "RunAndCaptureText|ReadAllTextAsync|ReadAllLinesAsync" - - type: "output_matches" - pattern: "RedirectStandardOutput|ReadToEnd" + graders: + - type: exit-success + - type: output-matches + config: + pattern: net10\.0 + - type: output-not-matches + config: + pattern: RunAndCaptureText|ReadAll(Text|Bytes|Lines)(Async)?|KillOnParentExit|StartAndForget + - type: output-matches + config: + pattern: RedirectStandardOutput|ReadToEnd + - type: prompt rubric: - - "Solves the task with pre-.NET 11 APIs (RedirectStandardOutput + ReadToEnd/ReadToEndAsync)" - - "Recognizes the new .NET 11 Process convenience APIs are unavailable on net10.0 and does not use them" - - "Does NOT load the process-api-net11 skill; targets net10.0" - timeout: 180 + - Solves the task with pre-.NET 11 APIs (RedirectStandardOutput + ReadToEnd/ReadToEndAsync) + - Recognizes the new .NET 11 Process convenience APIs are unavailable on net10.0 and does not use them + - Does NOT load the process-api-net11 skill; targets net10.0