Skip to content

Latest commit

 

History

History
383 lines (321 loc) · 28.6 KB

copilot-instructions.md

File metadata and controls

383 lines (321 loc) · 28.6 KB

Copilot Instructions

Project Details

  • .NET SDK pinned in #file:'global.json'
  • Common parameters specified in #file:'Directory.Build.props'
  • Central NuGet package version management – versions go in #file:'Directory.Packages.props', not in .fsproj files
  • Build: dotnet build FSharp.Collections.Immutable.slnx
  • Test: dotnet test FSharp.Collections.Immutable.slnx

Solution Structure

IMPORTANT: Every collection binding implementation file must have a corresponding .fsi signature file. The one exception is the XTypes.fs file of a folder, which holds only type abbreviations and is its own signature: a signature file without an implementation file does not compile (FS0240).

The comparer struct and the extension files of a module carry <DependentUpon> in the project file, the .fsi on the module's .fsi and the .fs on its .fs, so that Visual Studio shows them as a tree under the module.

/
├── build/                                              – FAKE build scripts and release automation
│   └── CheckOrder.fs                                   – checks the category order of the signature files with the Fantomas parser
├── docsSrc/                                            – FSharp.Formatting documentation source
├── src/FSharp.Collections.Immutable/                    – main library
│   ├── Helper/                                         – internal helpers shared by the modules, one root module per file
│   │   ├── ImmutableCollectionUtil.fs                  – argument checks and the wrapping of arrays
│   │   ├── Comparers.fs                                – the default comparer of F# generic comparison
│   │   ├── ErrorStrings.fs                             – the exception messages of FSharp.Core and the .NET libraries
│   │   ├── Raise.fs                                    – the exceptions of the FSharp.Core DetailedExceptions module
│   │   ├── ArrayChecks.fs                              – the checks that the functions of several arrays share
│   │   └── Arrays.fs                                   – the algorithms over arrays that the default and Tuple modules share
│   ├── FlatList/                                       – ImmutableArray API surface and builders
│   │   ├── FlatListTypes.fs                            – the FlatList abbreviation
│   │   ├── FlatList.fs / FlatList.fsi                  – includes the nested Parallel module
│   │   ├── FlatListOption.fs / FlatListOption.fsi      – Option namespace overrides
│   │   ├── FlatListTuple.fs / FlatListTuple.fsi        – Tuple namespace overrides
│   │   ├── FlatListBuilder.fs / FlatListBuilder.fsi
│   │   └── FlatListCE.fs / FlatListCE.fsi
│   ├── IndexedList/                                    – ImmutableList API surface and builders
│   │   ├── IndexedListTypes.fs
│   │   ├── IndexedList.fs / IndexedList.fsi
│   │   ├── IndexedListOption.fs / IndexedListOption.fsi
│   │   ├── IndexedListTuple.fs / IndexedListTuple.fsi
│   │   ├── IIndexedList.fs / IIndexedList.fsi
│   │   ├── IIndexedListOption.fs / IIndexedListOption.fsi
│   │   ├── IIndexedListTuple.fs / IIndexedListTuple.fsi
│   │   ├── IndexedListBuilder.fs / IndexedListBuilder.fsi
│   │   └── IndexedListCE.fs / IndexedListCE.fsi
│   ├── Queue/                                          – ImmutableQueue API surface and CE
│   │   ├── QueueTypes.fs                               – the Queue and IQueue abbreviations
│   │   ├── Queue.fs / Queue.fsi
│   │   ├── QueueOption.fs / QueueOption.fsi
│   │   ├── QueueTuple.fs / QueueTuple.fsi
│   │   ├── IQueue.fs / IQueue.fsi
│   │   ├── IQueueOption.fs / IQueueOption.fsi
│   │   ├── IQueueTuple.fs / IQueueTuple.fsi
│   │   └── QueueCE.fs / QueueCE.fsi
│   ├── Stack/                                          – ImmutableStack API surface and CE
│   │   ├── StackTypes.fs                               – the Stack and IStack abbreviations
│   │   ├── Stack.fs / Stack.fsi
│   │   ├── StackOption.fs / StackOption.fsi
│   │   ├── StackTuple.fs / StackTuple.fsi
│   │   ├── IStack.fs / IStack.fsi
│   │   ├── IStackOption.fs / IStackOption.fsi
│   │   ├── IStackTuple.fs / IStackTuple.fsi
│   │   └── StackCE.fs / StackCE.fsi
│   ├── HashMap/                                        – ImmutableDictionary API surface and CE
│   │   ├── HashMapTypes.fs                             – the HashMap and IMap abbreviations
│   │   ├── HashMap.fs / HashMap.fsi                    – nested Tuple / ValueTuple submodules
│   │   │   ├── HashMap.CustomComparers.fs / .fsi       – creates hash maps with comparers
│   │   │   └── HashMap.Extensions.fs / .fsi            – ofArray / ofList / ofSeq for every binding shape
│   │   ├── HashMapOption.fs / HashMapOption.fsi
│   │   ├── HashMapTuple.fs / HashMapTuple.fsi
│   │   ├── IMap.fs / IMap.fsi
│   │   ├── IMapOption.fs / IMapOption.fsi
│   │   ├── IMapTuple.fs / IMapTuple.fsi
│   │   └── HashMapCE.fs / HashMapCE.fsi
│   ├── SortedMap/                                      – ImmutableSortedDictionary API surface and CE
│   │   ├── SortedMapTypes.fs
│   │   ├── SortedMap.fs / SortedMap.fsi
│   │   │   ├── SortedMap.CustomComparers.fs / .fsi
│   │   │   └── SortedMap.Extensions.fs / .fsi
│   │   ├── SortedMapOption.fs / SortedMapOption.fsi
│   │   ├── SortedMapTuple.fs / SortedMapTuple.fsi
│   │   └── SortedMapCE.fs / SortedMapCE.fsi
│   ├── HashSet/                                        – ImmutableHashSet API surface and CE
│   │   ├── HashSetTypes.fs
│   │   ├── HashSet.fs / HashSet.fsi
│   │   │   └── HashSet.CustomComparer.fs / .fsi        – creates hash sets with a comparer
│   │   ├── HashSetOption.fs / HashSetOption.fsi
│   │   ├── HashSetTuple.fs / HashSetTuple.fsi
│   │   ├── ISet.fs / ISet.fsi
│   │   ├── ISetOption.fs / ISetOption.fsi
│   │   ├── ISetTuple.fs / ISetTuple.fsi
│   │   └── HashSetCE.fs / HashSetCE.fsi
│   ├── SortedSet/                                      – ImmutableSortedSet API surface and CE
│   │   ├── SortedSetTypes.fs
│   │   ├── SortedSet.fs / SortedSet.fsi
│   │   │   └── SortedSet.CustomComparer.fs / .fsi
│   │   ├── SortedSetOption.fs / SortedSetOption.fsi
│   │   ├── SortedSetTuple.fs / SortedSetTuple.fsi
│   │   └── SortedSetCE.fs / SortedSetCE.fsi
│   ├── FrozenMap/                                      – FrozenDictionary API surface and CE
│   │   ├── FrozenMapTypes.fs
│   │   ├── FrozenMap.fs / FrozenMap.fsi
│   │   │   ├── FrozenMap.CustomComparers.fs / .fsi
│   │   │   └── FrozenMap.Extensions.fs / .fsi
│   │   ├── FrozenMapOption.fs / FrozenMapOption.fsi
│   │   ├── FrozenMapTuple.fs / FrozenMapTuple.fsi
│   │   └── FrozenMapCE.fs / FrozenMapCE.fsi
│   ├── FrozenSet/                                      – FrozenSet API surface and CE (the BCL type keeps its name)
│   │   ├── FrozenSet.fs / FrozenSet.fsi
│   │   │   └── FrozenSet.CustomComparer.fs / .fsi
│   │   ├── FrozenSetOption.fs / FrozenSetOption.fsi
│   │   ├── FrozenSetTuple.fs / FrozenSetTuple.fsi
│   │   └── FrozenSetCE.fs / FrozenSetCE.fsi
│   └── ComputationExpressions.fs / .fsi                – the computation expression instances
└── tests/FSharp.Collections.Immutable.Tests/            – MSTest test project
    ├── TestCategories.fs                               – centralized custom test category attributes
    ├── TestCategoriesTests.fs                          – enforces the test category rules
    ├── Contracts/                                      – shared contract bases, inputs and assertion helpers
    └── <Collection>/                                   – ops records, contract classes, collection-specific,
                                                          builder, computation expression, Option and Tuple tests

