From f8b914acf692f30fa4de0d0e55ba285bc4e33177 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 2 Mar 2026 20:35:32 +0000
Subject: [PATCH 1/5] Initial plan
From 2c8380054068bed8e2470e88451f3886261d98b7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 2 Mar 2026 21:02:19 +0000
Subject: [PATCH 2/5] Improve template: API, tests, docs, and fix workflow/doc
inconsistencies
Code improvements:
- Add DefaultPrefix constant and Prefix property to DemoClass
- Strengthen argument validation to reject empty strings (ThrowIfNullOrEmpty)
- Add XML documentation for all new members with literate comments
- Add tests for DefaultPrefix, Prefix property, and empty-string validation
- Update requirements.yaml with TMPL-REQ-008 and TMPL-REQ-009
- Update user guide with DefaultPrefix, Prefix, and exception documentation
- Fix README to mention .NET Standard 2.0 target framework
Consistency fixes (repo-consistency-agent review):
- Upgrade actions/upload-artifact from v7 to v8 (matches download-artifact@v8)
- Fix CONTRIBUTING.md: encoding is UTF-8 (not UTF-8 with BOM)
- Fix CONTRIBUTING.md: Assert.HasCount(expectedCount, collection) argument order
- Fix AGENTS.md: encoding is UTF-8 (not UTF-8+BOM)
---
.github/workflows/build.yaml | 12 +-
AGENTS.md | 2 +-
CONTRIBUTING.md | 4 +-
README.md | 2 +-
docs/guide/guide.md | 26 +++-
requirements.yaml | 24 ++++
src/TemplateDotNetLibrary/DemoClass.cs | 49 ++++++-
.../DemoClassTests.cs | 121 ++++++++++++++++--
8 files changed, 211 insertions(+), 29 deletions(-)
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 0d94e19..2e03ee9 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -38,7 +38,7 @@ jobs:
echo "✓ Tool versions captured"
- name: Upload version capture
- uses: actions/upload-artifact@v7
+ uses: actions/upload-artifact@v8
with:
name: version-capture-quality
path: versionmark-quality.json
@@ -155,19 +155,19 @@ jobs:
echo "✓ Tool versions captured"
- name: Upload version capture
- uses: actions/upload-artifact@v7
+ uses: actions/upload-artifact@v8
with:
name: version-capture-${{ matrix.os }}
path: versionmark-build-*.json
- name: Upload Test Results
- uses: actions/upload-artifact@v7
+ uses: actions/upload-artifact@v8
with:
name: test-results-${{ matrix.os }}
path: test-results/*.trx
- name: Upload Artifacts
- uses: actions/upload-artifact@v7
+ uses: actions/upload-artifact@v8
with:
name: artifacts-${{ matrix.os }}
path: |
@@ -229,7 +229,7 @@ jobs:
upload: false
- name: Upload CodeQL SARIF
- uses: actions/upload-artifact@v7
+ uses: actions/upload-artifact@v8
with:
name: codeql-sarif
path: sarif-results/csharp.sarif
@@ -495,7 +495,7 @@ jobs:
# Downstream projects: Add any additional artifact uploads here.
- name: Upload documentation
- uses: actions/upload-artifact@v7
+ uses: actions/upload-artifact@v8
with:
name: documents
path: |
diff --git a/AGENTS.md b/AGENTS.md
index 4506302..f54c4f6 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -19,7 +19,7 @@ implementation demonstrating best practices for DEMA Consulting .NET libraries.
## Key Files
- **`requirements.yaml`** - All requirements with test linkage (enforced via `dotnet reqstream --enforce`)
-- **`.editorconfig`** - Code style (file-scoped namespaces, 4-space indent, UTF-8+BOM, LF endings)
+- **`.editorconfig`** - Code style (file-scoped namespaces, 4-space indent, UTF-8, LF endings)
- **`.cspell.json`, `.markdownlint-cli2.jsonc`, `.yamllint.yaml`** - Linting configs
## Requirements
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ef6e8f2..6062608 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -95,7 +95,7 @@ This project enforces code style through `.editorconfig`. Key requirements:
- **Indentation**: 4 spaces for C#, 2 spaces for YAML/JSON/XML
- **Line Endings**: LF (Unix-style)
-- **Encoding**: UTF-8 with BOM
+- **Encoding**: UTF-8
- **Namespaces**: Use file-scoped namespace declarations
- **Braces**: Required for all control statements
- **Naming Conventions**:
@@ -142,7 +142,7 @@ Examples:
- Write tests that are clear and focused
- Use modern MSTest v4 assertions:
- - `Assert.HasCount(collection, expectedCount)`
+ - `Assert.HasCount(expectedCount, collection)`
- `Assert.IsEmpty(collection)`
- `Assert.DoesNotContain(item, collection)`
- Always clean up resources (use `try/finally` for console redirection)
diff --git a/README.md b/README.md
index 92e91c7..51c802e 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ This template demonstrates:
- **Simple Library Structure**: Demo class with example methods
- **Multi-Platform Support**: Builds and runs on Windows and Linux
-- **Multi-Runtime Support**: Targets .NET 8, 9, and 10
+- **Multi-Runtime Support**: Targets .NET Standard 2.0, .NET 8, 9, and 10
- **MSTest V4**: Modern unit testing with MSTest framework version 4
- **Comprehensive CI/CD**: GitHub Actions workflows with quality checks and builds
- **Documentation Generation**: Automated build notes, user guide, code quality reports,
diff --git a/docs/guide/guide.md b/docs/guide/guide.md
index 71dbf6d..cb199f8 100644
--- a/docs/guide/guide.md
+++ b/docs/guide/guide.md
@@ -39,6 +39,16 @@ Console.WriteLine(result); // Output: Hello, World!
The `DemoClass` provides demonstration functionality for the template library.
+#### Constants
+
+##### DefaultPrefix
+
+```csharp
+public const string DefaultPrefix = "Hello";
+```
+
+The greeting prefix used when no custom prefix is specified.
+
#### Constructors
##### DemoClass()
@@ -59,11 +69,22 @@ Initializes a new instance of the `DemoClass` class with a custom prefix.
**Parameters:**
-- `prefix` (string): The prefix to use in greetings. Must not be null.
+- `prefix` (string): The prefix to use in greetings. Must not be null or empty.
**Exceptions:**
- `ArgumentNullException`: Thrown when `prefix` is null.
+- `ArgumentException`: Thrown when `prefix` is an empty string.
+
+#### Properties
+
+##### Prefix
+
+```csharp
+public string Prefix { get; }
+```
+
+Gets the greeting prefix used by this instance.
#### Methods
@@ -77,7 +98,7 @@ Returns a greeting message for the specified name.
**Parameters:**
-- `name` (string): The name to greet. Must not be null.
+- `name` (string): The name to greet. Must not be null or empty.
**Returns:**
@@ -86,6 +107,7 @@ A string containing the greeting message in the format "{prefix}, {name}!".
**Exceptions:**
- `ArgumentNullException`: Thrown when `name` is null.
+- `ArgumentException`: Thrown when `name` is an empty string.
**Example:**
diff --git a/requirements.yaml b/requirements.yaml
index c205cce..1ed0922 100644
--- a/requirements.yaml
+++ b/requirements.yaml
@@ -21,6 +21,30 @@ sections:
.NET library best practices for DEMA Consulting libraries.
tests:
- DemoMethod_ReturnsGreeting_WithDefaultPrefix
+ - DemoMethod_ReturnsGreeting_WithCustomPrefix
+
+ - id: TMPL-REQ-008
+ title: The library shall support a customizable greeting prefix.
+ justification: |
+ Consumers may need to produce greetings with a prefix other than the
+ default, so the library must allow the prefix to be specified at
+ construction time.
+ tests:
+ - DemoMethod_ReturnsGreeting_WithCustomPrefix
+
+ - id: TMPL-REQ-009
+ title: The library shall reject null and empty arguments with an appropriate ArgumentException.
+ justification: |
+ Null-safety and non-empty validation are fundamental API contracts: passing null or an
+ empty string to the constructor or to DemoMethod is a programming error. Throwing
+ ArgumentNullException for null arguments and ArgumentException for empty-string arguments
+ immediately surfaces the mistake to callers and prevents malformed output (such as
+ ", World!" or "Hello, !") and obscure failures deeper in the call stack.
+ tests:
+ - DemoMethod_ThrowsArgumentNullException_ForNullInput
+ - DemoMethod_ThrowsArgumentException_ForEmptyInput
+ - Constructor_ThrowsArgumentNullException_ForNullPrefix
+ - Constructor_ThrowsArgumentException_ForEmptyPrefix
- title: Platform Support
requirements:
diff --git a/src/TemplateDotNetLibrary/DemoClass.cs b/src/TemplateDotNetLibrary/DemoClass.cs
index 0035a85..3dd0524 100644
--- a/src/TemplateDotNetLibrary/DemoClass.cs
+++ b/src/TemplateDotNetLibrary/DemoClass.cs
@@ -5,13 +5,24 @@ namespace TemplateDotNetLibrary;
///
public class DemoClass
{
+ ///
+ /// The greeting prefix used when no custom prefix is specified.
+ ///
+ public const string DefaultPrefix = "Hello";
+
+ ///
+ /// The prefix prepended to every greeting produced by this instance.
+ ///
private readonly string _prefix;
///
- /// Initializes a new instance of the class with default prefix.
+ /// Initializes a new instance of the class with the default prefix.
///
+ ///
+ /// The prefix is set to .
+ ///
public DemoClass()
- : this("Hello")
+ : this(DefaultPrefix)
{
}
@@ -19,20 +30,44 @@ public DemoClass()
/// Initializes a new instance of the class with a custom prefix.
///
/// The prefix to use in greetings.
+ ///
+ /// Thrown when is .
+ ///
+ ///
+ /// Thrown when is an empty string.
+ ///
public DemoClass(string prefix)
{
- ArgumentNullException.ThrowIfNull(prefix);
+ // Validate that the prefix is non-null and non-empty before storing it
+ ArgumentException.ThrowIfNullOrEmpty(prefix);
_prefix = prefix;
}
///
- /// Demo method that returns a greeting message.
+ /// Gets the greeting prefix used by this instance.
///
- /// The name to greet.
- /// A greeting message.
+ public string Prefix => _prefix;
+
+ ///
+ /// Returns a greeting message that combines the instance prefix with the given name.
+ ///
+ /// The name to include in the greeting.
+ ///
+ /// A greeting string in the format {prefix}, {name}!,
+ /// for example Hello, World!.
+ ///
+ ///
+ /// Thrown when is .
+ ///
+ ///
+ /// Thrown when is an empty string.
+ ///
public string DemoMethod(string name)
{
- ArgumentNullException.ThrowIfNull(name);
+ // Validate that the name is non-null and non-empty before building the greeting
+ ArgumentException.ThrowIfNullOrEmpty(name);
+
+ // Combine the prefix and name into the standard greeting format
return $"{_prefix}, {name}!";
}
}
diff --git a/test/TemplateDotNetLibrary.Tests/DemoClassTests.cs b/test/TemplateDotNetLibrary.Tests/DemoClassTests.cs
index 389b915..0a00c64 100644
--- a/test/TemplateDotNetLibrary.Tests/DemoClassTests.cs
+++ b/test/TemplateDotNetLibrary.Tests/DemoClassTests.cs
@@ -6,8 +6,13 @@ namespace TemplateDotNetLibrary.Tests;
[TestClass]
public class DemoClassTests
{
+ // -------------------------------------------------------------------------
+ // DemoMethod – happy-path greeting output
+ // -------------------------------------------------------------------------
+
///
- /// Test that DemoMethod returns the expected greeting with default prefix.
+ /// Proves that DemoMethod produces the expected "{prefix}, {name}!" format
+ /// when the default constructor is used.
///
[TestMethod]
public void DemoMethod_ReturnsGreeting_WithDefaultPrefix()
@@ -19,12 +24,13 @@ public void DemoMethod_ReturnsGreeting_WithDefaultPrefix()
// Act
var result = demo.DemoMethod(name);
- // Assert
+ // Assert – greeting must be exactly "Hello, World!"
Assert.AreEqual("Hello, World!", result);
}
///
- /// Test that DemoMethod returns the expected greeting with custom prefix.
+ /// Proves that DemoMethod uses the custom prefix supplied at construction
+ /// time instead of the default.
///
[TestMethod]
public void DemoMethod_ReturnsGreeting_WithCustomPrefix()
@@ -36,12 +42,17 @@ public void DemoMethod_ReturnsGreeting_WithCustomPrefix()
// Act
var result = demo.DemoMethod(name);
- // Assert
+ // Assert – greeting must use the custom prefix "Hi"
Assert.AreEqual("Hi, Alice!", result);
}
+ // -------------------------------------------------------------------------
+ // DemoMethod – argument validation
+ // -------------------------------------------------------------------------
+
///
- /// Test that DemoMethod throws ArgumentNullException for null input.
+ /// Proves that DemoMethod throws ArgumentNullException (not a base
+ /// ArgumentException) when a null name is supplied.
///
[TestMethod]
public void DemoMethod_ThrowsArgumentNullException_ForNullInput()
@@ -49,17 +60,107 @@ public void DemoMethod_ThrowsArgumentNullException_ForNullInput()
// Arrange
var demo = new DemoClass();
- // Act & Assert
- _ = Assert.Throws(() => demo.DemoMethod(null!));
+ // Act & Assert – exact exception type proves null is explicitly rejected
+ Assert.ThrowsExactly(() => demo.DemoMethod(null!));
}
///
- /// Test that constructor throws ArgumentNullException for null prefix.
+ /// Proves that DemoMethod throws ArgumentException (not ArgumentNullException)
+ /// when an empty string name is supplied.
+ ///
+ [TestMethod]
+ public void DemoMethod_ThrowsArgumentException_ForEmptyInput()
+ {
+ // Arrange
+ var demo = new DemoClass();
+
+ // Act & Assert – ArgumentException (not the null sub-type) must be thrown
+ Assert.ThrowsExactly(() => demo.DemoMethod(string.Empty));
+ }
+
+ // -------------------------------------------------------------------------
+ // Constructor – argument validation
+ // -------------------------------------------------------------------------
+
+ ///
+ /// Proves that the custom-prefix constructor throws ArgumentNullException
+ /// (not a base ArgumentException) when a null prefix is supplied.
///
[TestMethod]
public void Constructor_ThrowsArgumentNullException_ForNullPrefix()
{
- // Act & Assert
- _ = Assert.Throws(() => new DemoClass(null!));
+ // Act & Assert – exact exception type proves null is explicitly rejected
+ Assert.ThrowsExactly(() => new DemoClass(null!));
+ }
+
+ ///
+ /// Proves that the custom-prefix constructor throws ArgumentException
+ /// (not ArgumentNullException) when an empty string prefix is supplied.
+ ///
+ [TestMethod]
+ public void Constructor_ThrowsArgumentException_ForEmptyPrefix()
+ {
+ // Act & Assert – ArgumentException (not the null sub-type) must be thrown
+ Assert.ThrowsExactly(() => new DemoClass(string.Empty));
+ }
+
+ // -------------------------------------------------------------------------
+ // DefaultPrefix constant
+ // -------------------------------------------------------------------------
+
+ ///
+ /// Proves that the DefaultPrefix constant exposes the value "Hello",
+ /// which is the expected default greeting prefix.
+ ///
+ [TestMethod]
+ public void DemoClass_DefaultPrefix_IsHello()
+ {
+ // Arrange
+ const string expected = "Hello";
+
+ // Act – read the public constant directly
+ var actual = DemoClass.DefaultPrefix;
+
+ // Assert – constant value must not silently change
+ Assert.AreEqual(expected, actual);
+ }
+
+ // -------------------------------------------------------------------------
+ // Prefix property
+ // -------------------------------------------------------------------------
+
+ ///
+ /// Proves that the Prefix property returns the custom value provided to
+ /// the constructor, confirming the property reflects what was stored.
+ ///
+ [TestMethod]
+ public void DemoClass_Prefix_ReturnsCustomPrefix()
+ {
+ // Arrange
+ const string customPrefix = "Greetings";
+ var demo = new DemoClass(customPrefix);
+
+ // Act
+ var actual = demo.Prefix;
+
+ // Assert – Prefix must exactly match the value passed at construction
+ Assert.AreEqual(customPrefix, actual);
+ }
+
+ ///
+ /// Proves that the default constructor stores DefaultPrefix in the Prefix
+ /// property, tying the constant and the property together explicitly.
+ ///
+ [TestMethod]
+ public void DemoClass_DefaultConstructor_SetsDefaultPrefix()
+ {
+ // Arrange
+ var demo = new DemoClass();
+
+ // Act
+ var actual = demo.Prefix;
+
+ // Assert – default constructor must yield exactly DemoClass.DefaultPrefix
+ Assert.AreEqual(DemoClass.DefaultPrefix, actual);
}
}
From 628e6577396628b7ef6f9fbca0539c685009ac06 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 2 Mar 2026 21:31:37 +0000
Subject: [PATCH 3/5] Remove non-XML-doc section separator comments from test
file
Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com>
---
.../DemoClassTests.cs | 20 -------------------
1 file changed, 20 deletions(-)
diff --git a/test/TemplateDotNetLibrary.Tests/DemoClassTests.cs b/test/TemplateDotNetLibrary.Tests/DemoClassTests.cs
index 0a00c64..c70659b 100644
--- a/test/TemplateDotNetLibrary.Tests/DemoClassTests.cs
+++ b/test/TemplateDotNetLibrary.Tests/DemoClassTests.cs
@@ -6,10 +6,6 @@ namespace TemplateDotNetLibrary.Tests;
[TestClass]
public class DemoClassTests
{
- // -------------------------------------------------------------------------
- // DemoMethod – happy-path greeting output
- // -------------------------------------------------------------------------
-
///
/// Proves that DemoMethod produces the expected "{prefix}, {name}!" format
/// when the default constructor is used.
@@ -46,10 +42,6 @@ public void DemoMethod_ReturnsGreeting_WithCustomPrefix()
Assert.AreEqual("Hi, Alice!", result);
}
- // -------------------------------------------------------------------------
- // DemoMethod – argument validation
- // -------------------------------------------------------------------------
-
///
/// Proves that DemoMethod throws ArgumentNullException (not a base
/// ArgumentException) when a null name is supplied.
@@ -78,10 +70,6 @@ public void DemoMethod_ThrowsArgumentException_ForEmptyInput()
Assert.ThrowsExactly(() => demo.DemoMethod(string.Empty));
}
- // -------------------------------------------------------------------------
- // Constructor – argument validation
- // -------------------------------------------------------------------------
-
///
/// Proves that the custom-prefix constructor throws ArgumentNullException
/// (not a base ArgumentException) when a null prefix is supplied.
@@ -104,10 +92,6 @@ public void Constructor_ThrowsArgumentException_ForEmptyPrefix()
Assert.ThrowsExactly(() => new DemoClass(string.Empty));
}
- // -------------------------------------------------------------------------
- // DefaultPrefix constant
- // -------------------------------------------------------------------------
-
///
/// Proves that the DefaultPrefix constant exposes the value "Hello",
/// which is the expected default greeting prefix.
@@ -125,10 +109,6 @@ public void DemoClass_DefaultPrefix_IsHello()
Assert.AreEqual(expected, actual);
}
- // -------------------------------------------------------------------------
- // Prefix property
- // -------------------------------------------------------------------------
-
///
/// Proves that the Prefix property returns the custom value provided to
/// the constructor, confirming the property reflects what was stored.
From dc34fef698c5c03a01afcc66b28e3481c1366b6a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 2 Mar 2026 21:41:55 +0000
Subject: [PATCH 4/5] Fix upload-artifact action version: v8 does not exist,
use v4
Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com>
---
.github/workflows/build.yaml | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 2e03ee9..11897b8 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -38,7 +38,7 @@ jobs:
echo "✓ Tool versions captured"
- name: Upload version capture
- uses: actions/upload-artifact@v8
+ uses: actions/upload-artifact@v4
with:
name: version-capture-quality
path: versionmark-quality.json
@@ -155,19 +155,19 @@ jobs:
echo "✓ Tool versions captured"
- name: Upload version capture
- uses: actions/upload-artifact@v8
+ uses: actions/upload-artifact@v4
with:
name: version-capture-${{ matrix.os }}
path: versionmark-build-*.json
- name: Upload Test Results
- uses: actions/upload-artifact@v8
+ uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.os }}
path: test-results/*.trx
- name: Upload Artifacts
- uses: actions/upload-artifact@v8
+ uses: actions/upload-artifact@v4
with:
name: artifacts-${{ matrix.os }}
path: |
@@ -229,7 +229,7 @@ jobs:
upload: false
- name: Upload CodeQL SARIF
- uses: actions/upload-artifact@v8
+ uses: actions/upload-artifact@v4
with:
name: codeql-sarif
path: sarif-results/csharp.sarif
@@ -495,7 +495,7 @@ jobs:
# Downstream projects: Add any additional artifact uploads here.
- name: Upload documentation
- uses: actions/upload-artifact@v8
+ uses: actions/upload-artifact@v4
with:
name: documents
path: |
From 95c946e51d731edf9730e57cb28d8d30a8f6a506 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 2 Mar 2026 21:46:53 +0000
Subject: [PATCH 5/5] Revert upload-artifact to v7 (original version)
Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com>
---
.github/workflows/build.yaml | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 11897b8..0d94e19 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -38,7 +38,7 @@ jobs:
echo "✓ Tool versions captured"
- name: Upload version capture
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: version-capture-quality
path: versionmark-quality.json
@@ -155,19 +155,19 @@ jobs:
echo "✓ Tool versions captured"
- name: Upload version capture
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: version-capture-${{ matrix.os }}
path: versionmark-build-*.json
- name: Upload Test Results
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: test-results-${{ matrix.os }}
path: test-results/*.trx
- name: Upload Artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: artifacts-${{ matrix.os }}
path: |
@@ -229,7 +229,7 @@ jobs:
upload: false
- name: Upload CodeQL SARIF
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: codeql-sarif
path: sarif-results/csharp.sarif
@@ -495,7 +495,7 @@ jobs:
# Downstream projects: Add any additional artifact uploads here.
- name: Upload documentation
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: documents
path: |