Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# .NET Repository

**Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.**

## Working Effectively

### Essential Build Commands
- **Restore dependencies**: `dotnet restore`

- **Build the entire solution**: `dotnet build`

- **Run tests**: `dnx --yes retest`
- Runs all unit tests across the solution
- If tests fail due to Azure Storage, run the following commands and retry: `npm install azurite` and `npx azurite &`

### Build Validation and CI Requirements
- **Always run before committing**:
* `dnx --yes retest`
* `dotnet format whitespace -v:diag --exclude ~/.nuget`
* `dotnet format style -v:diag --exclude ~/.nuget`

### Project Structure and Navigation

| Directory | Description |
|-----------|-------------|
| `src/` | Contains the repo source code. |
| `bin/` | Contains built packages (if any) |

### Code Style and Formatting

#### EditorConfig Rules
The repository uses `.editorconfig` at the repo root for consistent code style.

- **Indentation**: 4 spaces for C# files, 2 spaces for XML/YAML/JSON
- **Line endings**: LF (Unix-style)
- **Sort using directives**: System.* namespaces first (`dotnet_sort_system_directives_first = true`)
- **Type references**: Prefer language keywords over framework type names (`int` vs `Int32`)
- **Modern C# features**: Use object/collection initializers, coalesce expressions when possible, use var when the type is apparent from the right-hand side of the assignment
- **Visibility modifiers**: only explicitly specify visibility when different from the default (e.g. `public` for classes, no `internal` for classes or `private` for fields, etc.)

#### Formatting Validation
- CI enforces formatting with `dotnet format whitespace` and `dotnet format style`
- Run locally: `dotnet format whitespace --verify-no-changes -v:diag --exclude ~/.nuget`
- Fix formatting: `dotnet format` (without `--verify-no-changes`)

### Testing Practices

#### Test Framework
- **xUnit** for all unit and integration tests
- **Moq** for mocking dependencies
- Located in `src/*.Tests/`

#### Test Attributes
Custom xUnit attributes are sometimes used for conditional test execution:
- `[SecretsFact("XAI_API_KEY")]` - Skips test if required secrets are missing from user secrets or environment variables
- `[LocalFact("SECRET")]` - Runs only locally (skips in CI), requires specified secrets
- `[CIFact]` - Runs only in CI environment

### Dependency Management

#### Adding Dependencies
- Add to appropriate `.csproj` file
- Run `dotnet restore` to update dependencies
- Ensure version consistency across projects where applicable

#### CI/CD Pipeline
- **Build workflow**: `.github/workflows/build.yml` - runs on PR and push to main/rel/feature branches
- **Publish workflow**: Publishes to Sleet feed when `SLEET_CONNECTION` secret is available
- **OS matrix**: Configured in `.github/workflows/os-matrix.json` (defaults to ubuntu-latest)

### Special Files and Tools