The modules follow ADR 0001 – Per-collection modules (docsSrc/Explanations/ADR_0001_Collection_Modules.md):

  • Each module groups its functions under banner comments // ---- Category ---- in the order Creation, Builder, Properties, Access, Search, Transformation, Folding, Partitioning and grouping, Sorting, Random, Conversion, Type-specific, and sorts the functions of a category alphabetically (ordinal), active patterns last. The .fs file repeats the order of its .fsi file. build/CheckOrder.fs (FAKE target CheckOrder) enforces it.
  • A file with one module declares it as the root module of the file (module FSharp.Collections.Immutable.FlatList); the type abbreviations of a folder live in its XTypes.fs. No module carries [<CompiledName>].
  • try* functions return voption, tuples are struct tuples. The namespaces FSharp.Collections.Immutable.Option and FSharp.Collections.Immutable.Tuple repeat these functions with option results and reference tuples.
  • No module function creates a collection with comparers: the CustomComparers* (maps) and CustomComparer* (sets) structs do, and the modules create the structs with forComparers, forKeyComparer and forComparer.
  • Maps take bindings in every shape: ofArray, ofList and ofSeq are static extension members of XExtensions, overloaded for KeyValuePair, reference tuples and struct tuples; addRange and setItems take KeyValuePairs, and the nested Tuple and ValueTuple submodules of the module take the tuple shapes.
  • A function throws what the FSharp.Core function of the mirrored module throws for the same input: Array for FlatList, List for the other sequence-like modules, Map and Set for maps and sets.
  • Contract test bases in Contracts/ take a record of the module's functions; a collection derives one test class per capability that it supports. Expected values and exceptions come from FSharp.Core. The categories are attributes on the test classes; test methods carry none.

