diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..4a649ba --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,163 @@ +# GitHub Copilot Instructions for SharedCode + +## Project Overview + +SharedCode is a multi-project .NET 9 solution that provides reusable utility libraries +published as individual NuGet packages. Each project corresponds to one NuGet package: + +| Project | NuGet Package | Purpose | +|---|---|---| +| `SharedCode.Core` | `SharedCode.Core` | Core utilities: extensions, specifications, domain primitives, text, calendar, security, threading, reactive | +| `SharedCode.Core.Tests` | *(test only)* | Unit tests for `SharedCode.Core` | +| `SharedCode.Data` | `SharedCode.Data` | Data access abstractions and helpers (repository pattern, paging, query results) | +| `SharedCode.Data.CosmosDb` | `SharedCode.Data.CosmosDb` | Azure Cosmos DB integration | +| `SharedCode.Data.EntityFramework` | `SharedCode.Data.EntityFramework` | Entity Framework Core integration (auditable contexts, EF repository, specifications) | +| `SharedCode.DependencyInjection` | `SharedCode.DependencyInjection` | DI assembly scanning and auto-registration | +| `SharedCode.MediatR` | `SharedCode.MediatR` | MediatR pipeline integration helpers | +| `SharedCode.Web` | `SharedCode.Web` | ASP.NET Core helpers (base controller, service collection extensions) | +| `SharedCode.Windows.WPF` | `SharedCode.Windows.WPF` | WPF helpers (BindableBase, converters, attached properties, MVVM mediator) | + +## Build & Test + +```bash +# Build the entire solution +dotnet build SharedCode.sln + +# Run all tests +dotnet test SharedCode.sln + +# Run tests with verbose output and GitHub Actions logging +dotnet test --logger GitHubActions --verbosity normal SharedCode.sln +``` + +## Code Conventions + +### Language & Framework + +- **Target framework**: `net9.0` +- **Language version**: `preview` (latest C# features enabled) +- **Nullable reference types**: enabled (`enable`) +- **Implicit usings**: enabled +- **Warnings as errors**: all warnings are treated as errors + (`true`, + `true`) + +### Formatting + +- 4-space indentation; no tabs +- CRLF line endings +- UTF-8 BOM for `.cs` files +- Maximum line length: 100 characters +- Opening brace on its own line for all constructs + (`csharp_new_line_before_open_brace = all`) + +### Naming + +- `PascalCase` for types, public members, and constants +- `camelCase` for parameters and local variables +- Private fields: `camelCase` without an underscore prefix +- Use `this.` prefix for all instance member accesses +- Use `_` as the discard variable + (e.g., `_ = value ?? throw new ArgumentNullException(nameof(value))`) +- Use `@this` as the parameter name for the extended type in extension methods + (e.g., `this DateTime @this`) + +### XML Documentation + +Every public type and member **must** have complete XML documentation, including: + +- `` — describes what the member does +- `` — for each generic type parameter +- `` — for each method parameter +- `` — describes the return value when non-void +- `` — lists each documented exception +- `` — additional usage notes where appropriate + +Example from `DateTimeExtensions.cs`: + +```csharp +/// +/// Adds the given number of business days to the . +/// +/// The date to be changed. +/// Number of business days to be added. +/// A increased by a given number of business days. +public static DateTime AddBusinessDays(this DateTime @this, int days) { ... } +``` + +### Null Handling + +- Prefer `ArgumentNullException.ThrowIfNull(param)` (.NET 6+) +- Prefer `ArgumentException.ThrowIfNullOrEmpty(param)` for strings (.NET 7+) +- When targeting older frameworks via polyfill, wrap with `#if NET6_0_OR_GREATER` + +### Code Analysis Suppressions + +- Suppress with `[SuppressMessage("Category", "RuleId:Title", Justification = "reason")]` +- Always provide a meaningful `Justification`; never suppress without explanation + +## Extension Method Conventions + +- Place extension methods in the same namespace as the extended type + (e.g., `namespace SharedCode` for core types, `namespace SharedCode.Text` for string helpers) +- One static extension class per file: `Extensions.cs` +- Mark the class `public static` (never `internal`) +- Use `@this` as the first parameter name + +## Key Design Patterns + +### Specification Pattern (`SharedCode.Core/Specifications`) + +Concrete specifications inherit `Specification` (or `Specification` for +projections) and build filter/order/paging expressions via the `this.Query` fluent builder +in the constructor: + +```csharp +public sealed class ActiveUsersSpec : Specification +{ + public ActiveUsersSpec() => + this.Query.Where(u => u.IsActive).OrderBy(u => u.LastName); +} +``` + +### Repository Pattern (`SharedCode.Data`) + +`IQueryRepository` and `ICommandRepository` define data access contracts. +`QueryRepository` is the default in-memory/generic implementation. +EF-specific implementations live in `SharedCode.Data.EntityFramework`. + +### Dependency Injection (`SharedCode.DependencyInjection`) + +Services implement `IDependencyRegister` to self-register via assembly scanning with +`DependencyLoader` / `CatalogSelector`. + +## Testing Conventions + +Unit tests live in `SharedCode.Core.Tests/` and follow these rules: + +- **Framework**: MSTest (`[TestClass]`, `[TestMethod]`, `[DataRow]`) +- **Assertions**: AwesomeAssertions +- **Pattern**: Arrange / Act / Assert with blank lines separating each block +- **File location**: mirror the source structure + (e.g., `Calendar/DateTimeExtensionsTests.cs` for `Calendar/DateTimeExtensions.cs`) +- Suppress `CA1515` on test classes — MSTest requires them to be `public` + +## Shared Build Configuration + +- `Directory.Build.props` — solution-wide MSBuild settings (analyzer settings, package metadata, + version scheme, SourceLink, Roslynator) +- `Directory.Packages.props` — centrally managed package versions; always update here, never in + individual `.csproj` files + +## Maintenance + +These instructions must stay aligned with the codebase. Use the prompt at +`.github/prompts/maintain-copilot-instructions.prompt.md` whenever: + +- A new project or module is added to the solution +- Coding conventions or patterns change +- New analyzers or packages are introduced +- Test infrastructure changes + +The workflow at `.github/workflows/maintain-copilot-instructions.yml` automatically +detects undocumented projects and opens a GitHub Issue to prompt a review. diff --git a/.github/prompts/add-extension-method.prompt.md b/.github/prompts/add-extension-method.prompt.md new file mode 100644 index 0000000..ccb9cb0 --- /dev/null +++ b/.github/prompts/add-extension-method.prompt.md @@ -0,0 +1,80 @@ +--- +mode: edit +description: Add a new extension method to the SharedCode library following project conventions. +--- + +# Add an Extension Method + +Add a new extension method to the SharedCode library. + +## Steps + +1. **Identify the right project and namespace** + + | Type being extended | Project | Namespace | + |---|---|---| + | `DateTime` / `DateTimeOffset` | `SharedCode.Core/Calendar` | `SharedCode.Calendar` | + | `string` | `SharedCode.Core/Text` | `SharedCode.Text` | + | `int`, `long`, other numerics | `SharedCode.Core` | `SharedCode` | + | `IEnumerable` / `IQueryable` | `SharedCode.Core/Linq` | `SharedCode.Linq` | + | `Exception` | `SharedCode.Core` | `SharedCode` | + | Generic / `object` | `SharedCode.Core` | `SharedCode` | + | `IServiceCollection` | matching module | matching namespace | + +2. **Find or create `Extensions.cs`** + + - If the file exists, add the method to the existing static class. + - If it does not exist, create it. Use `public static class Extensions`. + - If the class spans multiple files use `partial`. + +3. **Write the method** following these rules: + - Name the extended parameter `@this` + - Use `ArgumentNullException.ThrowIfNull(@this)` as the first statement for reference types + - Use `this.` for all instance member accesses inside the body + - Nullable annotations must be correct (`T?` vs `T`) + - Expression-body (`=>`) is preferred for single-expression methods + +4. **Add complete XML documentation** (required — `true` is set globally) + +5. **Verify zero warnings**: `dotnet build SharedCode.sln` + +## Template + +```csharp +/// +/// [One-line summary of what this method does.] +/// +/// [Description of the type parameter, if generic.] +/// The [type name] to operate on. +/// [Description of the parameter.] +/// [Description of the return value.] +/// +/// is . +/// +public static ReturnType MethodName(this TargetType @this, ParamType paramName) +{ + ArgumentNullException.ThrowIfNull(@this); + + // implementation +} +``` + +## Single-expression example + +```csharp +/// +/// Determines whether the falls between +/// and (inclusive). +/// +/// The type of the values being compared. +/// The value to test. +/// The lower bound (inclusive). +/// The upper bound (inclusive). +/// +/// if is between +/// and ; otherwise . +/// +public static bool IsBetween(this T value, T low, T high) + where T : IComparable => + value.CompareTo(low) >= 0 && value.CompareTo(high) <= 0; +``` diff --git a/.github/prompts/add-specification.prompt.md b/.github/prompts/add-specification.prompt.md new file mode 100644 index 0000000..65748b1 --- /dev/null +++ b/.github/prompts/add-specification.prompt.md @@ -0,0 +1,115 @@ +--- +mode: edit +description: Implement a new Specification class following the repository's Specification pattern. +--- + +# Add a Specification + +Create a new concrete specification in the SharedCode library. + +## Background + +The Specification pattern is implemented in `SharedCode.Core/Specifications`. A specification +encapsulates a query (filter, order, paging, and includes) independently of any persistence +mechanism. `InMemorySpecificationEvaluator` evaluates specs against in-memory collections; +the EF evaluator in `SharedCode.Data.EntityFramework` evaluates them against a `DbContext`. + +## Steps + +1. **Choose the right project** + + | Scope | Project | Folder | + |---|---|---| + | In-memory / framework-agnostic | `SharedCode.Core` | `Specifications/` | + | Entity Framework | `SharedCode.Data.EntityFramework` | `Specifications/` | + +2. **Choose the base class** + + - `Specification` — returns a collection of `T` + - `Specification` — projects `T` to `TResult` via a `Selector` expression + +3. **Create `Specification.cs`** in the appropriate folder. + +4. **Build the query in the constructor** using `this.Query`: + + | Builder method | Purpose | + |---|---| + | `.Where(e => ...)` | Filter predicate | + | `.OrderBy(e => ...)` | Ascending order | + | `.OrderByDescending(e => ...)` | Descending order | + | `.ThenBy(e => ...)` / `.ThenByDescending(...)` | Secondary sort | + | `.Skip(n).Take(m)` | Paging | + | `.Include(e => e.Navigation)` | Eager loading (EF only) | + | `.EnableCache(key)` | Second-level cache hint | + +5. **Seal the class** unless it is explicitly designed for inheritance. + +6. **Add full XML documentation** on the class and all constructors. + +7. **Verify zero warnings**: `dotnet build SharedCode.sln` + +## Template — filter specification + +```csharp +namespace SharedCode.Specifications; + +/// +/// A specification that selects [describe what is filtered]. +/// +public sealed class ExampleByIdSpecification : Specification +{ + /// + /// Initializes a new instance of the class. + /// + /// The identifier of the entity to match. + public ExampleByIdSpecification(int id) => + this.Query.Where(e => e.Id == id); +} +``` + +## Template — projection specification + +```csharp +namespace SharedCode.Specifications; + +/// +/// A specification that projects [source entity] to [result type]. +/// +public sealed class ExampleSummarySpecification : Specification +{ + /// + /// Initializes a new instance of the class. + /// + /// When , only active entities are returned. + public ExampleSummarySpecification(bool isActive) + { + this.Query + .Where(e => e.IsActive == isActive) + .OrderBy(e => e.Name) + .Select(e => new ExampleSummary { Id = e.Id, Name = e.Name }); + } +} +``` + +## Template — paged specification + +```csharp +namespace SharedCode.Specifications; + +/// +/// A paged specification for [entity type]. +/// +public sealed class PagedExampleSpecification : Specification +{ + /// + /// Initializes a new instance of the class. + /// + /// The one-based page number. + /// The number of items per page. + public PagedExampleSpecification(int pageNumber, int pageSize) => + this.Query + .OrderBy(e => e.Id) + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize); +} +``` diff --git a/.github/prompts/create-module.prompt.md b/.github/prompts/create-module.prompt.md new file mode 100644 index 0000000..9f2257e --- /dev/null +++ b/.github/prompts/create-module.prompt.md @@ -0,0 +1,95 @@ +--- +mode: edit +description: Scaffold a new SharedCode project that publishes as a separate NuGet package. +--- + +# Create a New SharedCode Module + +Add a new project to the SharedCode solution that will be published as its own NuGet package. + +## Steps + +### 1 — Create the project folder + +``` +SharedCode./ +``` + +### 2 — Create the `.csproj` file + +All shared MSBuild settings come from `Directory.Build.props`; only module-specific values +belong here. + +```xml + + + SharedCode. + A library of [short description] shared for free use to help with common scenarios. + shared code, c#, [relevant tags] + SharedCode. + net9.0 + + + + + + + + + + + True + + + + +``` + +### 3 — Copy shared assets + +Copy `share.ico` and `share.png` from any existing module into the new folder. + +### 4 — Create `README.md` + +```markdown +# Shared Code Library + +A library of code shared for free use to help with common scenarios. + +To use this, search NuGet for SharedCode. +``` + +### 5 — Add to the solution + +```bash +dotnet sln SharedCode.sln add SharedCode./SharedCode..csproj +``` + +### 6 — Add new package versions (if needed) + +If the module depends on new NuGet packages, add `` entries to +`Directory.Packages.props`: + +```xml + +``` + +Then reference the package in the `.csproj` **without** a version attribute: + +```xml + +``` + +### 7 — Update documentation + +- Add the new package to the table in the root `README.md`. +- Add a row to the module table in `.github/copilot-instructions.md`. + +### 8 — Verify the build + +```bash +dotnet build SharedCode.sln +``` + +Zero warnings are required before merging. diff --git a/.github/prompts/fix-code-analysis.prompt.md b/.github/prompts/fix-code-analysis.prompt.md new file mode 100644 index 0000000..d861990 --- /dev/null +++ b/.github/prompts/fix-code-analysis.prompt.md @@ -0,0 +1,63 @@ +--- +mode: edit +description: Resolve a compiler warning or code-analysis diagnostic in the SharedCode solution. +--- + +# Fix a Code Analysis Warning + +Resolve a compiler warning or Roslyn diagnostic in the SharedCode solution. + +## Process + +1. **Read the full diagnostic** — note the rule ID (e.g., `CA1031`, `RCS1234`, `IDE0046`), + the message, and the exact file and line number. + +2. **Understand the rule** before touching any code. + +3. **Apply the minimal fix** that aligns with the existing code style: + - Fix the root cause whenever possible. + - If suppression is the only viable option, use `[SuppressMessage]` with a meaningful + `Justification` (never suppress without explanation). + +4. **Verify no new warnings are introduced**: `dotnet build SharedCode.sln` + +5. **Do not change unrelated code.** + +## Common Fixes + +| Rule ID | Description | Typical Fix | +|---|---|---| +| `CA1062` | Validate parameter is non-null | `ArgumentNullException.ThrowIfNull(param)` | +| `CA1031` | Do not catch general exception types | Catch specific exception types instead | +| `CA2201` | Do not raise reserved exception types | Throw a more derived `Exception` subclass | +| `CA1014` | Mark assemblies with CLSCompliant | Already suppressed globally via `CA1014` | +| `IDE0046` | Convert to conditional expression | Use ternary `? :` only when it improves readability | +| `IDE0028` | Use collection initializer | Replace `Add()` calls with `{ ... }` initializer syntax | +| `RCS1175` | Unused `this` parameter | Remove unused `this` param, or add `[SuppressMessage]` with justification | +| `nullable` | Nullable warning (CS8600–CS8629) | Add `?` to nullable types or add a null-guard | +| `CA1515` | Consider making public types internal | **Do not apply in test projects** — MSTest requires `public` test classes | + +## Suppression template + +Only use when the rule genuinely does not apply: + +```csharp +[SuppressMessage( + "Category", + "RuleId:Short title", + Justification = "One sentence explaining why this rule does not apply here.")] +``` + +## Null-guard patterns + +```csharp +// .NET 6+ (preferred) +ArgumentNullException.ThrowIfNull(param); + +// .NET 7+ for strings +ArgumentException.ThrowIfNullOrEmpty(param); +ArgumentException.ThrowIfNullOrWhiteSpace(param); + +// Discard pattern for guard-and-assign +_ = param ?? throw new ArgumentNullException(nameof(param)); +``` diff --git a/.github/prompts/maintain-copilot-instructions.prompt.md b/.github/prompts/maintain-copilot-instructions.prompt.md new file mode 100644 index 0000000..1481270 --- /dev/null +++ b/.github/prompts/maintain-copilot-instructions.prompt.md @@ -0,0 +1,46 @@ +--- +mode: edit +description: Review and update the Copilot configuration files to keep them aligned with the current codebase. +--- + +# Maintain Copilot Instructions + +Review and update all Copilot configuration files so they stay accurate as the codebase evolves. +Run this prompt after adding new modules, changing conventions, or updating the toolchain. + +## Files to Review + +### `.github/copilot-instructions.md` + +- [ ] The module table lists every project currently in `SharedCode.sln` + (run `grep '\.csproj' SharedCode.sln` to get the current list) +- [ ] Build and test commands are still correct +- [ ] The C# language version and target framework match `Directory.Build.props` +- [ ] Namespace conventions match the source files +- [ ] New patterns or base classes that have emerged are documented +- [ ] Analyzer packages and their roles match `Directory.Build.props` +- [ ] Testing framework details match `SharedCode.Core.Tests/Core.Tests.csproj` + +### `.github/prompts/*.prompt.md` + +- [ ] `add-extension-method.prompt.md` — namespace table is complete and templates compile +- [ ] `add-specification.prompt.md` — builder API still matches `ISpecificationBuilder` +- [ ] `fix-code-analysis.prompt.md` — rule table covers the analyzers actually in use +- [ ] `create-module.prompt.md` — scaffolding steps still match the solution structure +- [ ] Each prompt's `description` front-matter field is accurate + +### `.vscode/mcp.json` + +- [ ] Listed MCP servers are still current and useful +- [ ] Docker image tags or npx package versions are up to date +- [ ] Any new MCP servers that would improve development tasks should be added + +## Process + +1. Open `SharedCode.sln` and list all current projects. +2. Open `Directory.Build.props` and `Directory.Packages.props` to confirm the current SDK + targets, language version, and package versions. +3. Spot-check two or three recently changed source files for new patterns or conventions. +4. Update each file listed above as needed, keeping changes minimal and accurate. +5. Run `dotnet build SharedCode.sln` to confirm no build errors were introduced. +6. Commit with the message: `chore: update copilot instructions`. diff --git a/.github/workflows/maintain-copilot-instructions.yml b/.github/workflows/maintain-copilot-instructions.yml new file mode 100644 index 0000000..9d24a9c --- /dev/null +++ b/.github/workflows/maintain-copilot-instructions.yml @@ -0,0 +1,135 @@ +name: Maintain Copilot Instructions + +on: + # Run automatically when solution structure or shared build config changes + push: + branches: [main] + paths: + - '**/*.csproj' + - 'SharedCode.sln' + - 'Directory.Build.props' + - 'Directory.Packages.props' + # Run on the first day of each month to catch any drift that slipped through + schedule: + - cron: '0 9 1 * *' + # Allow a maintainer to trigger a review at any time + workflow_dispatch: + +permissions: + contents: read + issues: write + +jobs: + check-instructions: + name: Check Copilot Instructions are Up-to-Date + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Detect projects not documented in copilot-instructions.md + id: check + shell: bash + run: | + instructions=".github/copilot-instructions.md" + + # Collect all SharedCode project names from the solution file. + # The .sln format uses Windows paths: "SharedCode.\.csproj" + # so we extract the folder name (everything between the opening quote + # and the first backslash). + projects=$(grep -oP '"SharedCode\.[^\\"]+(?=\\)' SharedCode.sln \ + | tr -d '"' | sort -u) + + missing_list="" + while IFS= read -r project; do + if [[ -z "$project" ]]; then + continue + fi + if ! grep -qF "$project" "$instructions"; then + missing_list="${missing_list}\n- \`${project}\`" + fi + done <<< "$projects" + + if [[ -n "$missing_list" ]]; then + echo "has_missing=true" >> "$GITHUB_OUTPUT" + else + echo "has_missing=false" >> "$GITHUB_OUTPUT" + fi + + # Use a heredoc to safely pass multi-line content to GITHUB_OUTPUT + { + echo "missing_list<> "$GITHUB_OUTPUT" + + - name: Open or update a GitHub Issue when instructions are outdated + if: steps.check.outputs.has_missing == 'true' + uses: actions/github-script@v7 + with: + script: | + const missing = `${{ steps.check.outputs.missing_list }}`; + + const body = [ + '## Copilot Instructions May Be Outdated', + '', + 'The following projects are present in `SharedCode.sln` but are not yet', + 'documented in `.github/copilot-instructions.md`:', + '', + missing, + '', + '### What to do', + '', + '1. Open `.github/copilot-instructions.md` and add the missing projects to', + ' the module table.', + '2. Use the prompt at `.github/prompts/maintain-copilot-instructions.prompt.md`', + ' to guide a full review of all Copilot configuration files.', + '3. Close this issue once the instructions are up to date.', + ].join('\n'); + + const label = 'copilot-instructions'; + + // Ensure the label exists (create it if not) + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label, + }); + } catch { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label, + color: '0075ca', + description: 'Tracks updates needed to .github/copilot-instructions.md', + }); + } + + // Update an existing open issue rather than creating duplicates + const existing = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + labels: label, + }); + + if (existing.data.length > 0) { + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: existing.data[0].number, + body, + }); + core.info(`Updated existing issue #${existing.data[0].number}`); + } else { + const created = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: 'Update Copilot Instructions', + body, + labels: [label, 'documentation'], + }); + core.info(`Created issue #${created.data.number}`); + } diff --git a/.vscode/mcp.json b/.vscode/mcp.json new file mode 100644 index 0000000..97abdf7 --- /dev/null +++ b/.vscode/mcp.json @@ -0,0 +1,26 @@ +{ + "inputs": [ + { + "id": "github-token", + "type": "promptString", + "description": "GitHub Personal Access Token (required for the GitHub MCP server)", + "password": true + } + ], + "servers": { + "github": { + "command": "docker", + "args": [ + "run", + "--interactive", + "--rm", + "--env", + "GITHUB_PERSONAL_ACCESS_TOKEN", + "ghcr.io/github/github-mcp-server" + ], + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "${input:github-token}" + } + } + } +} diff --git a/Directory.Packages.props b/Directory.Packages.props index 0f6287b..9d4cf76 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -18,6 +18,7 @@ + diff --git a/SharedCode.Core/Text/StringExtensions.cs b/SharedCode.Core/Text/StringExtensions.cs index 4832454..cb7c044 100644 --- a/SharedCode.Core/Text/StringExtensions.cs +++ b/SharedCode.Core/Text/StringExtensions.cs @@ -329,7 +329,7 @@ public static string GetEnumDescription(string value) /// The input value. /// Array of string values to compare /// Return true if any string value matches - public static bool In(this string @this, params string[] stringValues) => stringValues.Any(otherValue => string.CompareOrdinal(@this, otherValue) == 0); + public static bool In(this string @this, params string[] stringValues) => stringValues.Any(otherValue => string.Equals(@this, otherValue, StringComparison.Ordinal)); /// /// Determines whether the input string can be converted to the target type. diff --git a/SharedCode.Data.EntityFramework/Data.EntityFramework.csproj b/SharedCode.Data.EntityFramework/Data.EntityFramework.csproj index 1fbfd27..8232b2d 100644 --- a/SharedCode.Data.EntityFramework/Data.EntityFramework.csproj +++ b/SharedCode.Data.EntityFramework/Data.EntityFramework.csproj @@ -10,6 +10,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive +