From 208fac3ce6ff1faecb3fd51e1ce555b062f953f3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 10 Mar 2026 02:47:17 +0000
Subject: [PATCH 1/2] Initial plan
From dd6dcf70d0b27b7ec59f55da40fdf1bc3e6723e1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 10 Mar 2026 02:52:43 +0000
Subject: [PATCH 2/2] Create dotnet-fsharp plugin with fsharp-getting-started
skill
Co-authored-by: ManishJayaswal <9527491+ManishJayaswal@users.noreply.github.com>
---
.github/plugin/marketplace.json | 5 +
plugins/dotnet-fsharp/plugin.json | 6 +
.../skills/fsharp-getting-started/SKILL.md | 191 ++++++++++++++++++
.../fsharp-getting-started/eval.yaml | 38 ++++
4 files changed, 240 insertions(+)
create mode 100644 plugins/dotnet-fsharp/plugin.json
create mode 100644 plugins/dotnet-fsharp/skills/fsharp-getting-started/SKILL.md
create mode 100644 tests/dotnet-fsharp/fsharp-getting-started/eval.yaml
diff --git a/.github/plugin/marketplace.json b/.github/plugin/marketplace.json
index 02a140fe07..7e58b4dde2 100644
--- a/.github/plugin/marketplace.json
+++ b/.github/plugin/marketplace.json
@@ -33,6 +33,11 @@
"name": "dotnet-maui",
"source": "./plugins/dotnet-maui",
"description": "Skills for .NET MAUI development: environment setup, diagnostics, and troubleshooting."
+ },
+ {
+ "name": "dotnet-fsharp",
+ "source": "./plugins/dotnet-fsharp",
+ "description": "Skills for F# development: creating idiomatic F# projects, working with discriminated unions, records, pattern matching, and integrating F# with the .NET ecosystem."
}
]
}
diff --git a/plugins/dotnet-fsharp/plugin.json b/plugins/dotnet-fsharp/plugin.json
new file mode 100644
index 0000000000..0773c693ee
--- /dev/null
+++ b/plugins/dotnet-fsharp/plugin.json
@@ -0,0 +1,6 @@
+{
+ "name": "dotnet-fsharp",
+ "version": "0.1.0",
+ "description": "F# development skills for creating idiomatic F# code, working with F# types, and integrating F# with the .NET ecosystem.",
+ "skills": "./skills/"
+}
diff --git a/plugins/dotnet-fsharp/skills/fsharp-getting-started/SKILL.md b/plugins/dotnet-fsharp/skills/fsharp-getting-started/SKILL.md
new file mode 100644
index 0000000000..87b25b449c
--- /dev/null
+++ b/plugins/dotnet-fsharp/skills/fsharp-getting-started/SKILL.md
@@ -0,0 +1,191 @@
+---
+name: fsharp-getting-started
+description: >
+ Create and work with F# projects in .NET: project setup, idiomatic types, and core language patterns.
+ USE FOR: creating new F# console apps, libraries, or test projects with `dotnet new`; writing
+ discriminated unions, records, pattern matching, pipe operators, and async computation expressions;
+ understanding F# project structure and module conventions; choosing between F# and C# for a given task.
+ DO NOT USE FOR: C#-only projects, advanced F# type providers, or MSBuild/build pipeline configuration.
+---
+
+# Getting Started with F#
+
+Get up and running with F# on .NET: create a project, understand the key language constructs, and write
+idiomatic functional code from day one.
+
+## When to Use
+
+- User wants to create a new F# console app, library, or test project
+- User is learning F# and needs guidance on idiomatic patterns
+- User wants to use discriminated unions, records, active patterns, or computation expressions
+- User needs to understand how F# modules, namespaces, and files are organized
+- User is evaluating whether F# is a good fit for their use case
+
+## When Not to Use
+
+- The project is already C# and the user wants C#-specific guidance
+- The user needs advanced F# type providers (requires a dedicated skill)
+- The issue is a build or MSBuild problem (use dotnet-msbuild skills)
+- The user wants F#-to-C# interoperability patterns (consider a dedicated interop skill)
+
+## Inputs
+
+| Input | Required | Description |
+|-------|----------|-------------|
+| Project type | Yes | console, classlib, mstest, nunit, or xunit |
+| Target framework | No | Defaults to the current LTS (e.g., `net9.0`) |
+| Project name | No | Kebab-case name for the new project |
+
+## Workflow
+
+### Step 1: Create the F# project
+
+Use `dotnet new` with the `-lang F#` flag:
+
+```bash
+# Console application
+dotnet new console -lang F# -n MyApp -o ./MyApp
+
+# Class library
+dotnet new classlib -lang F# -n MyLib -o ./MyLib
+
+# xUnit test project
+dotnet new xunit -lang F# -n MyApp.Tests -o ./MyApp.Tests
+```
+
+For a solution with multiple projects:
+
+```bash
+dotnet new sln -n MyApp
+dotnet sln add ./MyApp/MyApp.fsproj
+dotnet sln add ./MyApp.Tests/MyApp.Tests.fsproj
+```
+
+### Step 2: Understand the project structure
+
+An F# project (`.fsproj`) lists source files **in compilation order** — unlike C#, the order matters:
+
+```xml
+
+
+ Exe
+ net9.0
+
+
+
+
+
+
+
+
+
+```
+
+### Step 3: Write idiomatic F# types
+
+Prefer F# records and discriminated unions over classes:
+
+```fsharp
+// Immutable record — use instead of a data-only class
+type Customer = {
+ Id: int
+ Name: string
+ Email: string
+}
+
+// Discriminated union — model a fixed set of alternatives
+type Shape =
+ | Circle of radius: float
+ | Rectangle of width: float * height: float
+ | Triangle of base': float * height: float
+
+// Compute area with exhaustive pattern matching
+let area shape =
+ match shape with
+ | Circle r -> System.Math.PI * r * r
+ | Rectangle(w, h) -> w * h
+ | Triangle(b, h) -> 0.5 * b * h
+```
+
+### Step 4: Use the pipe operator for readable data transformations
+
+Chain operations left-to-right with `|>` instead of nesting calls:
+
+```fsharp
+// Idiomatic F#: data flows through a pipeline
+let result =
+ [1..20]
+ |> List.filter (fun x -> x % 2 = 0)
+ |> List.map (fun x -> x * x)
+ |> List.sum // 1540
+
+// Equivalent nested form (avoid in F#):
+// List.sum (List.map (fun x -> x * x) (List.filter (fun x -> x % 2 = 0) [1..20]))
+```
+
+### Step 5: Handle optional values with `option` and `Result`
+
+Avoid nulls — use F# option types and result types instead:
+
+```fsharp
+// option<'T> — value may or may not exist
+let tryFindById (id: int) (customers: Customer list) : Customer option =
+ customers |> List.tryFind (fun c -> c.Id = id)
+
+// Result<'T, 'TError> — success or structured failure
+let validateEmail (email: string) : Result =
+ if email.Contains('@') then Ok email
+ else Error $"Invalid email: {email}"
+
+// Compose with pattern matching
+let processCustomer id customers =
+ match tryFindById id customers with
+ | None -> printfn "Customer %d not found" id
+ | Some customer ->
+ match validateEmail customer.Email with
+ | Error msg -> printfn "Validation failed: %s" msg
+ | Ok email -> printfn "Processing %s <%s>" customer.Name email
+```
+
+### Step 6: Write async code with computation expressions
+
+Use `async { }` for .NET async work (or `task { }` for direct `Task` interop):
+
+```fsharp
+open System.Net.Http
+
+// async workflow — F#-native; use Async.RunSynchronously or pipe to Async.StartAsTask
+let fetchAsync (url: string) =
+ async {
+ use client = new HttpClient()
+ let! response = client.GetStringAsync(url) |> Async.AwaitTask
+ return response.Length
+ }
+
+// task workflow — directly returns Task, easier for .NET interop
+let fetchTask (url: string) =
+ task {
+ use client = new HttpClient()
+ let! response = client.GetStringAsync(url)
+ return response.Length
+ }
+```
+
+## Validation
+
+- [ ] Project builds with `dotnet build` without errors or warnings
+- [ ] Core types (records, DUs) are defined before they are used in the file list
+- [ ] Pattern matching is exhaustive — the compiler warns on missing cases
+- [ ] No mutable state or nulls introduced without explicit intent (`let mutable`, `Nullable`)
+- [ ] Tests pass with `dotnet test`
+
+## Common Pitfalls
+
+| Pitfall | Solution |
+|---------|----------|
+| File order in `.fsproj` is wrong | F# compiles top-to-bottom; move the defining file above the using file |
+| Using `null` instead of `option` | Use `option<'T>` and pattern match; reserve `null` only for .NET interop |
+| Shadowing a binding with `let` | Intentional in F#, but rename to avoid confusion when it is accidental |
+| Mixing `async` and `task` workflows | Pick one style per project; prefer `task { }` when calling .NET APIs |
+| Forgetting `rec` on recursive functions | Add `let rec` (or `let rec … and …` for mutually recursive functions) |
+| Class-heavy design from C# habits | Prefer modules + functions + records/DUs over classes in F# |
diff --git a/tests/dotnet-fsharp/fsharp-getting-started/eval.yaml b/tests/dotnet-fsharp/fsharp-getting-started/eval.yaml
new file mode 100644
index 0000000000..9809751809
--- /dev/null
+++ b/tests/dotnet-fsharp/fsharp-getting-started/eval.yaml
@@ -0,0 +1,38 @@
+scenarios:
+ - name: "Create an F# console app with discriminated unions and pattern matching"
+ prompt: |
+ I want to create a new F# console app called ShapeCalculator that models geometric shapes and
+ computes their areas. It should use discriminated unions for the shape types and pattern matching
+ to compute the area. Show me the project structure and the code.
+ assertions:
+ - type: output_contains
+ value: "dotnet new console"
+ - type: output_matches
+ pattern: "-lang\\s+F#"
+ - type: output_contains
+ value: "discriminated union"
+ - type: output_matches
+ pattern: "type Shape"
+ - type: output_matches
+ pattern: "match\\s+shape\\s+with"
+ rubric:
+ - "Creates the project using `dotnet new console -lang F#`"
+ - "Defines a Shape discriminated union with at least two cases (e.g., Circle, Rectangle)"
+ - "Uses exhaustive pattern matching to compute area for each shape case"
+ - "Code is idiomatic F#: uses records or DUs, not classes; uses pipe operator where appropriate"
+ timeout: 60
+
+ - name: "Explain F# file compilation order"
+ prompt: |
+ Why does my F# project fail to compile with "The value or constructor 'Customer' is not defined"
+ even though Customer is defined in Domain.fs and used in Services.fs?
+ assertions:
+ - type: output_matches
+ pattern: "(?i)compilation order|compile order|order.*matter|fsproj"
+ - type: output_matches
+ pattern: "(?i)Domain\\.fs.*before|above.*Services\\.fs|order.*fsproj"
+ rubric:
+ - "Explains that F# compiles files in the order they appear in the .fsproj file"
+ - "Instructs the user to place Domain.fs before Services.fs in the item group"
+ - "Does NOT suggest the fix is to add `open` or change namespace"
+ timeout: 60