Libraries in Use

Use GitHub MCP tools for code search in these repositories when needed.

MCP Servers

Agents discover servers from #file:'.mcp.json'; these are only hints on when to prefer one.

  • For F# work, prefer the F# server (FsLangMCP) over rg/plain text search whenever the task depends on symbol meaning, compile context, cross-project usage, diagnostics, or safe refactoring preview. Its tool package is pinned in #file:'.config/dotnet-tools.json' – run dotnet tool restore before first use.
  • Use the GitHub server for code search in dependency repositories. In Claude Code it authenticates through .claude/scripts/github-mcp-headers.ps1, which reuses the GitHub token stored by Git Credential Manager, because the server does not support the OAuth dynamic client registration Claude Code needs.
  • Use the Microsoft Docs server for official Microsoft and Azure documentation.
  • When adding or changing a server, keep servers (VS Code / GitHub Copilot) and mcpServers (Claude Code) in sync and sorted alphabetically by key.

F# Coding Guidelines

Language and Tooling

  • Always use the latest F# 10 features over old syntax.
  • If you are running outside of an IDE, or the IDE does not provide F# semantic tools, use FsLangMCP as the primary tool for F# code navigation, symbol discovery, diagnostics, usage search and refactoring preview. Prefer its semantic tools over plain text search.
  • The compiler generates IsCaseName instance properties (for example IsOk, IsNotFound) for each DU case – use them when a single-case check is needed.
  • Write at most one level of a type in the postfix ML style: int[] seq, int voption[], seq<int list> – never int array seq or int list list. Only the postfix levels count: seq<int array> and struct (int array * int array) have one each.

Asynchrony and Cancellation

  • Prefer task CE over async CE.
  • When a method must return non-generic Task, annotate the return type explicitly: member _.MyMethod (...) : Task = task { ... }. Never cast through pipelines.
  • task CE can await ValueTask APIs directly.
  • Curried functions take the CancellationToken as their first parameter, so that it can be partially applied.
  • Never hand-roll wrappers such as ValueTask (task { ... }), .AsTask () round-trips or manual unit -> Task thunks. Use the matching IcedTasks CE instead (valueTask, valueTaskUnit, taskUnit, coldTask, cancellableTask, cancellableValueTask, backgroundTask, …) and always use the full CE names, not the short aliases (vTask, pvTask, …).

