diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..514ebd5 --- /dev/null +++ b/.github/copilot-instructions.md @@ -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. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 11c5d7d..17ca3e2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -32,6 +32,9 @@ updates: Web: patterns: - "Microsoft.AspNetCore*" + OpenTelemetry: + patterns: + - "OpenTelemetry*" Tests: patterns: - "Microsoft.NET.Test*" diff --git a/.github/workflows/dotnet-env.yml b/.github/workflows/dotnet-env.yml index a76d0fd..1206d10 100644 --- a/.github/workflows/dotnet-env.yml +++ b/.github/workflows/dotnet-env.yml @@ -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 @@ -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 @@ -41,4 +44,9 @@ jobs: title: "⚙ Update dotnet versions" body: "Update dotnet versions" commit-message: "Update dotnet versions" - token: ${{ env.GH_TOKEN }} \ No newline at end of file + 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." diff --git a/.github/workflows/dotnet-file-core.yml b/.github/workflows/dotnet-file-core.yml index 824f52c..3475f1a 100644 --- a/.github/workflows/dotnet-file-core.yml +++ b/.github/workflows/dotnet-file-core.yml @@ -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 @@ -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<> $env:GITHUB_ENV cat $changelog >> $env:GITHUB_ENV diff --git a/.github/workflows/includes.yml b/.github/workflows/includes.yml index fc6dbeb..8fd6690 100644 --- a/.github/workflows/includes.yml +++ b/.github/workflows/includes.yml @@ -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 @@ -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 @@ -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: | @@ -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." diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index 56ff299..99eec76 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -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" diff --git a/.gitignore b/.gitignore index 0fe79fb..25e70dd 100644 --- a/.gitignore +++ b/.gitignore @@ -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 @@ -25,6 +32,7 @@ local.settings.json *.binlog *.zip __azurite*.* +AzuriteConfig __*__ .nuget diff --git a/.netconfig b/.netconfig index f63a2cd..e42bcf8 100644 --- a/.netconfig +++ b/.netconfig @@ -24,8 +24,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 @@ -44,8 +44,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 @@ -54,8 +54,8 @@ weak [file ".gitignore"] url = https://github.com/devlooped/oss/blob/main/.gitignore - sha = 3776526342afb3f57da7e80f2095e5fdca3c31c9 - etag = 11767f73556aa4c6c8bcc153b77ee8e8114f99fa3b885b0a7d66d082f91e77b3 + sha = ff61659751374b95c7a8a0477c908a8119f756f0 + etag = e5865f083db45081a7b4eaa518018971b34e6ef93f917ac510dea96d27f792b3 weak [file "Directory.Build.rsp"] url = https://github.com/devlooped/oss/blob/main/Directory.Build.rsp @@ -79,13 +79,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 = c67952501337303eda0fb8b340cb7606666abd8f + etag = cb83faed0cc8b930a7b6bdc61bea03a54059858cf04353e55fee94d9e3ae0fad weak [file ".github/workflows/combine-prs.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/combine-prs.yml @@ -114,16 +114,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 diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 29281ee..93a0b1e 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -47,6 +47,9 @@ false true + + + false @@ -166,6 +169,10 @@ + + + + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 083afa6..20a680d 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -32,6 +32,22 @@ true + + + <_PackageId>$(PackageId) + + $(MSBuildSDKsPath)\..\NuGet.Build.Tasks.Pack.targets + + + + + + + $(_PackageId) + + false + + @@ -174,7 +190,7 @@ @@ -184,6 +200,16 @@ + + + + + OSMFEULA.txt + true + + + +