diff --git a/.agents/skills/dotnet-codestyle/SKILL.md b/.agents/skills/dotnet-codestyle/SKILL.md index 69efa64..ee8d9a9 100644 --- a/.agents/skills/dotnet-codestyle/SKILL.md +++ b/.agents/skills/dotnet-codestyle/SKILL.md @@ -101,130 +101,13 @@ updates, dependency upgrades, benchmarks) on top: ## Coding standards and conventions -Code snippets below are illustrative examples only, replace namespaces and types to match your -project. +Key rules: no `var` (always explicit types), file-scoped namespaces, Allman braces, Nullable +enabled, modern C# features (primary constructors, pattern matching, collection expressions). Every +public surface has XML documentation. Private fields use `_camelCase`, static fields `s_camelCase`, +constants PascalCase. Member ordering follows StyleCop SA1201. -### C# language features - -1. **File-scoped namespaces**: - - ```csharp - namespace Example.Project.Library; - ``` - -2. **Nullable reference types**: enabled (`enable`), use nullable annotations - appropriately, use `required` for mandatory properties. -3. **Modern C# features**: prefer modern language constructs, primary constructors when - appropriate, top-level statements for console apps, pattern matching over traditional checks, - collection expressions when types loosely match, extension methods (the classic - `this`-parameter form or an `extension() { ... }` block on C# 14+), implicit object - creation when the type is apparent, range and index operators. -4. **Expression-bodied members**: use for applicable methods, properties, accessors, operators, - lambdas, local functions. -5. **`var` keyword**: do NOT use `var`, always use explicit types: - - ```csharp - // Correct - int count = 42; - string name = "test"; - - // Incorrect - var count = 42; - var name = "test"; - ``` - -### Naming conventions - -1. **Private fields**: underscore prefix with camelCase: - - ```csharp - private readonly HttpClient _httpClient; - private int _counter; - ``` - -2. **Static fields**: `s_` prefix with camelCase: - - ```csharp - private static int s_instanceCount; - ``` - -3. **Constants**: PascalCase: - - ```csharp - private const int MaxRetries = 3; - ``` - -### Code structure - -1. **Global usings**: use `GlobalUsings.cs` for common namespaces: - - ```csharp - global using System; - global using System.Net.Http; - global using System.Threading.Tasks; - global using Microsoft.Extensions.Logging; - ``` - -2. **Usings placement**: outside the namespace, sorted with `System` directives first: - - ```csharp - using System.CommandLine; - using System.Runtime.CompilerServices; - using Example.Project.Library; - - namespace Example.Project.Console; - ``` - -3. **Braces**: Allman style: - - ```csharp - public void Method() - { - if (condition) - { - // code - } - } - ``` - -4. **Indentation**: C# files 4 spaces, XML/csproj files 2 spaces, YAML files 2 spaces, JSON files - 4 spaces. -5. **Line endings**: not specified here, governed per repo by `.editorconfig` / `.gitattributes` - per GOVERNANCE.md's "Line Endings" section. -6. **`#region`**: do not use regions, prefer logical file/folder/namespace organization. -7. **Member ordering (StyleCop SA1201)**: const -> static readonly -> static fields -> instance - readonly fields -> instance fields -> constructors -> public (events -> properties -> indexers - -> methods -> operators) -> non-public in same order -> nested types. - -### Comments and documentation - -XML documentation is on: `true`, and -missing XML comments for public APIs are suppressed in `.editorconfig`. Every public surface must -still be documented: a single-line summary, additional details in remarks, documented input -parameters, return values, exceptions, and crefs. - -```csharp -/// -/// Example of a single line summary. -/// -/// -/// Additional important details about usage. -/// Multiple lines if needed. -/// -/// -/// The quote category to request -/// -/// -/// A that can be used to cancel the request. -/// -/// -/// A containing the quote text. -/// -/// -/// Thrown when is not a supported value. -/// -public async Task GetQuoteOfTheDayAsync(string category, CancellationToken cancellationToken) {} -``` +For language features, naming, code structure, and XML documentation examples, see +`references/conventions.md`. ## Analyzer suppressions (.NET) @@ -312,47 +195,15 @@ The .NET mechanics, narrowest first: ## Testing conventions -1. **Framework**: xUnit v3 or later (the `xunit.v3` package, never the legacy v2 `xunit` package) - with AwesomeAssertions for every assertion. Native xUnit asserts (`Assert.Equal`, - `Assert.True`, ...) are not allowed, use the fluent `.Should()` API. Dynamic test skipping - (`Assert.Skip`, `Assert.SkipWhen`) is control flow, not an assertion, and stays native: - - ```csharp - [Fact] - public void MethodName_Scenario_ExpectedBehavior() - { - // Arrange - int expected = 42; - - // Act - int actual = GetValue(); - - // Assert - actual.Should().Be(expected); - } - ``` - -2. **Organization**: Arrange-Act-Assert pattern. -3. **Naming**: descriptive names with underscores. -4. **Theory tests**: use `[Theory]` with `[InlineData]`. +xUnit v3 (`xunit.v3`, not the legacy `xunit`) + AwesomeAssertions (`.Should()` API, never native +asserts). Arrange-Act-Assert pattern, descriptive underscore names, `[Theory]`/`[InlineData]` for +parameterized tests. See `references/testing.md` for the framework setup template. ## Project configuration -1. **Target framework**: .NET 10.0 (`net10.0`). -2. **AOT compatibility**: `true`, - `true`. -3. **Assembly information**: use semantic versioning, include SourceLink - (`true`), embed untracked sources - (`true`). -4. **Internal visibility**: use `InternalsVisibleTo` for test and benchmark access (adapt the - project names to your repo's test/benchmark projects): - - ```xml - - - - - ``` +.NET 10.0 target, AOT-compatible (`IsAotCompatible=true`, `VerifyReferenceAotCompatibility=true`), +SourceLink, embedded untracked sources, `InternalsVisibleTo` for test/benchmark access. See +`references/project-config.md` for the full property list. ## Best practices diff --git a/.agents/skills/dotnet-codestyle/references/conventions.md b/.agents/skills/dotnet-codestyle/references/conventions.md new file mode 100644 index 0000000..5eb4854 --- /dev/null +++ b/.agents/skills/dotnet-codestyle/references/conventions.md @@ -0,0 +1,126 @@ +# .NET Coding Standards and Conventions + +Code snippets below are illustrative examples only, replace namespaces and types to match your +project. + +## C# language features + +1. **File-scoped namespaces**: + + ```csharp + namespace Example.Project.Library; + ``` + +2. **Nullable reference types**: enabled (`enable`), use nullable annotations + appropriately, use `required` for mandatory properties. +3. **Modern C# features**: prefer modern language constructs, primary constructors when + appropriate, top-level statements for console apps, pattern matching over traditional checks, + collection expressions when types loosely match, extension methods (the classic + `this`-parameter form or an `extension() { ... }` block on C# 14+), implicit object + creation when the type is apparent, range and index operators. +4. **Expression-bodied members**: use for applicable methods, properties, accessors, operators, + lambdas, local functions. +5. **`var` keyword**: do NOT use `var`, always use explicit types: + + ```csharp + // Correct + int count = 42; + string name = "test"; + + // Incorrect + var count = 42; + var name = "test"; + ``` + +## Naming conventions + +1. **Private fields**: underscore prefix with camelCase: + + ```csharp + private readonly HttpClient _httpClient; + private int _counter; + ``` + +2. **Static fields**: `s_` prefix with camelCase: + + ```csharp + private static int s_instanceCount; + ``` + +3. **Constants**: PascalCase: + + ```csharp + private const int MaxRetries = 3; + ``` + +## Code structure + +1. **Global usings**: use `GlobalUsings.cs` for common namespaces: + + ```csharp + global using System; + global using System.Net.Http; + global using System.Threading.Tasks; + global using Microsoft.Extensions.Logging; + ``` + +2. **Usings placement**: outside the namespace, sorted with `System` directives first: + + ```csharp + using System.CommandLine; + using System.Runtime.CompilerServices; + using Example.Project.Library; + + namespace Example.Project.Console; + ``` + +3. **Braces**: Allman style: + + ```csharp + public void Method() + { + if (condition) + { + // code + } + } + ``` + +4. **Indentation**: C# files 4 spaces, XML/csproj files 2 spaces, YAML files 2 spaces, JSON files + 4 spaces. +5. **Line endings**: not specified here, governed per repo by `.editorconfig` / `.gitattributes` + per GOVERNANCE.md's "Line Endings" section. +6. **`#region`**: do not use regions, prefer logical file/folder/namespace organization. +7. **Member ordering (StyleCop SA1201)**: const -> static readonly -> static fields -> instance + readonly fields -> instance fields -> constructors -> public (events -> properties -> indexers + -> methods -> operators) -> non-public in same order -> nested types. + +## Comments and documentation + +XML documentation is on: `true`, and +missing XML comments for public APIs are suppressed in `.editorconfig`. Every public surface must +still be documented: a single-line summary, additional details in remarks, documented input +parameters, return values, exceptions, and crefs. + +```csharp +/// +/// Example of a single line summary. +/// +/// +/// Additional important details about usage. +/// Multiple lines if needed. +/// +/// +/// The quote category to request +/// +/// +/// A that can be used to cancel the request. +/// +/// +/// A containing the quote text. +/// +/// +/// Thrown when is not a supported value. +/// +public async Task GetQuoteOfTheDayAsync(string category, CancellationToken cancellationToken) {} +``` diff --git a/.agents/skills/dotnet-codestyle/references/project-config.md b/.agents/skills/dotnet-codestyle/references/project-config.md new file mode 100644 index 0000000..8f6e838 --- /dev/null +++ b/.agents/skills/dotnet-codestyle/references/project-config.md @@ -0,0 +1,17 @@ +# .NET Project Configuration + +1. **Target framework**: .NET 10.0 (`net10.0`). +2. **AOT compatibility**: `true`, + `true`. +3. **Assembly information**: use semantic versioning, include SourceLink + (`true`), embed untracked sources + (`true`). +4. **Internal visibility**: use `InternalsVisibleTo` for test and benchmark access (adapt the + project names to your repo's test/benchmark projects): + + ```xml + + + + + ``` diff --git a/.agents/skills/dotnet-codestyle/references/testing.md b/.agents/skills/dotnet-codestyle/references/testing.md new file mode 100644 index 0000000..5a84a17 --- /dev/null +++ b/.agents/skills/dotnet-codestyle/references/testing.md @@ -0,0 +1,25 @@ +# .NET Testing Conventions + +1. **Framework**: xUnit v3 or later (the `xunit.v3` package, never the legacy v2 `xunit` package) + with AwesomeAssertions for every assertion. Native xUnit asserts (`Assert.Equal`, + `Assert.True`, ...) are not allowed, use the fluent `.Should()` API. Dynamic test skipping + (`Assert.Skip`, `Assert.SkipWhen`) is control flow, not an assertion, and stays native: + + ```csharp + [Fact] + public void MethodName_Scenario_ExpectedBehavior() + { + // Arrange + int expected = 42; + + // Act + int actual = GetValue(); + + // Assert + actual.Should().Be(expected); + } + ``` + +2. **Organization**: Arrange-Act-Assert pattern. +3. **Naming**: descriptive names with underscores. +4. **Theory tests**: use `[Theory]` with `[InlineData]`. diff --git a/.agents/skills/git-commit-conventions/SKILL.md b/.agents/skills/git-commit-conventions/SKILL.md index 3709d31..85e46eb 100644 --- a/.agents/skills/git-commit-conventions/SKILL.md +++ b/.agents/skills/git-commit-conventions/SKILL.md @@ -154,28 +154,13 @@ the rewrite looks, a rejected push is recoverable, a force-pushed one is not. ## History rewrites re-identify only what changed -**A history rewrite includes only the commits that must change, and re-identifies any commit it -rewrites that is not the agent's own.** Filtering history (`git filter-repo` or an equivalent, for -example to strip PII) re-signs every commit it touches with the rewriter's own key, while the -tooling preserves each commit's original `author`/`committer` unless told otherwise. GitHub -verifies a signature against the commit's `committer` identity, so a signature from the rewriter's -key over a commit still committed by a bot (`dependabot[bot]`, `github-actions[bot]`) or GitHub's -own web-flow does not match its committer and lands `unknown_key`/unverified, which a -require-signed-commits rule then rejects. - -Two gates keep committer and signature aligned: - -1. **Scope the rewrite to only the commits that must be modified.** By default those are the - rewriter's own, whose committer already matches, so a commit that needs no change stays out of - the rewrite entirely and its identity and signature are never touched. -2. **If a commit that must change is not the rewriter's own, set its `committer` to the rewriter's - own signing identity before re-signing** (and its `author` too, since a rewrite that alters - content should not keep attributing it to the bot). The original bot attribution is deliberately - given up as the cost of having to rewrite it. - -Never leave a signature over a commit committed by another identity. Verify after any rewrite that -every rewritten commit is signed and committed under the correct identity -(`git log --show-signature`). +**Do not rewrite a commit that does not need to change.** A history rewrite (e.g. `git filter-repo` +to strip PII) re-signs every touched commit with the rewriter's key. If that commit is still +committed by a bot (`dependabot[bot]`, `github-actions[bot]`) or GitHub's own web-flow, the +signature will not match the committer and the require-signed-commits rule rejects it. Scope the +rewrite to only the commits that must change. Set `committer` (and `author`) to the rewriter's +identity on any non-own commit that must be modified. Verify with `git log --show-signature` after +any rewrite. See `references/history-rewrite.md` for the full two-gate rule. ## Never run destructive git commands without being asked diff --git a/.agents/skills/git-commit-conventions/references/history-rewrite.md b/.agents/skills/git-commit-conventions/references/history-rewrite.md new file mode 100644 index 0000000..b331a55 --- /dev/null +++ b/.agents/skills/git-commit-conventions/references/history-rewrite.md @@ -0,0 +1,24 @@ +# History Rewrites: Re-identification Rules + +**A history rewrite includes only the commits that must change, and re-identifies any commit it +rewrites that is not the agent's own.** Filtering history (`git filter-repo` or an equivalent, for +example to strip PII) re-signs every commit it touches with the rewriter's own key, while the +tooling preserves each commit's original `author`/`committer` unless told otherwise. GitHub +verifies a signature against the commit's `committer` identity, so a signature from the rewriter's +key over a commit still committed by a bot (`dependabot[bot]`, `github-actions[bot]`) or GitHub's +own web-flow does not match its committer and lands `unknown_key`/unverified, which a +require-signed-commits rule then rejects. + +Two gates keep committer and signature aligned: + +1. **Scope the rewrite to only the commits that must be modified.** By default those are the + rewriter's own, whose committer already matches, so a commit that needs no change stays out of + the rewrite entirely and its identity and signature are never touched. +2. **If a commit that must change is not the rewriter's own, set its `committer` to the rewriter's + own signing identity before re-signing** (and its `author` too, since a rewrite that alters + content should not keep attributing it to the bot). The original bot attribution is deliberately + given up as the cost of having to rewrite it. + +Never leave a signature over a commit committed by another identity. Verify after any rewrite that +every rewritten commit is signed and committed under the correct identity +(`git log --show-signature`). diff --git a/.agents/skills/python-codestyle/SKILL.md b/.agents/skills/python-codestyle/SKILL.md index a5a262d..911ae30 100644 --- a/.agents/skills/python-codestyle/SKILL.md +++ b/.agents/skills/python-codestyle/SKILL.md @@ -24,66 +24,16 @@ language shares (clean-compile verification as a concept, the suppression-scope casing in prose), this Skill is everything specific to a Python project on top of that: the two profiles, the toolchain, layout, and the language-level conventions. -## Adapt before propagating - -The rules below describe the default Python profile: a package that publishes to PyPI, -type-checked by pyright in strict mode, dependencies in `[dependency-groups]`. A derived repo -often differs, and when it does, adapt these fields to match the repo's actual toolchain rather -than copying verbatim (a verbatim copy that misdescribes the repo is inaccurate and gets rejected -in review). The axes that commonly vary per repo: - -- **Type checker in CI**: pyright strict, mypy in CI with pyright editor-only (Pylance), or both. - Whichever runs in CI is the one the clean-compile and the CI gate invoke. -- **Dependency declaration**: `[dependency-groups]`, or PEP 621 `[project.optional-dependencies]` - (dev tools installed with `uv sync --extra `). -- **Versioning / publishing**: a published package (`_version.py` plus a version source, - `uv build`, and a PyPI publish step), or a source-only repo with a static `version` and no - publish step (see Versioning below). -- **Disabled markdownlint rules**: repo-specific, `.markdownlint-cli2.jsonc` at the repo root is - the source of truth, not any example rule named here. -- **VS Code config home**: editor settings/extensions may live in `.vscode/*.json` or the - `.code-workspace`, while tasks/launch/debug configs can only be external `.vscode/*.json` - (they cannot live in the workspace file). The repo's own `tasks.json` sits wherever it keeps it, - and the canonical task definitions it is written against are the hub `vscode-tasks-python.json` - snippet, which resolves the same way from every repo. - ## Two profiles -A repo's Python is one of two shapes, declared as the `build` or `lint-only` profile and validated -against the `pyproject.toml` shape. Most of this Skill (uv project, `uv.lock`, `uv run`, src -layout, pytest coverage) describes the Project shape (the `build` profile). The two differ by -whether the Python has third-party runtime dependencies, which shows up structurally in -`pyproject.toml`, so the fleet's audit reads the shape there: +Read the repo's `pyproject.toml` shape and pick the profile before applying any other rule: -- **Project** (the `build` profile): the Python has third-party runtime dependencies, or is the - repo's deliverable. It is a PEP 621 uv project: `[project]` with `dependencies` (dev tools in - `[project.optional-dependencies]` or `[dependency-groups]`), a `[build-system]`, and a committed - `uv.lock` (pinned LF, per GOVERNANCE.md's "Line Endings" section). CI runs `uv sync --frozen` + - `uv run `, so the lockfile pins tool versions. -- **Scripts** (the `lint-only` profile): stdlib-only utility scripts embedded in a non-Python repo - (e.g. a Python tooling subtree of a `csharp` app). Run the tools with `uvx` (no project install, - no lockfile): the `pyproject.toml` carries only tool config (`[tool.ruff]`, `[tool.mypy]`, and - an optional `[tool.pyright]` editor block), with no `[project]`, no `[build-system]`, and no - `uv.lock` (that metadata would misrepresent it as a shippable package). mypy is the type-check - gate (there is no first-party package for pyright strict to anchor on), and a `[tool.pyright]` - block in standard mode keeps Pylance quiet in the editor, the same mypy-gate/pyright-editor - split the build profile uses. There is no lockfile, and a `uvx @` pin in a `run:` - step is not something Dependabot tracks, so CI runs `uvx ruff@latest` / `uvx mypy@latest` rather - than a manual pin that would silently go stale. The fleet rule is to pin only what Dependabot - auto-updates (SHA-pinned actions, package deps) and otherwise run latest, so the VS Code tasks, - README, and CI all run the unpinned latest here. `.py` files follow the repo's LF line-ending - default (per GOVERNANCE.md's "Line Endings" section). There is no pytest suite, and `unittest` is the runner - instead. A script that carries a gate still earns tests, written with the standard library's - `unittest` so they run under bare `python3` with nothing installed, as `test_