Values and Collections

  • Prefer voption (ValueSome/ValueNone) over option. Fields, members, parameters and values shared between threads included: none of them is a reason to pick option. Exception: when an API hands you 'T option and has no voption counterpart, unwrap it with Option.defaultValue/Option.defaultWith directly – do not insert ValueOption.ofOption just to switch modules.
  • The mirror case, an API that takes 'T option (an optional argument ?name = …, a field typed 'T option): stay in ValueOption through the whole chain and convert once, last – x |> ValueOption.bind _.Value |> ValueOption.toOption, never x |> ValueOption.toOption |> Option.bind _.Value.
  • Prefer struct ('T1 * 'T2) over reference tuples, and anonymous struct records (struct {| ... |}) over tuples for return types of public functions and methods.
  • Never group with Seq.groupBy – use ToLookup from System.Linq. It groups once into an ILookup<'Key, 'T> instead of re-grouping on every enumeration and does not allocate a tuple per group. Pass a lambda (xs.ToLookup (fun x -> keyOf x)), not a bare function value.
  • When casting sequence items use Seq.cast<TargetType> instead of Seq.map (fun item -> item :> TargetType).
  • When concatenating two sequences or lists, prefer seq { yield! xs; yield! ys } (or [ yield! xs; yield! ys ] for lists) over the @ operator or Seq.append.
  • When pipe operators are used on a materializable collection multiple times in a row, prefer Seq module for the chain and materialize at the end.
  • Keep a pipeline in harmony: when consecutive steps can come from one module, take them from that module, the conversion at the end included – xs |> Seq.filter isValid |> Seq.sort |> Seq.toArray, not … |> Seq.sort |> Array.ofSeq, and items |> List.map _.Id |> List.toArray, not … |> Array.ofList. Change the module only where the steps that follow need the other collection.
  • Compare System.Type values with Type.(=) (a, b), the Type.op_Equality operator. The F# = goes through generic equality and is several times slower, and comparing names is both slow and wrong: FullName.StartsWith is culture-sensitive without a StringComparison, and the full name of an array of a generic type, such as FSharpOption`1[[System.Int32, ...]][], starts with the name of the generic type. To recognize a generic type, check t.IsGenericType and compare t.GetGenericTypeDefinition () with its definition, such as typedefof<_ option>, through Type.(=).

Functions, Lambdas and Strings

  • Prefer underscore lambda syntax like Seq.map _.Name over Seq.map (fun x -> x.Name), but only when the expression is a simple member access. Complex expressions like Seq.where (fun x -> x.Name = name) or Seq.map (fun x -> x.Field1, x.Field2) cannot be simplified. A member chain ending in a method call is not complex: _.Changed.Subscribe(handler). The shorthand needs its input type known, so pipe the value in first – value |> ValueOption.map _.Id, not ValueOption.map _.Id value (FS0072). Never write a space in _.MethodCall() – it breaks parsing.
  • Simplify Seq.map (fun x -> someFunction x) to Seq.map someFunction.
  • Prefer interpolated strings over printf functions for string formatting. Format specifiers like $"%s{value}" are valid in interpolated strings and help type inference.
  • Pass an explicit StringComparison to every Equals, StartsWith, EndsWith, IndexOf, Contains and Compare, and an explicit comparer to every HashSet<string> and Dictionary<string, _>. Ordinal by default; culture-sensitive comparison is a decision, never a default. Use OrdinalIgnoreCase only where the thing compared really is case-insensitive.
  • A slice of a string that is only inspected – compared, trimmed, scanned, matched against a prefix – is a ReadOnlySpan<char> (text.AsSpan (start, length)), not a Substring: the substring allocates a copy per call, the span does not. Materialize with Substring/ToString () only for the value that leaves the function or is stored.
  • The StringComparison rule applies to spans unchanged: MemoryExtensions has StringComparison overloads of StartsWith, EndsWith, Equals, CompareTo, IndexOf and Contains for ReadOnlySpan<char> – s.AsSpan().TrimStart(' ').StartsWith("//", StringComparison.Ordinal). The overloads without one are the generic element-wise ReadOnlySpan<'T> ones – ordinal for char by accident, not by statement.
  • A slice that must be stored – kept in a record or class field, captured by a closure or used across a let!/do! in a task CE, put into a tuple, option or collection – is ReadOnlyMemory<char> (text.AsMemory (...)), never a ReadOnlySpan<char>: a byref-like value cannot be stored. Returning a ReadOnlySpan<char> sliced from a string or from a span parameter is fine.
  • To compare two slices without building either, use String.CompareOrdinal (a, aIndex, b, bIndex, length) or spanA.Equals (spanB, StringComparison.Ordinal).

