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."
12 changes: 9 additions & 3 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 All @@ -74,7 +80,7 @@ jobs:
}

- name: +Mᐁ includes
uses: devlooped/actions-includes@v1
uses: devlooped/actions-includes@v2
with:
validate: false

Expand Down
16 changes: 12 additions & 4 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,16 +23,16 @@ 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
uses: devlooped/actions-includes@v2

- name: 📝 OSMF EULA
shell: pwsh
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."
12 changes: 11 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ env:
Configuration: Release
PackOnBuild: true
GeneratePackageOnBuild: true
VersionLabel: ${{ github.ref }}
VersionLabel: refs/tags/${{ github.event.release.tag_name }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
MSBUILDTERMINALLOGGER: auto
SLEET_FEED_URL: https://api.nuget.org/v3/index.json
Expand Down Expand Up @@ -44,6 +44,16 @@ jobs:
name: logs
path: '*.binlog'

- name: ✅ validate
shell: pwsh
working-directory: bin
run: |
$invalid = Get-ChildItem -File -Filter "*.42.42*.nupkg"
if ($invalid) {
Write-Error "Found dev/local packages with disallowed 42.42* version prefix:`n$($invalid.Name -join "`n")"
exit 1
}

- name: 🚀 nuget
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
Expand Down
10 changes: 7 additions & 3 deletions .github/workflows/triage.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: 'triage'
on:
schedule:
- cron: '42 0 * * *'
- cron: '42 0 1,15 * *'

workflow_dispatch:
# Manual triggering through the GitHub UI, API, or CLI
Expand Down 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 Expand Up @@ -100,4 +104,4 @@ jobs:
closeDays: ${{ fromJson(inputs.daysBeforeClose || 30) }}
closeComment: "This issue has been closed automatically because it needs more information and has not had recent activity.\n\nHappy Coding!"
pingDays: 80
pingComment: "Hey @${assignee}, this issue might need further attention.\n\n@${author}, you can help us out by closing this issue if the problem no longer exists, or adding more information."
pingComment: "Hey @${assignee}, this issue might need further attention.\n\n@${author}, you can help us out by closing this issue if the problem no longer exists, or adding more information."
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
bin
obj
out
artifacts
pack
agent-tools
terminals
TestResults
results
BenchmarkDotNet.Artifacts
mcps
/app
/temp
.vs
.vscode
.genaiscript
.idea
local.settings.json
.env
.next
*.local

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

.nuget
Expand Down
Loading
Loading