#### dnx Command
- **Purpose**: built-in tool for running arbitrary dotnet tools that are published on nuget.org. `--yes` auto-confirms install before run.
- **Example**: `dnx --yes retest` - runs tests with automatic retry on transient failures (retest being a tool package published at https://www.nuget.org/packages/retest)
- **In CI**: `dnx --yes retest -- --no-build` (skips build, runs tests only)

#### Directory.Build.rsp
- MSBuild response file with default build arguments
- `-nr:false` - disables node reuse
- `-m:1` - single-threaded build (for stability)
- `-v:m` - minimal verbosity

#### Code Quality
- All PRs must pass format validation
- Tests must pass on all target frameworks
- Follow existing patterns and conventions in the codebase

## Documenting Work

Project implemention details, design and key decisions should be documented in a top-level AGENTS.md file at the repo root.
Keep this file updated whenever you make change significant changes for future reference.

User-facing features and APIs should be documented to highlight (not extensively, as an overview) key project features and capabilities, in the readme.md file at the repo root.
3 changes: 3 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ updates:
Web:
patterns:
- "Microsoft.AspNetCore*"
OpenTelemetry:
patterns:
- "OpenTelemetry*"
Tests:
patterns:
- "Microsoft.NET.Test*"
Expand Down
14 changes: 11 additions & 3 deletions .github/workflows/dotnet-env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ on:
jobs:
which-dotnet:
runs-on: ubuntu-latest
env:
PR_TOKEN: ${{ secrets.DEVLOOPED_TOKEN || secrets.GH_TOKEN }}
permissions:
contents: write
pull-requests: write
Expand All @@ -20,18 +22,19 @@ jobs:
with:
name: ${{ secrets.BOT_NAME }}
email: ${{ secrets.BOT_EMAIL }}
gh_token: ${{ secrets.GH_TOKEN }}
gh_token: ${{ env.PR_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}

- name: 🤘 checkout
uses: actions/checkout@v4
with:
token: ${{ env.GH_TOKEN }}
token: ${{ env.PR_TOKEN || github.token }}

- name: 🤌 dotnet
uses: devlooped/actions-which-dotnet@v1

- name: ✍ pull request
if: env.PR_TOKEN != ''
uses: peter-evans/create-pull-request@v7
with:
base: main
Expand All @@ -41,4 +44,9 @@ jobs:
title: "⚙ Update dotnet versions"
body: "Update dotnet versions"
commit-message: "Update dotnet versions"
token: ${{ env.GH_TOKEN }}
token: ${{ env.PR_TOKEN }}

- name: ⚠️ skip pull request
if: env.PR_TOKEN == ''
shell: bash
run: echo "::warning::Skipping PR creation because neither DEVLOOPED_TOKEN nor GH_TOKEN is configured. GITHUB_TOKEN cannot create pull requests in this repository."
10 changes: 8 additions & 2 deletions .github/workflows/dotnet-file-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,19 @@ jobs:
# if we don't have at least 100 requests left, wait until reset
if ($rate.remaining -lt 10) {
$wait = ($rate.reset - (Get-Date (Get-Date).ToUniversalTime() -UFormat %s))
echo "Rate limit remaining is $($rate.remaining), waiting for $($wait / 1000) seconds to reset"
if ($wait -gt 300) {
echo "Rate limit remaining is $($rate.remaining), reset in $wait seconds (more than 5'). Aborting."
exit 1
}
echo "Rate limit remaining is $($rate.remaining), waiting $wait seconds to reset"
sleep $wait
$rate = gh api rate_limit | convertfrom-json | select -expandproperty rate
echo "Rate limit has reset to $($rate.remaining) requests"
}

- name: 🔄 sync
env:
GCM_CREDENTIAL_STORE: cache
run: |
dotnet tool update -g dotnet-gcm
# store credentials in plaintext for linux compat
Expand All @@ -63,7 +69,7 @@ jobs:

dotnet tool update -g dotnet-file
$changelog = "$([System.IO.Path]::GetTempPath())dotnet-file.md"
dotnet file sync -c:$changelog
dotnet file sync -c:$changelog --init https://github.com/devlooped/oss/blob/main/.netconfig
if (test-path $changelog) {
echo 'CHANGES<<EOF' >> $env:GITHUB_ENV
cat $changelog >> $env:GITHUB_ENV
Expand Down
14 changes: 11 additions & 3 deletions .github/workflows/includes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ on:
jobs:
includes:
runs-on: ubuntu-latest
env:
PR_TOKEN: ${{ secrets.DEVLOOPED_TOKEN || secrets.GH_TOKEN }}
permissions:
contents: write
pull-requests: write
Expand All @@ -21,13 +23,13 @@ jobs:
with:
name: ${{ secrets.BOT_NAME }}
email: ${{ secrets.BOT_EMAIL }}
gh_token: ${{ secrets.GH_TOKEN }}
gh_token: ${{ env.PR_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}

- name: 🤘 checkout
uses: actions/checkout@v4
with:
token: ${{ env.GH_TOKEN }}
token: ${{ env.PR_TOKEN || github.token }}

- name: +Mᐁ includes
uses: devlooped/actions-includes@v1
Expand All @@ -50,6 +52,7 @@ jobs:
((get-content -raw $file) -replace '\$product\$',$product).trim() | set-content $file

- name: ✍ pull request
if: env.PR_TOKEN != ''
uses: peter-evans/create-pull-request@v8
with:
add-paths: |
Expand All @@ -64,4 +67,9 @@ jobs:
commit-message: +Mᐁ includes
title: +Mᐁ includes
body: +Mᐁ includes
token: ${{ env.GH_TOKEN }}
token: ${{ env.PR_TOKEN }}

- name: ⚠️ skip pull request
if: env.PR_TOKEN == ''
shell: bash
run: echo "::warning::Skipping PR creation because neither DEVLOOPED_TOKEN nor GH_TOKEN is configured. GITHUB_TOKEN cannot create pull requests in this repository."
6 changes: 5 additions & 1 deletion .github/workflows/triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ jobs:
# if we don't have at least 100 requests left, wait until reset
if ($rate.remaining -lt 100) {
$wait = ($rate.reset - (Get-Date (Get-Date).ToUniversalTime() -UFormat %s))
echo "Rate limit remaining is $($rate.remaining), waiting for $($wait / 1000) seconds to reset"
if ($wait -gt 300) {
echo "Rate limit remaining is $($rate.remaining), reset in $wait seconds (more than 5'). Aborting."
exit 1
}
echo "Rate limit remaining is $($rate.remaining), waiting $wait seconds to reset"
sleep $wait
$rate = gh api rate_limit | convertfrom-json | select -expandproperty rate
echo "Rate limit has reset to $($rate.remaining) requests"
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
bin
obj
out
artifacts
pack
TestResults
Expand All @@ -12,6 +13,8 @@ BenchmarkDotNet.Artifacts
.idea
local.settings.json
.env
.next
*.local

*.suo
*.sdf
Expand All @@ -25,6 +28,7 @@ local.settings.json
*.binlog
*.zip
__azurite*.*
AzuriteConfig
__*__

.nuget
Expand Down
43 changes: 27 additions & 16 deletions .netconfig
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
weak
[file ".github/dependabot.yml"]
url = https://github.com/devlooped/oss/blob/main/.github/dependabot.yml
sha = e733294084fb3e75d517a2e961e87df8faae7dc6
etag = 3bf8d9214a15c049ca5cfe80d212a8cbe4753b8a638a9804ef73d34c7def9618
sha = 387f0616b2c56adc4f33f2858da818f7e0d92ef3
etag = a1aaba1440e5ee2a7b7f93fc0fb004d4e1d4d9780350c753f7c429e37241345a
weak
[file ".github/workflows/build.yml"]
url = https://github.com/devlooped/oss/blob/main/.github/workflows/build.yml
Expand All @@ -53,8 +53,8 @@
weak
[file ".github/workflows/includes.yml"]
url = https://github.com/devlooped/oss/blob/main/.github/workflows/includes.yml
sha = 06628725a6303bb8c4cf3076a384fc982a91bc0b
etag = 478f91d4126230e57cc601382da1ba23f9daa054645b4af89800d8dd862e64fd
sha = 7c1c6e615b5785e0ac9db33cb17343d6c1de16ff
etag = 5e6a10be141ee629201bfad01eae09b5c36a67f541ec7ab411ae400b5d73de1d
weak
[file ".github/workflows/publish.yml"]
url = https://github.com/devlooped/oss/blob/main/.github/workflows/publish.yml
Expand All @@ -63,8 +63,8 @@
weak
[file ".gitignore"]
url = https://github.com/devlooped/oss/blob/main/.gitignore
sha = 3776526342afb3f57da7e80f2095e5fdca3c31c9
etag = 11767f73556aa4c6c8bcc153b77ee8e8114f99fa3b885b0a7d66d082f91e77b3
sha = c76f0cc3e225e39e79420587d94852e12cdcb32a
etag = 09b499201361a59fad0036e925f4008cdd7bdc6723ba37ff18cc509e6024b2bf
weak
[file "Directory.Build.rsp"]
url = https://github.com/devlooped/oss/blob/main/Directory.Build.rsp
Expand All @@ -88,13 +88,13 @@
weak
[file "src/Directory.Build.props"]
url = https://github.com/devlooped/oss/blob/main/src/Directory.Build.props
sha = 0ff8b7b79a82112678326d1dc5543ed890311366
etag = 3ebde0a8630d526b80f15801179116e17a857ff880a4442e7db7b075efa4fd63
sha = 6e2438919e108aeb75106dc0737c45f5e55d5f42
etag = f1d6384abf18d8d891ce5e835a10c73fe029c42151374be96d7e4af43d189c65
weak
[file "src/Directory.Build.targets"]
url = https://github.com/devlooped/oss/blob/main/src/Directory.Build.targets
sha = 4339749ef4b8f66def75931df09ef99c149f8421
etag = 8b4492765755c030c4c351e058a92f53ab493cab440c1c0ef431f6635c4dae0e
sha = 6e2438919e108aeb75106dc0737c45f5e55d5f42
etag = 31cf95c54402944a7aeddcde93cea5f2403ae594212307513d9b93c53ef62fb6
weak
[file "src/kzu.snk"]
url = https://github.com/devlooped/oss/blob/main/src/kzu.snk
Expand Down Expand Up @@ -126,16 +126,27 @@
weak
[file ".github/workflows/dotnet-env.yml"]
url = https://github.com/devlooped/oss/blob/main/.github/workflows/dotnet-env.yml
sha = 77e83f238196d2723640abef0c7b6f43994f9747
etag = fcb9759a96966df40dcd24906fd328ddec05953b7e747a6bb8d0d1e4c3865274
sha = 7c1c6e615b5785e0ac9db33cb17343d6c1de16ff
etag = 68c1e28f475ff9d05f985bf53a51fce6e64b24a8b75bd8e47119ff57309281f1
weak
[file ".github/workflows/dotnet-file-core.yml"]
url = https://github.com/devlooped/oss/blob/main/.github/workflows/dotnet-file-core.yml
sha = d00364faaa84c414b868c0758b9e1a5fc0520dcc
etag = d3b7d8ca69e6d22066a2348f02b409e2d6fb900f5039f68940c14e4ce6021826
sha = 61a602fc61eedbdae235f01e93657a6219ac2427
etag = 1de1d7aff26365e25be4abc0c728e10c20df9c4f1adc0cd1f28485c5238883e0
weak
[file ".github/workflows/triage.yml"]
url = https://github.com/devlooped/oss/blob/main/.github/workflows/triage.yml
sha = 33000c0c4ab4eb4e0e142fa54515b811a189d55c
etag = 013a47739e348f06891f37c45164478cca149854e6cd5c5158e6f073f852b61a
sha = 61a602fc61eedbdae235f01e93657a6219ac2427
etag = 152cd3a559c08da14d1da12a5262ba1d2e0ed6bed6d2eabf5bd209b0c35d8a75
weak
[file "readme.tmp.md"]
url = https://github.com/devlooped/oss/blob/main/readme.tmp.md
skip
[file "oss.cs"]
url = https://github.com/devlooped/oss/blob/main/oss.cs
skip
[file ".github/copilot-instructions.md"]
url = https://github.com/devlooped/oss/blob/main/.github/copilot-instructions.md
sha = e616d89d9537c4b8ccf1c20dd277ab82104167c4
etag = 6ee650d118a57494d3545d54718ccaa5257b09d54504e9c21514fe596edd9678
weak
7 changes: 7 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
<WarnOnPackingNonPackableProject>false</WarnOnPackingNonPackableProject>
<!-- See https://learn.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#prunepackagereference-specification -->
<RestoreEnablePackagePruning>true</RestoreEnablePackagePruning>

<!-- Needed to improve nuget default for empty PackageId. Workaround for https://github.com/NuGet/Home/issues/14928 -->
<ImportNuGetBuildTasksPackTargetsFromSdk>false</ImportNuGetBuildTasksPackTargetsFromSdk>
</PropertyGroup>

<PropertyGroup Label="Build">
Expand Down Expand Up @@ -166,6 +169,10 @@
<Using Include="System.ArgumentNullException" Static="true" />
</ItemGroup>

<ItemGroup Label="OSMF" Condition="Exists('$(MSBuildThisFileDirectory)..\osmfeula.txt')">
<Content Include="$(MSBuildThisFileDirectory)..\osmfeula.txt" Link="osmfeula.txt" Pack="true" PackagePath="OSMFEULA.txt" Visible="false" />
</ItemGroup>

<Import Project="Directory.props" Condition="Exists('Directory.props')"/>
<Import Project="Directory.props.user" Condition="Exists('Directory.props.user')" />

Expand Down
Loading
Loading