Nullable Reference Types

  • Declare variables non-nullable; check for null at entry points only.

  • Trust the SDK null annotations – do not add null checks when the type system says a value cannot be null.

  • Use withNull for null checks instead of boxing delegates/functions (avoid isNull (box value)).

  • Prefer match on null over if isNull – it narrows the type and suppresses nullness warnings:

    // Preferred
    match someObject with
    | null -> ()
    | someObject -> someObject.SomeProperty
  • Before suppressing a nullness warning (3261, 3262, …), exhaust these alternatives in order:

    1. nonNull value – asserts non-null at runtime and fails fast.
    2. Unchecked.nonNull value – skips the runtime check; only when non-null was already verified upstream.
    3. An inline #nowarn / #warnon pair around the smallest possible scope – last resort for interop boundaries, centralised in a single helper rather than scattered across call sites.
  • Never suppress warnings file-wide; #nowarn and #warnon are valid anywhere in a file.

XML Documentation Comments

  • A doc comment is either plain text with no tags at all, or fully explicit XML starting with <summary> – never a mixture. The compiler adds <summary> by itself only when the comment has no tags, and silently escapes tags otherwise:

    • No tags anywhere → bare /// lines, no <summary>.
    • Any tag at all (<see/>, <c/>, <para>, <param>, <returns>, …) → the comment must start with an explicit <summary>, and every <para> must be inside it.
    // ✅ plain text — the compiler supplies <summary>
    /// Represents the result of a read operation.
    
    // ❌ a sibling tag without <summary> — the <param> is escaped into the summary and lost
    /// Reads an item.
    /// <param name="id">Item Id</param>
    
    // ✅ any tag present, so the comment is explicit XML throughout
    /// <summary>Reads an item.</summary>
    /// <param name="id">Item Id</param>
  • Keep <summary> and <remarks> on one line each only when both are short – up to 60 characters of text each. When either is longer, write both over several lines, with each opening and closing tag on a line of its own:

    /// <summary>Returns the number of elements.</summary>
    /// <remarks>This is an O(1) operation.</remarks>
    
    /// <summary>
    /// The inputs of the set contracts as rows of <c>DynamicData</c>: none, one and several elements.
    /// </summary>
    /// <remarks>
    /// The property builds new arrays on each read, so that no test sees a row that another one changed.
    /// </remarks>
  • Every public API must have XML documentation.

  • On an explicit interface implementation (interface X with member _.M (...) = ...), write /// <inheritdoc /> alone instead of restating the interface member's documentation, unless this implementation has behavior worth calling out beyond what the interface already documents – write a normal <summary>/<remarks> there instead.

  • Refer to types and members through <see cref="Type.Member"/>, never through <c> or plain text. <c> is for literal values only (JSON, SQL, setting names). Refer to language keywords through <see langword="null"/>.

  • Split multi-paragraph documentation into <para> elements inside <summary> – bare line breaks are collapsed by documentation renderers.

  • Write enumerations as <list type="bullet"> (or type="number") with <item><description>…</description></item>, never as Markdown-style bullets.

Opens Sorting

Sort open statements alphabetically within groups: System first, Microsoft second, FSharp third; then other external namespaces; then this solution's namespaces. open type goes last in each group. Type and module aliases form a separate final group.

Class Constructors

This is how to define a non-default F# class constructor:

type DerivedClass =
    inherit BaseClass

    new (``arguments here``) as ``created object``
        =
        // create any objects used in the base class constructor
        let fieldValue = ""
        {
            inherit
                BaseClass (``arguments here``)
        }
        then
            ``created object``.otherField <- fieldValue

    [<DefaultValue>]
    val mutable otherField : FieldType

Class Instantiation

Always prefer F# class initializers over property assignment! You absolutely must use F# class initializers instead of property assignment!

Class declaration:

type MyClass (someConstructorParam : string) =
    member ReadOnlyProperty = someConstructorParam

    member val MutableProperty1 = "" with get, set
    member val MutableProperty2 = "" with get, set

Wrong:

let myClass = MyClass("some value")
myClass.MutableProperty1 <- "new value"
myClass.MutableProperty2 <- "new value"

Right:

let myClass =
    MyClass(
        // constructor parameters go first without names
        "some value",
        // then mutable properties go next with names
        MutableProperty1 = "new value",
        MutableProperty2 =
            // operations must be placed into parentheses
            (5 |> string)
    )

C#-Consumable Extension Members

// AutoOpen makes the module automatically available without an explicit open statement
// Extension makes the members visible to C#
[<AutoOpen; Extension>]
module MyTypeExtensions =

    type MyType with

        // Extension is visible to C#
        // CompiledName makes the method name friendly to C#
        [<Extension; CompiledName "ExtensionMethod">]
        member this.ExtensionMethod (param1 : string) : ReturnType =
            ()

Naming Conventions

  • Use PascalCase for modules, types, and public members.
  • Use camelCase for let bindings, functions, private fields, and local variables.
  • Prefix interface names with I.
  • Do not prefix type parameters with T (e.g., use 'Result instead of 'TResult).
  • Name tests using spaces (e.g., member this.``Test name with spaces``() : Task = ...).

Testing

  • Tests use MSTest 4.
  • Collections in tests are arrays: build and inspect them with toArray and [| ... |], and compare them with CollectionAssert.AreEqual. Arrays are faster to build than F# lists, and CollectionAssert compares them element by element, nested arrays included; it cannot work with F# lists. Convert to a list (toList, List.ofSeq) only to pattern-match on it or to call the List function that the tested function mirrors; a list that is an input of the tested function (ofList, a list given to ofSeq) is a case of its own.
    • Never compare arrays with Assert.AreEqual – it compares references.
    • CollectionAssert compares tuple elements with Equals, so an array inside a tuple is compared by reference; check such results with Unquote, where = compares the array structurally.
  • Give the cases of a test as data rows and parameters of the test method instead of a loop over a list of cases inside the test: [<DataRow>] for constants ([<TestMethod; DataRow 0; DataRow -1>]), [<DynamicData>] for rows that are computed or shared by several tests. Each row runs and reports as a separate test named after its arguments: a failing case does not stop the others, and the message does not need to repeat the input.
    • A DynamicData source is a public static property that builds its rows on each read (static member Rows = ..., not static member val, whose rows every class that inherits the test would share), and a test never changes its row.
    • typeof<_> cannot name an F# module, so the sources that several test classes share go into a static class ([<AbstractClass; Sealed>]): DynamicData (nameof TestData.Rows, typeof<TestData>). The sources of one class, a generic contract class included, are its own static members: DynamicData (nameof MyContract<'C>.Rows).
    • A row is a single value, a tuple, which is unpacked into the parameters of the method, or a TestDataRow<'T>. Nested loops become one source: seq { for index in ... do for count in ... do struct (index, count) }.
    • MSTest names a row by the ToString of its values, so a row holds printable data – numbers, strings, arrays of them, F# union cases – and the test builds the collections and picks the functions from it. A collection or a function in a row gives a name like ImmutableHashSet`1[System.String].
    • Wrap a row that is an array of a reference type (string array, an array of reference tuples) in TestDataRow: MSTest spreads such an array into the parameters, as it does an obj array.
    • Do not set TestDataRow.DisplayName: it replaces the whole name of the test, the name of the method included.
  • StringAssert has overloads with StringComparison.
  • Use Assert.Contains instead of Assert.IsTrue (str.Contains ..., "message"), and do not put the actual value into the message.
  • Check collection size with Assert.HasCount (expected, collection, "message") instead of Assert.AreEqual (expected, collection.Length, "message") (or .Count / Seq.length); use Assert.IsEmpty, Assert.IsNotEmpty and Assert.ContainsSingle for the zero, non-zero and single-item cases. On failure they report the actual count and items.
  • Try running tests with --no-build first, run them individually where possible, and use the trx format for results so failures can be consumed and fixed.
  • Use Unquote only for complex object/hierarchy assertions; for simple scalar checks prefer standard Assert.* APIs.
  • Every Assert.* call must include a failure message so output is self-explanatory.
  • Async tests must return Task, not Async or Task<unit> – always declare ) : Task = task {.

General

  • Make only high-confidence suggestions when reviewing code changes.
  • Write code with good maintainability practices, including comments on why certain design decisions were made.
  • Handle edge cases and write clear exception handling.
  • Never duplicate code unless explicitly allowed.
  • All comments, documentation, README files, and markdown files must be written in English only.